diff --git a/.env b/.env index 3d8d13419fd0a0cab9b0ea1aa817eedb9ee758c7..eb30919784bcdaddaf95d9aa8992ae45f7aeb7a8 100644 --- a/.env +++ b/.env @@ -29,4 +29,6 @@ MESSENGER_TRANSPORT_DSN=sync:// # DATABASE_URL="mysql://app:!ChangeMe!@127.0.0.1:3306/app?serverVersion=10.11.2-MariaDB&charset=utf8mb4" DATABASE_URL="mysql://root:root@db_docker_symfony:3306/wishlist?serverVersion=8.0" ###< doctrine/doctrine-bundle ### +APP_SECRET=3d9f431b0fd768e0cb2e8b139c66e9fd67ecbd49762f6a345d459e2f115f61b4 + diff --git a/config/packages/security.yaml b/config/packages/security.yaml index 367af25a56d2decba5041becc5770b16c91b60d4..730a51d59c5f378a09e2851542bc18acce65cccc 100644 --- a/config/packages/security.yaml +++ b/config/packages/security.yaml @@ -1,39 +1,38 @@ security: - # https://symfony.com/doc/current/security.html#registering-the-user-hashing-passwords password_hashers: Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto' - # https://symfony.com/doc/current/security.html#loading-the-user-the-user-provider + providers: - users_in_memory: { memory: null } + app_user_provider: + entity: + class: App\Entity\User + property: email # Ou 'username' si tu utilises un autre identifiant + firewalls: dev: pattern: ^/(_(profiler|wdt)|css|images|js)/ security: false + main: lazy: true - provider: users_in_memory - - # activate different ways to authenticate - # https://symfony.com/doc/current/security.html#the-firewall - - # https://symfony.com/doc/current/security/impersonating_user.html - # switch_user: true + provider: app_user_provider + form_login: + login_path: login + check_path: login + logout: + path: logout + remember_me: + secret: '%kernel.secret%' - # Easy way to control access for large sections of your site - # Note: Only the *first* access control that matches will be used access_control: - # - { path: ^/admin, roles: ROLE_ADMIN } - # - { path: ^/profile, roles: ROLE_USER } + - { path: ^/admin, allow_if: "user and user.isAdmin == true" } + - { path: ^/*, allow_if: "user and user.isLocked != true" } when@test: security: password_hashers: - # By default, password hashers are resource intensive and take time. This is - # important to generate secure password hashes. In tests however, secure hashes - # are not important, waste resources and increase test times. The following - # reduces the work factor to the lowest possible values. Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: algorithm: auto - cost: 4 # Lowest possible value for bcrypt - time_cost: 3 # Lowest possible value for argon - memory_cost: 10 # Lowest possible value for argon + cost: 4 + time_cost: 3 + memory_cost: 10 diff --git a/config/routes/security.yaml b/config/routes/security.yaml index f853be15cf348ec1b6caaff738de0153eb9eeef0..60a7afbd5ca17a7fd80af9e4b322bfa0090a569e 100644 --- a/config/routes/security.yaml +++ b/config/routes/security.yaml @@ -1,3 +1,3 @@ _security_logout: resource: security.route_loader.logout - type: service + type: service \ No newline at end of file diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 0e63ddeecd626fd30e476e754ff43f0f5f3fd263..fef96537d168fa9c22c83f939f7e22f3df50e227 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -10,7 +10,6 @@ services: ports: - 3306:3306 environment: - MYSQL_ROOT_USER: 'root' MYSQL_ROOT_PASSWORD: 'root' networks: - dev diff --git a/public/css/style.css b/public/css/style.css index d99f15edffb29623d7efc145459e7d667490d11d..31d7b1329076bcc7230699698fbeceee9b48180f 100644 --- a/public/css/style.css +++ b/public/css/style.css @@ -287,4 +287,43 @@ h1 { border-radius: 3px; padding: 5px; cursor: pointer; +} + +/* Couleurs IMT Atlantique */ +:root { + --imt-primary: #00326E; + --imt-secondary: #009FE3; + --imt-accent: #96C11F; +} + +/* Styles généraux */ +body { + background-color: #f8f9fa; +} + +/* En-tête */ +.navbar { + background-color: var(--imt-primary); +} + +/* Boutons */ +.btn-primary { + background-color: var(--imt-primary); + border-color: var(--imt-primary); +} + +.btn-secondary { + background-color: var(--imt-secondary); + border-color: var(--imt-secondary); +} + +/* Cards */ +.card { + box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075); +} + +/* Tables */ +.table thead th { + background-color: var(--imt-primary); + color: white; } \ No newline at end of file diff --git a/src/Controller/AdminController.php b/src/Controller/AdminController.php new file mode 100644 index 0000000000000000000000000000000000000000..a4477ee421b398d76140ebe76dd3bf4f200e3aa6 --- /dev/null +++ b/src/Controller/AdminController.php @@ -0,0 +1,85 @@ +<?php + +namespace App\Controller; + +use App\Repository\UserRepository; +use App\Repository\WishlistRepository; +use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Routing\Attribute\Route; +use Doctrine\ORM\EntityManagerInterface; + +use App\Entity\Item; +use App\Entity\Wishlist; +use Twig\TokenParser\UseTokenParser; + +final class AdminController extends AbstractController +{ + #[Route('/admin', name: 'app_admin')] + public function index(): Response + { + return $this->render('admin/index.html.twig', [ + 'controller_name' => 'AdminController', + ]); + } + + #[Route('/admin/dashboard', name: 'admin_dashboard')] + public function dashboard(EntityManagerInterface $entityManager, WishlistRepository $wishlistRepository, UserRepository $userRepository): Response + { + $topItems = $wishlistRepository->mostExpensiveItems(); + $topWishlists = $wishlistRepository->mostExpensiveLists(); + $users = $userRepository->findAll(); // Get all users + + + if (!$topItems) { + $topItems = []; + } + + if (!$topWishlists) { + $topWishlists = []; + } + return $this->render('admin/dashboard.html.twig', [ + 'topItems' => $topItems, + 'topWishlists' => $topWishlists, + 'users' => $users, + ]); + } + + #[Route('/admin/users', name: 'admin_users')] + public function manageUsers(EntityManagerInterface $entityManager): Response + { + $users = $entityManager->getRepository(User::class)->findAll(); + + return $this->render('admin/users.html.twig', [ + 'users' => $users, + ]); + } + + #[Route('/admin/user/{id}/lock', name: 'admin_user_lock', methods: ['POST'])] + public function lockUser(User $user, EntityManagerInterface $entityManager): Response + { + $user->setIsLocked(true); + $entityManager->flush(); + + return $this->redirectToRoute('admin_users'); + } + + #[Route('/admin/user/{id}/unlock', name: 'admin_user_unlock', methods: ['POST'])] + public function unlockUser(User $user, EntityManagerInterface $entityManager): Response + { + $user->setIsLocked(false); + $entityManager->flush(); + + return $this->redirectToRoute('admin_users'); + } + + #[Route('/admin/user/{id}/delete', name: 'admin_user_delete', methods: ['POST'])] + public function deleteUser(User $user, EntityManagerInterface $entityManager): Response + { + $entityManager->remove($user); + $entityManager->flush(); + + return $this->redirectToRoute('admin_users'); + } + +} diff --git a/src/Controller/RegistrationController.php b/src/Controller/RegistrationController.php new file mode 100644 index 0000000000000000000000000000000000000000..fbf5028dc649bb727b6452e82a533df8d11bd6ad --- /dev/null +++ b/src/Controller/RegistrationController.php @@ -0,0 +1,40 @@ +<?php + +namespace App\Controller; + +use App\Entity\User; +use App\Form\RegistrationFormType; +use Doctrine\ORM\EntityManagerInterface; +use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface; +use Symfony\Component\Routing\Annotation\Route; + +class RegistrationController extends AbstractController +{ + #[Route('/register', name: 'register')] + public function register(Request $request, UserPasswordHasherInterface $passwordHasher, EntityManagerInterface $entityManager): Response + { + $user = new User(); + $form = $this->createForm(RegistrationFormType::class, $user); + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + // Hacher le mot de passe + $hashedPassword = $passwordHasher->hashPassword($user, $user->getPassword()); + $user->setPassword($hashedPassword); + + // Sauvegarder l'utilisateur + $entityManager->persist($user); + $entityManager->flush(); + + // Rediriger vers la page de connexion + return $this->redirectToRoute('login'); + } + + return $this->render('registration/register.html.twig', [ + 'registrationForm' => $form->createView(), + ]); + } +} \ No newline at end of file diff --git a/src/Controller/SecurityController.php b/src/Controller/SecurityController.php new file mode 100644 index 0000000000000000000000000000000000000000..af98a7890bd20a3874338b304a04450b892529d1 --- /dev/null +++ b/src/Controller/SecurityController.php @@ -0,0 +1,33 @@ +<?php + +namespace App\Controller; + +use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Routing\Annotation\Route; +use Symfony\Component\Security\Http\Authentication\AuthenticationUtils; +use Doctrine\ORM\EntityManagerInterface; + +class SecurityController extends AbstractController +{ + #[Route('/login', name: 'login')] + public function login(AuthenticationUtils $authenticationUtils): Response + { + // Récupère les erreurs de connexion, s'il y en a + $error = $authenticationUtils->getLastAuthenticationError(); + + // Récupère le dernier email saisi par l'utilisateur + $lastUsername = $authenticationUtils->getLastUsername(); + + return $this->render('security/login.html.twig', [ + 'last_username' => $lastUsername, + 'error' => $error, + ]); + } + + #[Route('/logout', name: 'logout')] + public function logout(): void + { + // Ce contrôleur peut rester vide, Symfony gère la déconnexion automatiquement + } +} \ No newline at end of file diff --git a/src/Controller/UserController.php b/src/Controller/UserController.php new file mode 100644 index 0000000000000000000000000000000000000000..b058aa940aab4de75d2b82cd10a39f23e66d420c --- /dev/null +++ b/src/Controller/UserController.php @@ -0,0 +1,65 @@ +<?php + +namespace App\Controller; + +use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Routing\Attribute\Route; +use Doctrine\ORM\EntityManagerInterface; + +final class UserController extends AbstractController +{ + #[Route('/user', name: 'app_user')] + public function index(): Response + { + return $this->render('user/index.html.twig', [ + 'controller_name' => 'UserController', + ]); + } + + + #[Route('/user/dashboard', name: 'user_dashboard')] + public function dashboard(EntityManagerInterface $entityManager): Response + { + $user = $this->getUser(); + $wishlists = $entityManager->getRepository(Wishlist::class)->findBy(['owner' => $user]); + + return $this->render('user/dashboard.html.twig', [ + 'user' => $user, + 'wishlists' => $wishlists, + ]); + } + + #[Route('/user/wishlist/new', name: 'user_wishlist_new', methods: ['GET', 'POST'])] + public function createWishlist(Request $request, EntityManagerInterface $entityManager): Response + { + $wishlist = new Wishlist(); + $form = $this->createForm(WishlistType::class, $wishlist); + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + $wishlist->setOwner($this->getUser()); + $entityManager->persist($wishlist); + $entityManager->flush(); + + $this->addFlash('success', 'Wishlist créée avec succès.'); + return $this->redirectToRoute('user_dashboard'); + } + + return $this->render('user/wishlist_form.html.twig', [ + 'form' => $form->createView(), + ]); + } + + #[Route('/user/wishlist/{id}/delete', name: 'user_wishlist_delete', methods: ['POST'])] + public function deleteWishlist(Wishlist $wishlist, EntityManagerInterface $entityManager): Response + { + $this->denyAccessUnlessGranted('delete', $wishlist); + + $entityManager->remove($wishlist); + $entityManager->flush(); + + $this->addFlash('success', 'Wishlist supprimée avec succès.'); + return $this->redirectToRoute('user_dashboard'); + } +} diff --git a/src/Controller/WishlistController.php b/src/Controller/WishlistController.php index 1a39ebad3397e67b8f4a4f0fe0f45fa8d708130a..24a225ff6509c799ca1bd7a442bafd9c99337dde 100644 --- a/src/Controller/WishlistController.php +++ b/src/Controller/WishlistController.php @@ -15,7 +15,7 @@ use Symfony\Component\Routing\Attribute\Route; final class WishlistController extends AbstractController { #[Route(name: 'app_wishlist_index', methods: ['GET'])] - public function index(WishlistRepository $wishlistRepository): Response + public function getWishLists(WishlistRepository $wishlistRepository): Response { return $this->render('wishlist/index.html.twig', [ 'wishlists' => $wishlistRepository->findAll(), @@ -23,11 +23,11 @@ final class WishlistController extends AbstractController } #[Route('/new', name: 'app_wishlist_new', methods: ['GET', 'POST'])] - public function new(Request $request, EntityManagerInterface $entityManager): Response + public function createWishlist(Request $request, EntityManagerInterface $entityManager): Response { $wishlist = new Wishlist(); - $name = $request->get('name'); - $wishlist->setName($name); + $name = $request->get(key: 'name'); + $wishlist->setName(name: $name); $deadline = $request->get('deadline') ; $wishlist->setDeadline($deadline); $entityManager->persist($wishlist); diff --git a/src/Entity/User.php b/src/Entity/User.php new file mode 100644 index 0000000000000000000000000000000000000000..197c87673a763194b5e52811967924622573e979 --- /dev/null +++ b/src/Entity/User.php @@ -0,0 +1,180 @@ +<?php + +namespace App\Entity; + +use App\Repository\UserRepository; +use Doctrine\ORM\Mapping as ORM; +use Symfony\Component\Security\Core\User\UserInterface; +use Doctrine\Common\Collections\ArrayCollection; +use Doctrine\Common\Collections\Collection; + +#[ORM\Entity(repositoryClass: UserRepository::class)] +class User implements UserInterface +{ + private array $roles = []; + + public function getRoles(): array + { + return $this->roles; + } + + public function getUserIdentifier(): string + { + return $this->email; + } + + public function eraseCredentials(): void + { + // If you store any temporary, sensitive data on the user, clear it here + // $this->plainPassword = null; + } + + #[ORM\Id] + #[ORM\GeneratedValue] + #[ORM\Column] + private ?int $id = null; + + #[ORM\Column(length: 255)] + private ?string $email = null; + + #[ORM\Column(length: 63)] + private ?string $firstName = null; + + #[ORM\Column(length: 63)] + private ?string $lastName = null; + + #[ORM\Column(length: 255)] + private ?string $password = null; + + #[ORM\Column] + private ?bool $isLocked = null; + + #[ORM\Column] + private ?bool $isAdmin = null; + + #[ORM\Column(length: 255, nullable: true)] + private ?string $image = null; + + #[ORM\OneToMany(mappedBy: 'owner', targetEntity: Wishlist::class)] + private Collection $wishlists; + + #[ORM\OneToMany(mappedBy: 'invitedUser', targetEntity: Item::class)] + private Collection $invitations; + + public function __construct() + { + $this->wishlists = new ArrayCollection(); + $this->invitations = new ArrayCollection(); + $this->roles = ['ROLE_USER']; + $this->isLocked = false; + } + + public function getId(): ?int + { + return $this->id; + } + + public function getEmail(): ?string + { + return $this->email; + } + + public function setEmail(string $email): static + { + $this->email = $email; + + return $this; + } + + public function getFirstName(): ?string + { + return $this->firstName; + } + + public function setFirstName(string $firstName): static + { + $this->firstName = $firstName; + + return $this; + } + + public function getLastName(): ?string + { + return $this->lastName; + } + + public function setLastName(string $lastName): static + { + $this->lastName = $lastName; + + return $this; + } + + public function getPassword(): ?string + { + return $this->password; + } + + public function setPassword(string $password): static + { + $this->password = $password; + + return $this; + } + + public function isLocked(): ?bool + { + return $this->isLocked; + } + + public function setIsLocked(bool $isLocked): static + { + $this->isLocked = $isLocked; + + return $this; + } + + public function isAdmin(): ?bool + { + return $this->isAdmin; + } + + public function setIsAdmin(bool $isAdmin): static + { + $this->isAdmin = $isAdmin; + + return $this; + } + + public function getImage(): ?string + { + return $this->image; + } + + public function setImage(?string $image): static + { + $this->image = $image; + + return $this; + } + + public function getWishlists(): Collection + { + return $this->wishlists; + } + + // public function getInvitations(): Collection + // { + // return $this->invitations; + // } + + // public function addInvitation(Item $invitation): static + // { + // if (!$this->invitations->contains($invitation)) { + // $this->invitations[] = $invitation; + // $invitation->setInvitedUser($this); + // } + + // } + +} diff --git a/src/Entity/Wishlist.php b/src/Entity/Wishlist.php index 97924ccb743f29f8fd7af796b8b2be03fd9ec101..9c16360e90dd7fe7e5adfe93d8137a1594cb0313 100644 --- a/src/Entity/Wishlist.php +++ b/src/Entity/Wishlist.php @@ -125,7 +125,7 @@ class Wishlist public function getItemById(int $id){ $itemsArray = (($this->items->toArray())) ; - for ($i = 0; $i < count($itemsArray); $i++) { + for ($i = 0; $i < sizeof($itemsArray); $i++) { $item = $itemsArray[$i]; if ($item->getId() == $id ) { return $item; diff --git a/src/Form/RegistrationFormType.php b/src/Form/RegistrationFormType.php new file mode 100644 index 0000000000000000000000000000000000000000..89c463ad2a136d76a099b5bd4a20cb14d5d139e0 --- /dev/null +++ b/src/Form/RegistrationFormType.php @@ -0,0 +1,70 @@ +<?php + +namespace App\Form; + +use App\Entity\User; +use Symfony\Component\Form\AbstractType; +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\TextType; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\OptionsResolver\OptionsResolver; +use Symfony\Component\Validator\Constraints\File; +use Symfony\Component\Validator\Constraints\Length; +use Symfony\Component\Validator\Constraints\NotBlank; + +class RegistrationFormType extends AbstractType +{ + public function buildForm(FormBuilderInterface $builder, array $options): void + { + $builder + ->add('firstName', TextType::class, [ + 'label' => 'Prénom', + 'constraints' => [ + new NotBlank(), + ], + ]) + ->add('lastName', TextType::class, [ + 'label' => 'Nom', + 'constraints' => [ + new NotBlank(), + ], + ]) + ->add('email', EmailType::class, [ + 'label' => 'Email', + 'constraints' => [ + new NotBlank(), + ], + ]) + ->add('password', PasswordType::class, [ + 'label' => 'Mot de passe', + 'constraints' => [ + new NotBlank(), + new Length(['min' => 6]), + ], + ]) + ->add('image', FileType::class, [ + 'label' => 'Image de profil (optionnel)', + 'required' => false, + 'mapped' => false, + 'constraints' => [ + new File([ + 'maxSize' => '2M', + 'mimeTypes' => [ + 'image/jpeg', + 'image/png', + ], + 'mimeTypesMessage' => 'Veuillez télécharger une image valide (JPEG ou PNG).', + ]), + ], + ]); + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'data_class' => User::class, + ]); + } +} \ No newline at end of file diff --git a/src/Repository/ItemRepository.php b/src/Repository/ItemRepository.php index 229f40652e92f36659747e345c2569cb626b2aa2..1d57e0458ffc005511fbbefc7e45b7d958689ceb 100644 --- a/src/Repository/ItemRepository.php +++ b/src/Repository/ItemRepository.php @@ -16,6 +16,15 @@ class ItemRepository extends ServiceEntityRepository parent::__construct($registry, Item::class); } + + public function findTopExpensiveItems(): array + { + return $this->createQueryBuilder('i') // Alias "i" pour Item + ->orderBy('i.price', 'DESC') // Trier par prix décroissant + ->setMaxResults(3) // Limiter à 3 résultats + ->getQuery() + ->getResult(); // Exécuter la requête + } // /** // * @return Item[] Returns an array of Item objects // */ diff --git a/src/Repository/UserRepository.php b/src/Repository/UserRepository.php new file mode 100644 index 0000000000000000000000000000000000000000..58793646c59e2a296843548663a195d99b0d8f75 --- /dev/null +++ b/src/Repository/UserRepository.php @@ -0,0 +1,69 @@ +<?php + +namespace App\Repository; + +use App\Entity\User; +use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; +use Doctrine\Persistence\ManagerRegistry; + +/** + * @extends ServiceEntityRepository<User> + */ +class UserRepository extends ServiceEntityRepository +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, User::class); + } + + public function findAllUsers(): array +{ + return $this->createQueryBuilder('u') + ->orderBy('u.username', 'ASC') + ->getQuery() + ->getResult(); +} + +public function findLockedUsers(): array +{ + return $this->createQueryBuilder('u') + ->where('u.isLocked = :locked') + ->setParameter('locked', true) + ->getQuery() + ->getResult(); +} + +public function findByEmail(string $email): ?User +{ + return $this->createQueryBuilder('u') + ->where('u.email = :email') + ->setParameter('email', $email) + ->getQuery() + ->getOneOrNullResult(); +} + + // /** + // * @return User[] Returns an array of User objects + // */ + // public function findByExampleField($value): array + // { + // return $this->createQueryBuilder('u') + // ->andWhere('u.exampleField = :val') + // ->setParameter('val', $value) + // ->orderBy('u.id', 'ASC') + // ->setMaxResults(10) + // ->getQuery() + // ->getResult() + // ; + // } + + // public function findOneBySomeField($value): ?User + // { + // return $this->createQueryBuilder('u') + // ->andWhere('u.exampleField = :val') + // ->setParameter('val', $value) + // ->getQuery() + // ->getOneOrNullResult() + // ; + // } +} diff --git a/src/Repository/WishlistRepository.php b/src/Repository/WishlistRepository.php index 93ae0c346f8f98da6aabcdbc5697fe72b343b3b6..637aa1d319898d6ff827e43b4fcd4680e156abf8 100644 --- a/src/Repository/WishlistRepository.php +++ b/src/Repository/WishlistRepository.php @@ -42,21 +42,62 @@ class WishlistRepository extends ServiceEntityRepository $wishlist->setDeadline($deadline); $this->getEntityManager()->flush(); } + return $wishlist; } - public function mostExpensiveList() { + public function mostExpensiveLists() { $wishlists = $this->findAll() ; + $rankings = array(); + foreach ($wishlists as $wishlist) { + $total = $wishlist->wishlistTotalPrice() ; + + if (sizeof(($rankings)) < 3 ) { + $rankings[] = ['wishlist' => $wishlist, 'total' => $total]; + usort($rankings, callback: function($a, $b) {return $a['total'] - $b['total'];}); + } else { + for ($i = 0; $i < sizeof($rankings) ; $i++ ) { + if ($rankings[i]['total'] < $total ) { + $rankings[i] = ['wishlist' => $wishlist, 'total' => $total] ; + } + } - + } + + } + $result = array() ; + for ($i = 0 ; $i < sizeof($rankings) ; $i++ ) { + $result[] = $rankings[i]['wishlist'] ; + } + return $result; + } + + + public function mostExpensiveItems() { + $wishlists = $this->findAll() ; + $rankings = array(); foreach ($wishlists as $wishlist) { $items = $wishlist->getItems(); - } + foreach ($items as $item) { + if ($item->getPurchaseProof()) { + if (sizeof(($rankings)) < 3 ) { + $rankings[] = $item; + usort( $rankings, function($a, $b) {return $a->getPrice() - $b->getPrice();}); + } else { + for ($i = 0; $i < sizeof($rankings) ; $i++ ) { + if ($rankings[i]->getPrice() < $item->getPrice() ) { + $rankings[i] = $item ; + } + } + } + } + } + } + return $rankings; } - // /** // * @return Wishlist[] Returns an array of Wishlist objects // */ diff --git a/templates/admin/dashboard.html.twig b/templates/admin/dashboard.html.twig new file mode 100644 index 0000000000000000000000000000000000000000..8c663bf6246bcb3e7b9dbdbafa838a3394d31cf9 --- /dev/null +++ b/templates/admin/dashboard.html.twig @@ -0,0 +1,118 @@ +{% extends 'base.html.twig' %} + +{% block title %}Administration{% endblock %} + +{% block body %} +<div class="container mt-4"> + <h1>Dashboard Administrateur</h1> + + <div class="row mt-4"> + <!-- Statistiques --> + <div class="col-md-6"> + <div class="card"> + <div class="card-body"> + <h5 class="card-title">Top 3 des items les plus chers</h5> + {% if topItems is empty %} + <p class="text-muted">Aucun item trouvé</p> + {% else %} + <ul class="list-group"> + {% for item in topItems %} + <li class="list-group-item d-flex justify-content-between align-items-center"> + {{ item.title }} + <span class="badge bg-primary rounded-pill">{{ item.price }} €</span> + </li> + {% endfor %} + </ul> + {% endif %} + </div> + </div> + </div> + + <div class="col-md-6"> + <div class="card"> + <div class="card-body"> + <h5 class="card-title">Top 3 des wishlists par valeur</h5> + {% if topWishlists is empty %} + <p class="text-muted">Aucune wishlist trouvée</p> + {% else %} + <ul class="list-group"> + {% for wishlist in topWishlists %} + <li class="list-group-item d-flex justify-content-between align-items-center"> + {{ wishlist.name }} + <span class="badge bg-success rounded-pill">{{ wishlist.totalValue }} €</span> + </li> + {% endfor %} + </ul> + {% endif %} + </div> + </div> + </div> + </div> + + <!-- Gestion des utilisateurs --> + <div class="card mt-4"> + <div class="card-body"> + <h5 class="card-title">Gestion des utilisateurs</h5> + + {% if users is empty %} + <p class="text-muted">Aucun utilisateur enregistré</p> + {% else %} + <div class="table-responsive"> + <table class="table"> + <thead> + <tr> + <th>ID</th> + <th>Nom d'utilisateur</th> + <th>Email</th> + <th>Statut</th> + <th>Actions</th> + </tr> + </thead> + <tbody> + {% for user in users %} + <tr> + <td>{{ user.id }}</td> + <td>{{ user.username }}</td> + <td>{{ user.email }}</td> + <td> + {% if user.isLocked %} + <span class="badge bg-danger">Verrouillé</span> + {% else %} + <span class="badge bg-success">Actif</span> + {% endif %} + </td> + <td> + <div class="btn-group"> + {% if user.isLocked %} + <form method="post" action="{{ path('admin_user_unlock', {'id': user.id}) }}" style="display: inline;"> + <button type="submit" class="btn btn-sm btn-success"> + <i class="fas fa-unlock"></i> + </button> + </form> + {% else %} + <form method="post" action="{{ path('admin_user_lock', {'id': user.id}) }}" style="display: inline;"> + <button type="submit" class="btn btn-sm btn-warning"> + <i class="fas fa-lock"></i> + </button> + </form> + {% endif %} + <form method="post" + action="{{ path('admin_user_delete', {'id': user.id}) }}" + onsubmit="return confirm('Êtes-vous sûr de vouloir supprimer cet utilisateur ?');" + style="display: inline;"> + <button type="submit" class="btn btn-sm btn-danger"> + <i class="fas fa-trash"></i> + </button> + </form> + </div> + </td> + </tr> + {% endfor %} + </tbody> + </table> + </div> + {% endif %} + </div> + </div> +</div> +{% endblock %} \ No newline at end of file diff --git a/templates/admin/index.html.twig b/templates/admin/index.html.twig new file mode 100644 index 0000000000000000000000000000000000000000..5e68ff4e7a5e497b75dd457e0f744777635d1ede --- /dev/null +++ b/templates/admin/index.html.twig @@ -0,0 +1,20 @@ +{% extends 'base.html.twig' %} + +{% block title %}Hello AdminController!{% endblock %} + +{% block body %} +<style> + .example-wrapper { margin: 1em auto; max-width: 800px; width: 95%; font: 18px/1.5 sans-serif; } + .example-wrapper code { background: #F5F5F5; padding: 2px 6px; } +</style> + +<div class="example-wrapper"> + <h1>Hello {{ controller_name }}! ✅</h1> + + This friendly message is coming from: + <ul> + <li>Your controller at <code>/home/user/www/Exercices/Wishlist-application/src/Controller/AdminController.php</code></li> + <li>Your template at <code>/home/user/www/Exercices/Wishlist-application/templates/admin/index.html.twig</code></li> + </ul> +</div> +{% endblock %} diff --git a/templates/base.html.twig b/templates/base.html.twig index 2eac7d55cf86cc2a94f66f2aaddedac60af3edff..213ebafff224acd34ceb8ae6605b8a2da7215aae 100644 --- a/templates/base.html.twig +++ b/templates/base.html.twig @@ -6,13 +6,29 @@ <link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 128 128%22><text y=%221.2em%22 font-size=%2296%22>⚫️</text><text y=%221.3em%22 x=%220.2em%22 font-size=%2276%22 fill=%22%23fff%22>sf</text></svg>"> <link rel="stylesheet" href="{{ asset('css/style.css') }}"> {% block stylesheets %} + <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet"> + <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css" rel="stylesheet"> + <link href="{{ asset('css/style.css') }}" rel="stylesheet"> {% endblock %} {% block javascripts %} - {% block importmap %}{{ importmap('app') }}{% endblock %} + {% block importmap %}{{ importmap('app') }} + {% endblock %} + <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script> + <script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/js/all.min.js"></script> {% endblock %} </head> <body> + {% for label, messages in app.flashes %} + {% for message in messages %} + <div class="alert alert-{{ label }} alert-dismissible fade show" role="alert"> + {{ message }} + <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button> + </div> + {% endfor %} + {% endfor %} + + {% block body %}{% endblock %} </body> </html> diff --git a/templates/registration/index.html.twig b/templates/registration/index.html.twig new file mode 100644 index 0000000000000000000000000000000000000000..e7ded79bcf6cbee1615e2aec99461f2ee3d298a3 --- /dev/null +++ b/templates/registration/index.html.twig @@ -0,0 +1,20 @@ +{% extends 'base.html.twig' %} + +{% block title %}Hello RegistrationController!{% endblock %} + +{% block body %} +<style> + .example-wrapper { margin: 1em auto; max-width: 800px; width: 95%; font: 18px/1.5 sans-serif; } + .example-wrapper code { background: #F5F5F5; padding: 2px 6px; } +</style> + +<div class="example-wrapper"> + <h1>Hello {{ controller_name }}! ✅</h1> + + This friendly message is coming from: + <ul> + <li>Your controller at <code>/home/user/www/Exercices/Wishlist-application/src/Controller/RegistrationController.php</code></li> + <li>Your template at <code>/home/user/www/Exercices/Wishlist-application/templates/registration/index.html.twig</code></li> + </ul> +</div> +{% endblock %} diff --git a/templates/registration/register.html.twig b/templates/registration/register.html.twig new file mode 100644 index 0000000000000000000000000000000000000000..1f9833dab4f5fb7ea0d896e4431a7ff418c77a1e --- /dev/null +++ b/templates/registration/register.html.twig @@ -0,0 +1,18 @@ +{% extends 'base.html.twig' %} + +{% block title %}Inscription{% endblock %} + +{% block body %} +<div class="container mt-5"> + <h1>Inscription</h1> + + {{ form_start(registrationForm) }} + {{ form_row(registrationForm.firstName) }} + {{ form_row(registrationForm.lastName) }} + {{ form_row(registrationForm.email) }} + {{ form_row(registrationForm.password) }} + {{ form_row(registrationForm.image) }} + <button type="submit" class="btn btn-primary">S'inscrire</button> + {{ form_end(registrationForm) }} +</div> +{% endblock %} \ No newline at end of file diff --git a/templates/security/index.html.twig b/templates/security/index.html.twig new file mode 100644 index 0000000000000000000000000000000000000000..71358c2f5b7080448345245c47ee3bb24f7ec76b --- /dev/null +++ b/templates/security/index.html.twig @@ -0,0 +1,20 @@ +{% extends 'base.html.twig' %} + +{% block title %}Hello SecurityController!{% endblock %} + +{% block body %} +<style> + .example-wrapper { margin: 1em auto; max-width: 800px; width: 95%; font: 18px/1.5 sans-serif; } + .example-wrapper code { background: #F5F5F5; padding: 2px 6px; } +</style> + +<div class="example-wrapper"> + <h1>Hello {{ controller_name }}! ✅</h1> + + This friendly message is coming from: + <ul> + <li>Your controller at <code>/home/user/www/Exercices/Wishlist-application/src/Controller/SecurityController.php</code></li> + <li>Your template at <code>/home/user/www/Exercices/Wishlist-application/templates/security/index.html.twig</code></li> + </ul> +</div> +{% endblock %} diff --git a/templates/security/login.html.twig b/templates/security/login.html.twig new file mode 100644 index 0000000000000000000000000000000000000000..244b6d55b638885ea8710f7eda847f831bc10cbf --- /dev/null +++ b/templates/security/login.html.twig @@ -0,0 +1,29 @@ +{% extends 'base.html.twig' %} + +{% block title %}Connexion{% endblock %} + +{% block body %} +<div class="container mt-5"> + <h1>Connexion</h1> + + {% if error %} + <div class="alert alert-danger"> + {{ error.messageKey|trans(error.messageData, 'security') }} + </div> + {% endif %} + + <form method="post" action="{{ path('login') }}"> + <div class="mb-3"> + <label for="username" class="form-label">Email</label> + <input type="text" name="_username" id="username" class="form-control" value="{{ last_username }}" required autofocus> + </div> + + <div class="mb-3"> + <label for="password" class="form-label">Mot de passe</label> + <input type="password" name="_password" id="password" class="form-control" required> + </div> + + <button type="submit" class="btn btn-primary">Se connecter</button> + </form> +</div> +{% endblock %} \ No newline at end of file diff --git a/templates/user/index.html.twig b/templates/user/index.html.twig new file mode 100644 index 0000000000000000000000000000000000000000..bea6b09038379ee264d5ba67bf9e5ae757eb49f2 --- /dev/null +++ b/templates/user/index.html.twig @@ -0,0 +1,20 @@ +{% extends 'base.html.twig' %} + +{% block title %}Hello UserController!{% endblock %} + +{% block body %} +<style> + .example-wrapper { margin: 1em auto; max-width: 800px; width: 95%; font: 18px/1.5 sans-serif; } + .example-wrapper code { background: #F5F5F5; padding: 2px 6px; } +</style> + +<div class="example-wrapper"> + <h1>Hello {{ controller_name }}! ✅</h1> + + This friendly message is coming from: + <ul> + <li>Your controller at <code>/home/user/www/Exercices/Wishlist-application/src/Controller/UserController.php</code></li> + <li>Your template at <code>/home/user/www/Exercices/Wishlist-application/templates/user/index.html.twig</code></li> + </ul> +</div> +{% endblock %}