'txt'])] public function index(Request $request): Response { $robots = new RobotsTxt(); $robots->disallow('/signature'); $robots->disallow('/payment'); $robots->crawlDelay(60); $robots->allow('/reservation'); $robots->sitemap($request->getSchemeAndHttpHost().'/seo/sitemap.xml'); return new Response($robots->toString(), Response::HTTP_OK, [ 'Content-Type' => 'text/plain' ]); } #[Route('/', name: 'reservation')] public function revervation(FormulesRepository $formulesRepository, ProductRepository $productRepository): Response { $products = $productRepository->findBy(['category' => '3-15 ans'], ['updatedAt' => 'DESC'], 3); $formules = $formulesRepository->findBy(['isPublish' => true], ['pos' => 'ASC'], 3); return $this->render('revervation/home.twig', [ 'products' => $products, 'formules' => $formules, ]); } #[Route('/produit/check', name: 'produit_check', methods: ['GET', 'POST'])] public function productCheck(Request $request, ProductReserveRepository $productReserveRepository, ProductRepository $productRepository): Response { $productId = $request->query->get('id'); $startStr = $request->query->get('start'); $endStr = $request->query->get('end'); if (!$productId && $request->isMethod('POST')) { $payload = $request->getPayload(); $productId = $payload->get('id'); $startStr = $payload->get('start'); $endStr = $payload->get('end'); } if (!$productId || !$startStr || !$endStr) { return new JsonResponse(['error' => 'Missing parameters'], Response::HTTP_BAD_REQUEST); } $product = $productRepository->find($productId); if (!$product) { return new JsonResponse(['error' => 'Product not found'], Response::HTTP_NOT_FOUND); } try { $start = new \DateTimeImmutable($startStr); $end = new \DateTimeImmutable($endStr); } catch (\Exception $e) { return new JsonResponse(['error' => 'Invalid date format'], Response::HTTP_BAD_REQUEST); } $reserve = new ProductReserve(); $reserve->setProduct($product); $reserve->setStartAt($start); $reserve->setEndAt($end); $isAvailable = $productReserveRepository->checkAvailability($reserve); return new JsonResponse(['dispo' => $isAvailable]); } #[Route('/web-vitals', name: 'reservation_web-vitals', methods: ['POST'])] public function webVitals(Request $request, EntityManagerInterface $em): Response { $data = json_decode($request->getContent(), true); if (!$data || !isset($data['name'], $data['value'])) { return new Response('Invalid data', Response::HTTP_BAD_REQUEST); } $existing = $em->getRepository(SitePerformance::class)->findOneBy(['metricId' => $data['id']]); $perf = $existing ?? new SitePerformance(); $perf->setName($data['name']); $perf->setValue((float)$data['value']); $perf->setPath($data['path'] ?? '/'); $perf->setMetricId($data['id'] ?? null); $perf->setCreatedAt(new \DateTimeImmutable()); if (!$existing) { $em->persist($perf); } $em->flush(); return new Response('', Response::HTTP_NO_CONTENT); } #[Route('/basket/json', name: 'reservation_basket_json', methods: ['POST'])] public function basketJson(Request $request, ProductRepository $productRepository, UploaderHelper $uploaderHelper): Response { $data = json_decode($request->getContent(), true); $ids = $data['ids'] ?? []; $startStr = $data['start'] ?? null; $endStr = $data['end'] ?? null; // Calcul de la durée $duration = 1; if ($startStr && $endStr) { try { $start = new \DateTimeImmutable($startStr); $end = new \DateTimeImmutable($endStr); if ($end >= $start) { $duration = $start->diff($end)->days + 1; } } catch (\Exception $e) { $duration = 1; } } if (!is_array($ids)) { $ids = []; } $products = []; if (!empty($ids)) { $products = $productRepository->findBy(['id' => $ids]); } $items = []; $totalHT = 0; $tvaEnabled = isset($_ENV['TVA_ENABLED']) && $_ENV['TVA_ENABLED'] === "true"; $tvaRate = $tvaEnabled ? 0.20 : 0; foreach ($products as $product) { $price1Day = $product->getPriceDay(); $priceSup = $product->getPriceSup() ?? 0.0; // Calcul du coût total pour ce produit selon la durée $productTotalHT = $price1Day + ($priceSup * max(0, $duration - 1)); $productTotalTTC = $productTotalHT * (1 + $tvaRate); $items[] = [ 'id' => $product->getId(), 'name' => $product->getName(), 'image' => $uploaderHelper->asset($product, 'imageFile'), 'priceHt1Day' => $price1Day, 'priceHTSupDay' => $priceSup, 'priceTTC1Day' => $price1Day * (1 + $tvaRate), 'totalPriceHT' => $productTotalHT, 'totalPriceTTC' => $productTotalTTC, ]; $totalHT += $productTotalHT; } $totalTva = $totalHT * $tvaRate; $totalTTC = $totalHT + $totalTva; return new JsonResponse([ 'start_date' => $startStr, 'end_date' => $endStr, 'products' => $items, 'total' => [ 'totalHT' => $totalHT, 'totalTva' => $totalTva, 'totalTTC' => $totalTTC ] ]); } #[Route('/session', name: 'reservation_session_create', methods: ['POST'])] public function createSession(Request $request, EntityManagerInterface $em, OrderSessionRepository $sessionRepository): Response { $data = json_decode($request->getContent(), true); $existingUuid = $request->getSession()->get('order_session_uuid'); $session = null; if ($existingUuid) { $session = $sessionRepository->findOneBy(['uuid' => $existingUuid]); } if (!$session) { $session = new \App\Entity\OrderSession(); $session->setUuid(\Symfony\Component\Uid\Uuid::v4()->toRfc4122()); $session->setState('created'); } $session->setProducts($data ?? []); $user = $this->getUser(); if ($user instanceof Customer) { $session->setCustomer($user); } $em->persist($session); $em->flush(); $request->getSession()->set('order_session_uuid', $session->getUuid()); return new JsonResponse([ 'flowUrl' => $this->generateUrl('reservation_flow', ['sessionId' => $session->getUuid()]) ]); } #[Route('/flow/{sessionId}', name: 'reservation_flow', methods: ['GET', 'POST'])] public function flowLogin( string $sessionId, AuthenticationUtils $authenticationUtils, OrderSessionRepository $repository, ProductRepository $productRepository, UploaderHelper $uploaderHelper ): Response { // This is the POST target for the login form, but also the GET page. // The authenticator handles the POST. For GET, we just render the page. $session = $repository->findOneBy(['uuid' => $sessionId]); if (!$session) { return $this->render('revervation/session_lost.twig'); } $sessionData = $session->getProducts(); $ids = $sessionData['ids'] ?? []; $startStr = $sessionData['start'] ?? null; $endStr = $sessionData['end'] ?? null; // Calcul de la durée $duration = 1; if ($startStr && $endStr) { try { $start = new \DateTimeImmutable($startStr); $end = new \DateTimeImmutable($endStr); if ($end >= $start) { $duration = $start->diff($end)->days + 1; } } catch (\Exception $e) { $duration = 1; } } $products = []; if (!empty($ids)) { $products = $productRepository->findBy(['id' => $ids]); } $items = []; $totalHT = 0; $tvaEnabled = isset($_ENV['TVA_ENABLED']) && $_ENV['TVA_ENABLED'] === "true"; $tvaRate = $tvaEnabled ? 0.20 : 0; foreach ($products as $product) { $price1Day = $product->getPriceDay(); $priceSup = $product->getPriceSup() ?? 0.0; // Calcul du coût total pour ce produit selon la durée $productTotalHT = $price1Day + ($priceSup * max(0, $duration - 1)); $productTotalTTC = $productTotalHT * (1 + $tvaRate); $items[] = [ 'product' => $product, 'image' => $uploaderHelper->asset($product, 'imageFile'), 'price1Day' => $price1Day, 'priceSup' => $priceSup, 'totalPriceHT' => $productTotalHT, 'totalPriceTTC' => $productTotalTTC, ]; $totalHT += $productTotalHT; } $totalTva = $totalHT * $tvaRate; $totalTTC = $totalHT + $totalTva; return $this->render('revervation/flow.twig', [ 'session' => $session, 'last_username' => $authenticationUtils->getLastUsername(), 'error' => $authenticationUtils->getLastAuthenticationError(), 'cart' => [ 'items' => $items, 'startDate' => $startStr ? new \DateTimeImmutable($startStr) : null, 'endDate' => $endStr ? new \DateTimeImmutable($endStr) : null, 'duration' => $duration, 'totalHT' => $totalHT, 'totalTva' => $totalTva, 'totalTTC' => $totalTTC, 'tvaEnabled' => $tvaEnabled, ] ]); } #[Route('/flow/{sessionId}/update', name: 'reservation_flow_update', methods: ['POST'])] public function flowUpdate( string $sessionId, Request $request, OrderSessionRepository $repository, EntityManagerInterface $em ): Response { $session = $repository->findOneBy(['uuid' => $sessionId]); if (!$session) { return $this->redirectToRoute('reservation'); } $session->setBillingAddress($request->request->get('billingAddress')); $session->setBillingZipCode($request->request->get('billingZipCode')); $session->setBillingTown($request->request->get('billingTown')); $session->setAdressEvent($request->request->get('adressEvent')); $session->setAdress2Event($request->request->get('adress2Event')); $session->setZipCodeEvent($request->request->get('zipCodeEvent')); $session->setTownEvent($request->request->get('townEvent')); $session->setType($request->request->get('type')); $session->setDetails($request->request->get('details')); $session->setTypeSol($request->request->get('typeSol')); $session->setPente($request->request->get('pente')); $session->setAccess($request->request->get('access')); $distance = $request->request->get('distancePower'); if ($distance !== null && $distance !== '') { $session->setDistancePower((float)$distance); } $em->flush(); return $this->redirectToRoute('reservation_flow', ['sessionId' => $sessionId]); } #[Route('/umami', name: 'reservation_umami', methods: ['POST'])] public function umami( Request $request, CustomerTrackingRepository $customerTrackingRepository, EntityManagerInterface $em ): Response { /** @var Customer $user */ $user = $this->getUser(); if (!$user) { return new JsonResponse(['error' => 'User not found'], Response::HTTP_UNAUTHORIZED); } $data = json_decode($request->getContent(), true); $umamiSessionId = $data['umami_session'] ?? null; if (!$umamiSessionId) { return new JsonResponse(['error' => 'No session provided'], Response::HTTP_BAD_REQUEST); } $track = $customerTrackingRepository->findOneBy(['trackId' => $umamiSessionId]); if (!$track) { $track = new CustomerTracking(); $track->setTrackId($umamiSessionId); $track->setCreateAT(new \DateTime()); $track->setCustomer($user); $em->persist($track); } else { if ($track->getCustomer() !== $user) { $track->setCustomer($user); } } $em->flush(); return new JsonResponse(['status' => 'success']); } #[Route('/catalogue', name: 'reservation_catalogue')] public function revervationCatalogue(ProductRepository $productRepository): Response { return $this->render('revervation/catalogue.twig', [ 'products' => $productRepository->findAll(), 'tvaEnabled' => isset($_ENV['TVA_ENABLED']) && $_ENV['TVA_ENABLED'] === "true", ]); } #[Route('/formules', name: 'reservation_formules')] public function revervationFormules(FormulesRepository $formulesRepository): Response { return $this->render('revervation/formules.twig', [ 'formules' => $formulesRepository->findBy(['isPublish' => true], ['pos' => 'ASC']), 'tvaEnabled' => isset($_ENV['TVA_ENABLED']) && $_ENV['TVA_ENABLED'] === "true", ]); } #[Route('/formules/{slug}', name: 'reservation_formule_show')] public function revervationView(string $slug, FormulesRepository $formulesRepository): Response { $parts = explode('-', $slug); $realId = $parts[0]; $formule = $formulesRepository->find($realId); if (!$formule) { throw $this->createNotFoundException('Formules introuvable'); } return $this->render('revervation/formule/show.twig', [ 'formule' => $formule, 'tvaEnabled' => isset($_ENV['TVA_ENABLED']) && $_ENV['TVA_ENABLED'] === "true", ]); } #[Route('/comment-reserver', name: 'reservation_workflow')] public function revervationWorkfkow(): Response { return $this->render('revervation/workflow.twig'); } #[Route('/options/{id}', name: 'reservation_options_show')] public function revervationShowOpitons(string $id, ProductRepository $productRepository): Response { // TODO: Implement logic return new Response('Not implemented'); } #[Route('/produit/{id}', name: 'reservation_product_show')] public function revervationShowProduct(string $id, ProductRepository $productRepository): Response { $parts = explode('-', $id); $realId = $parts[0]; $product = $productRepository->find($realId); if (!$product) { throw $this->createNotFoundException('Produit introuvable'); } $allInCat = $productRepository->findBy(['category' => $product->getCategory()], [], 5); $otherProducts = array_filter($allInCat, function ($p) use ($product) { return $p->getId() !== $product->getId(); }); return $this->render('revervation/produit.twig', [ 'product' => $product, 'tvaEnabled' => isset($_ENV['TVA_ENABLED']) && $_ENV['TVA_ENABLED'] === "true", 'otherProducts' => array_slice($otherProducts, 0, 4) ]); } #[Route('/connexion', name: 'reservation_login')] public function revervationLogin(AuthenticationUtils $authenticationUtils): Response { return $this->render('revervation/login.twig', [ 'last_username' => $authenticationUtils->getLastUsername(), 'error' => $authenticationUtils->getLastAuthenticationError() ]); } #[Route('/logout', name: 'reservation_logout')] public function revervationLogout(): Response { return $this->redirectToRoute('reservation'); } #[Route('/creation-compte', name: 'reservation_register')] public function revervationRegister( Request $request, Mailer $mailer, EntityManagerInterface $em, UserPasswordHasherInterface $hasher ): Response { if ($request->isMethod('POST')) { $payload = $request->getPayload(); $customer = new Customer(); $customer->setEmail($payload->getString('email')); $customer->setName($payload->getString('name')); $customer->setSurname($payload->getString('surname')); $customer->setPhone($payload->getString('phone')); $customer->setCiv($payload->getString('civ')); $customer->setType($payload->getString('type')); if ($customer->getType() === 'buisness') { $customer->setSiret($payload->getString('siret')); } $hashedPassword = $hasher->hashPassword($customer, $payload->getString('password')); $customer->setPassword($hashedPassword); $customer->setRoles(['ROLE_USER']); $mailer->send( $customer->getEmail(), $customer->getName() . " " . $customer->getSurname(), "[Ludikevent] - Code de récupération", "mails/welcome.twig", ['account' => $customer] ); $em->persist($customer); $em->flush(); $this->addFlash('success', 'Votre compte a été créé avec succès ! Connectez-vous.'); return $this->redirectToRoute('reservation_login'); } return $this->render('revervation/register.twig'); } #[Route('/mot-de-passe', name: 'reservation_password')] public function forgotPassword( Request $request, CustomerRepository $repository, EntityManagerInterface $em, Mailer $mailer, UserPasswordHasherInterface $hasher ): Response { $session = $request->getSession(); $step = $request->query->get('step', 'request'); if ($request->isMethod('POST')) { $payload = $request->getPayload(); // ÉTAPE 1 : Générer le code et l'envoyer if ($payload->has('email_request')) { $email = $payload->getString('email_request'); $customer = $repository->findOneBy(['email' => $email]); if ($customer) { $code = str_pad((string)random_int(0, 999999), 6, '0', STR_PAD_LEFT); $session->set('reset_password', [ 'email' => $email, 'code' => $code, 'expires' => time() + 900 // Valable 15 minutes ]); $mailer->send( $customer->getEmail(), $customer->getName() . " " . $customer->getSurname(), "[Ludikevent] - Code de récupération", "mails/code_password.twig", ['code' => $code] ); return $this->redirectToRoute('reservation_password', ['step' => 'verify']); } $this->addFlash('danger', 'Email inconnu.'); } // ÉTAPE 2 : Vérifier le code en session if ($payload->has('code_verify')) { $data = $session->get('reset_password'); $inputCode = $payload->getString('code_verify'); if ($data && $data['code'] === $inputCode && time() < $data['expires']) { return $this->redirectToRoute('reservation_password', ['step' => 'reset']); } $this->addFlash('danger', 'Code invalide ou expiré.'); } // ÉTAPE 3 : Changer le mot de passe if ($payload->has('new_password')) { $data = $session->get('reset_password'); if ($data) { $customer = $repository->findOneBy(['email' => $data['email']]); if ($customer) { $newEncoded = $hasher->hashPassword($customer, $payload->getString('new_password')); $customer->setPassword($newEncoded); $em->flush(); $session->remove('reset_password'); $this->addFlash('success', 'Mot de passe mis à jour !'); return $this->redirectToRoute('reservation_login'); } } } } return $this->render('reservation/password.twig', [ 'step' => $step, 'email' => $session->get('reset_password')['email'] ?? null ]); } #[Route('/contact', name: 'reservation_contact')] public function revervationContact(Request $request, Mailer $mailer): Response { $form = $this->createFormBuilder() ->add('name', TextType::class, ['label' => 'Nom', 'required' => true]) ->add('surname', TextType::class, ['label' => 'Prenom', 'required' => true]) ->add('email', EmailType::class, ['label' => 'Email', 'required' => true]) ->add('phone', TextType::class, ['label' => 'Telephone', 'required' => true]) ->add('message', TextareaType::class, ['label' => 'Message', 'required' => true]); $formObject = $form->getForm(); $formObject->handleRequest($request); if ($formObject->isSubmitted() && $formObject->isValid()) { $data = $formObject->getData(); $mailer->send( 'lilian@ludikevent.fr', "Ludikevent", "[Ludikevent] - Demande de contact via la plateforme de reservation", "mails/reserve/contact.twig", $data ); $this->addFlash('success', 'Votre message a bien été envoyé ! Notre équipe vous répondra dans les plus brefs délais.'); return $this->redirectToRoute('reservation_contact'); } return $this->render('revervation/contact.twig', [ 'form' => $formObject->createView() ]); } #[Route('/recherche', name: 'reservation_search')] public function recherche(UploaderHelper $uploaderHelper, Client $client, Request $request, ProductRepository $productRepository): Response { $results = $client->search('product', $request->query->get('q', '')); $items = []; foreach ($results['hits'] as $result) { $p = $productRepository->find($result['id']); if ($p instanceof Product) { $items[] = [ 'image' => $uploaderHelper->asset($p, 'imageFile') ?: "/provider/images/favicon.png", "name" => $p->getName(), "price" => $p->getPriceDay(), "price1day" => $p->getPriceDay(), "caution" => $p->getCaution(), "priceSup" => $p->getPriceSup(), 'link' => $this->generateUrl('reservation_product_show', ['id' => $p->slug()]), ]; } } return $this->render('revervation/search.twig', [ 'products' => $items ]); } #[Route('/mentions-legales', name: 'reservation_mentions-legal')] public function revervationLegal(): Response { return $this->render('revervation/legal.twig'); } #[Route('/rgpd', name: 'reservation_rgpd')] public function revervationRgpd(): Response { return $this->render('revervation/rgpd.twig'); } #[Route('/cookies', name: 'reservation_cookies')] public function revervationCookies(): Response { return $this->render('revervation/cookies.twig'); } #[Route('/cgv', name: 'reservation_cgv')] public function revervationCgv(): Response { return $this->render('revervation/cgv.twig'); } #[Route('/hosting', name: 'reservation_hosting')] public function revervationHosting(): Response { return $this->render('revervation/hosting.twig'); } }