diff --git a/assets/tools/FlowReserve.js b/assets/tools/FlowReserve.js index d33474d..ff745e3 100644 --- a/assets/tools/FlowReserve.js +++ b/assets/tools/FlowReserve.js @@ -94,15 +94,15 @@ export class FlowReserve extends HTMLAnchorElement { ensureSidebarExists() { if (document.getElementById(this.sidebarId)) return; - + const template = `
- +
- +
@@ -112,12 +112,12 @@ export class FlowReserve extends HTMLAnchorElement {
- +
- + `; document.body.insertAdjacentHTML('beforeend', template); - + // Bind events const sidebar = document.getElementById(this.sidebarId); - + const closeHandler = (e) => { if (e) { e.preventDefault(); @@ -154,7 +154,7 @@ export class FlowReserve extends HTMLAnchorElement { footer.innerHTML = ''; const ids = this.getList(); - + // Retrieve dates from localStorage let dates = { start: null, end: null }; try { @@ -172,7 +172,7 @@ export class FlowReserve extends HTMLAnchorElement { const response = await fetch(this.apiUrl, { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ + body: JSON.stringify({ ids, start: dates.start, end: dates.end @@ -182,12 +182,12 @@ export class FlowReserve extends HTMLAnchorElement { if (!response.ok) throw new Error('Erreur réseau'); const data = await response.json(); - + // Merge client-side dates if server didn't return them (or to prioritize client choice) if (!data.start_date && dates.start) data.start_date = this.formatDate(dates.start); if (!data.end_date && dates.end) data.end_date = this.formatDate(dates.end); - - // Fallback: if server returns dates in a format we can use directly, fine. + + // Fallback: if server returns dates in a format we can use directly, fine. // If we just want to display what is in local storage: if (dates.start) data.start_date = this.formatDate(dates.start); if (dates.end) data.end_date = this.formatDate(dates.end); @@ -291,7 +291,7 @@ export class FlowReserve extends HTMLAnchorElement { ${this.formatPrice(total.totalTTC || total.totalHT)}
- + Valider ma demande `; @@ -306,7 +306,7 @@ export class FlowReserve extends HTMLAnchorElement { async validateBasket(e) { e.preventDefault(); - + const ids = this.getList(); let dates = { start: null, end: null }; try { @@ -316,10 +316,10 @@ export class FlowReserve extends HTMLAnchorElement { } try { - const response = await fetch('/reservation/session', { + const response = await fetch('/session', { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ + body: JSON.stringify({ ids, start: dates.start, end: dates.end diff --git a/src/Controller/ReserverController.php b/src/Controller/ReserverController.php index 19b7555..e456b57 100644 --- a/src/Controller/ReserverController.php +++ b/src/Controller/ReserverController.php @@ -198,7 +198,7 @@ class ReserverController extends AbstractController ]); } - #[Route('/reservation/session', name: 'reservation_session_create', methods: ['POST'])] + #[Route('/session', name: 'reservation_session_create', methods: ['POST'])] public function createSession(Request $request, EntityManagerInterface $em, OrderSessionRepository $sessionRepository): Response { $data = json_decode($request->getContent(), true); diff --git a/src/Security/ErrorListener.php b/src/Security/ErrorListener.php index 76776a7..ac56dce 100644 --- a/src/Security/ErrorListener.php +++ b/src/Security/ErrorListener.php @@ -2,37 +2,58 @@ namespace App\Security; +use App\Service\Mailer\Mailer; use Symfony\Component\EventDispatcher\Attribute\AsEventListener; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Event\ExceptionEvent; use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface; -use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Symfony\Component\HttpKernel\KernelEvents; use Twig\Environment; #[AsEventListener(event: KernelEvents::EXCEPTION)] class ErrorListener { - public function __construct(private Environment $twig) {} + public function __construct( + private readonly Mailer $mailer, + private readonly Environment $twig + ) {} public function onKernelException(ExceptionEvent $event): void { - // En mode dev, on laisse Symfony afficher la Toolbar et les erreurs détaillées if ($_ENV['APP_ENV'] === "dev") { return; } $exception = $event->getThrowable(); $request = $event->getRequest(); + $path = $request->getPathInfo(); - // Détermination du code HTTP - $statusCode = Response::HTTP_INTERNAL_SERVER_ERROR; // 500 par défaut - if ($exception instanceof HttpExceptionInterface) { - $statusCode = $exception->getStatusCode(); + $statusCode = $exception instanceof HttpExceptionInterface + ? $exception->getStatusCode() + : Response::HTTP_INTERNAL_SERVER_ERROR; + + // On envoie le mail pour les 500 ET les 404 + // Filtre optionnel : on évite les mails pour les fichiers techniques (.env, .php, etc) cherchés par les bots + $isBotTarget = preg_match('/\.(php|env|yaml|xml|map)$/i', $path); + + if (!$isBotTarget) { + $this->mailer->send( + 'notification@siteconseil.fr', + "Notification siteconseil", + "[Intranet Ludikevent] - Alerte " . ($statusCode === 404 ? "404 Page introuvable" : "500 Erreur serveur"), + "mails/tech/noaccess.twig", + [ + 'message' => [ + 'service' => 'Application Core', + 'status' => "Code $statusCode sur l'URL : $path", + 'trace' => $exception->getMessage() + ] + ] + ); } - // Détection si la requête attend du JSON (API/AJAX) + // --- Gestion de la Réponse (JSON vs HTML) --- $acceptHeader = $request->headers->get('Accept', ''); $isJsonRequest = str_contains($acceptHeader, 'application/json') || $request->getContentTypeFormat() === 'json'; @@ -43,7 +64,6 @@ class ErrorListener 'message' => $statusCode === 404 ? 'Resource not found' : 'Internal server error' ], $statusCode); } else { - // Sélection du template selon l'erreur $template = ($statusCode === 404) ? 'error/404.twig' : 'error/500.twig'; try { @@ -52,8 +72,7 @@ class ErrorListener 'exception' => $exception ]); } catch (\Exception $e) { - // Fallback si Twig plante lui-même - $html = "

Erreur critique

Une erreur inattendue est survenue.

"; + $html = "

Erreur critique ($statusCode)

Une erreur inattendue est survenue.

"; } $response = new Response($html, $statusCode); diff --git a/tests/Security/ErrorListenerTest.php b/tests/Security/ErrorListenerTest.php index 6d39337..0b4a544 100644 --- a/tests/Security/ErrorListenerTest.php +++ b/tests/Security/ErrorListenerTest.php @@ -3,6 +3,7 @@ namespace App\Tests\Security; use App\Security\ErrorListener; +use App\Service\Mailer\Mailer; use PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations; use PHPUnit\Framework\TestCase; use Symfony\Component\HttpFoundation\JsonResponse; @@ -17,12 +18,14 @@ use Twig\Environment; class ErrorListenerTest extends TestCase { private $twig; + private $mailer; private $listener; protected function setUp(): void { $this->twig = $this->createMock(Environment::class); - $this->listener = new ErrorListener($this->twig); + $this->mailer = $this->createMock(Mailer::class); + $this->listener = new ErrorListener($this->mailer, $this->twig); } public function testOnKernelExceptionInDevModeDoesNothing() @@ -32,11 +35,50 @@ class ErrorListenerTest extends TestCase $request = new Request(); $event = new ExceptionEvent($kernel, $request, HttpKernelInterface::MAIN_REQUEST, new \Exception()); + // Le mailer ne doit jamais être appelé en dev + $this->mailer->expects($this->never())->method('send'); + $this->listener->onKernelException($event); $this->assertNull($event->getResponse()); - - unset($_ENV['APP_ENV']); // Cleanup + + unset($_ENV['APP_ENV']); + } + + public function testOnKernelExceptionSendsEmailFor404() + { + $_ENV['APP_ENV'] = 'prod'; + $kernel = $this->createMock(HttpKernelInterface::class); + $request = Request::create('/une-page-existante'); + + $exception = new NotFoundHttpException('Not found'); + $event = new ExceptionEvent($kernel, $request, HttpKernelInterface::MAIN_REQUEST, $exception); + + // On vérifie que le mail est bien envoyé pour une 404 standard + $this->mailer->expects($this->once()) + ->method('send') + ->with($this->anything(), $this->anything(), $this->stringContains('404 Page introuvable')); + + $this->listener->onKernelException($event); + + unset($_ENV['APP_ENV']); + } + + public function testOnKernelExceptionDoesNotSendEmailForBotTarget() + { + $_ENV['APP_ENV'] = 'prod'; + $kernel = $this->createMock(HttpKernelInterface::class); + $request = Request::create('/.env'); // URL typique de bot + + $exception = new NotFoundHttpException('Not found'); + $event = new ExceptionEvent($kernel, $request, HttpKernelInterface::MAIN_REQUEST, $exception); + + // Le mailer ne doit PAS être appelé car c'est une cible de bot + $this->mailer->expects($this->never())->method('send'); + + $this->listener->onKernelException($event); + + unset($_ENV['APP_ENV']); } public function testOnKernelExceptionJsonRequest() @@ -45,7 +87,7 @@ class ErrorListenerTest extends TestCase $kernel = $this->createMock(HttpKernelInterface::class); $request = new Request(); $request->headers->set('Accept', 'application/json'); - + $exception = new NotFoundHttpException('Not found'); $event = new ExceptionEvent($kernel, $request, HttpKernelInterface::MAIN_REQUEST, $exception); @@ -54,11 +96,7 @@ class ErrorListenerTest extends TestCase $response = $event->getResponse(); $this->assertInstanceOf(JsonResponse::class, $response); $this->assertEquals(404, $response->getStatusCode()); - - $content = json_decode($response->getContent(), true); - $this->assertEquals('error', $content['status']); - $this->assertEquals('Resource not found', $content['message']); - + unset($_ENV['APP_ENV']); } @@ -67,8 +105,8 @@ class ErrorListenerTest extends TestCase $_ENV['APP_ENV'] = 'prod'; $kernel = $this->createMock(HttpKernelInterface::class); $request = new Request(); - - $exception = new \Exception('Error'); + + $exception = new \Exception('500 Error'); $event = new ExceptionEvent($kernel, $request, HttpKernelInterface::MAIN_REQUEST, $exception); $this->twig->expects($this->once()) @@ -79,10 +117,8 @@ class ErrorListenerTest extends TestCase $this->listener->onKernelException($event); $response = $event->getResponse(); - $this->assertInstanceOf(Response::class, $response); $this->assertEquals(500, $response->getStatusCode()); - $this->assertEquals('Error', $response->getContent()); - + unset($_ENV['APP_ENV']); } }