diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 992710b..990defa 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -2,10 +2,9 @@ "permissions": { "allow": [ "Bash(php -l:*)", - "Bash(./vendor/bin/phpunit --testdox)", - "Bash(./vendor/bin/php-cs-fixer fix:*)", - "Bash(ddev logs:*)", - "Bash(/opt/homebrew/bin/php-cs-fixer fix:*)" + "Bash(ddev php vendor/bin/phpunit --testdox)", + "Bash(ddev php vendor/bin/php-cs-fixer fix:*)", + "Bash(ddev logs:*)" ], "deny": [] } diff --git a/src/Controller/Booking/Create/IndexController.php b/src/Controller/Booking/Create/IndexController.php index 286ffff..f62d596 100644 --- a/src/Controller/Booking/Create/IndexController.php +++ b/src/Controller/Booking/Create/IndexController.php @@ -59,6 +59,9 @@ class IndexController extends AbstractController // Determine agency ID from optional query parameter $agencyId = $this->resolveAgencyId($request->query->get('agency')); + // Store optional return URL in session (defaults to main EP site) + $this->bookingService->storeReturnUrl($request, $request->query->get('r')); + // Create fresh booking session with the provided parameters $this->bookingService->startFreshBooking($request, $dateId, $hotelId, $agencyId); @@ -110,13 +113,17 @@ class IndexController extends AbstractController * Cancels the active booking session and returns to the appropriate page. * * This endpoint allows users to exit the booking flow at any time by - * clearing the booking session data and redirecting them to the account - * dashboard (if logged in) or login page (if guest). + * clearing the booking session data and redirecting them appropriately: + * - Logged-in users: redirected to account dashboard + * - Guest users: redirected to the return URL (stored during booking init) */ #[Route('/bookings/cancel', name: 'app_booking_cancel')] public function cancel(Request $request): Response { if (Request::METHOD_POST === $request->getMethod()) { + // Get return URL before clearing session (for guest users) + $returnUrl = $this->bookingService->getReturnUrl($request); + // Clear the booking session $this->bookingService->clearBookingSession($request); @@ -124,13 +131,14 @@ class IndexController extends AbstractController // Without this, logging in after cancel would redirect back to a stale booking URL $request->getSession()->remove('_security.main.target_path'); - // Add a flash message to inform the user - $this->addFlash('info', 'Buchung abgebrochen.'); + // Redirect to account dashboard if logged in, otherwise to return URL + if (null !== $this->getUser()) { + $this->addFlash('info', 'Buchung abgebrochen.'); - // Redirect to account dashboard if logged in, otherwise to login page - $targetRoute = null !== $this->getUser() ? 'app_account' : 'app_login'; + return $this->redirectToRoute('app_account'); + } - return $this->redirectToRoute($targetRoute); + return $this->redirect($returnUrl); } return $this->render('booking/modal_cancel.html.twig'); @@ -145,6 +153,8 @@ class IndexController extends AbstractController #[Route('/bookings/create/error', name: 'app_booking_create_error')] public function error(Request $request): Response { - return $this->render('booking/create/error.html.twig'); + return $this->render('booking/create/error.html.twig', [ + 'returnUrl' => $this->bookingService->getReturnUrl($request), + ]); } } diff --git a/src/Controller/Booking/Create/SuccessController.php b/src/Controller/Booking/Create/SuccessController.php index f9ce6b5..0f3b004 100644 --- a/src/Controller/Booking/Create/SuccessController.php +++ b/src/Controller/Booking/Create/SuccessController.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace App\Controller\Booking\Create; +use App\Service\BookingService; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; @@ -14,18 +15,25 @@ use Symfony\Component\Routing\Attribute\Route; */ class SuccessController extends AbstractController { + public function __construct( + private readonly BookingService $bookingService, + ) { + } + #[Route('/bookings/create/success', name: 'app_booking_create_success')] public function success(Request $request): Response { $bookingNumber = $request->getSession()->getFlashBag()->get('booking_number')[0] ?? null; + $returnUrl = $this->bookingService->getReturnUrl($request); - // Redirect to homepage if no booking number (direct access or refresh) + // Redirect to return URL if no booking number (direct access or refresh) if (null === $bookingNumber) { - return $this->redirect('https://www.ep-reisen.de'); + return $this->redirect($returnUrl); } return $this->render('booking/create/success.html.twig', [ 'bookingNumber' => $bookingNumber, + 'returnUrl' => $returnUrl, ]); } } diff --git a/src/Service/BookingService.php b/src/Service/BookingService.php index b3ce9f3..5d2e6bf 100644 --- a/src/Service/BookingService.php +++ b/src/Service/BookingService.php @@ -18,6 +18,8 @@ class BookingService public const BOOKING_CREATE_KEY = 'booking_create'; public const BOOKING_CREATE_BASELINE_KEY = 'booking_create_baseline_snapshot'; public const BOOKING_EDIT_KEY = 'booking_edit'; + public const RETURN_URL_KEY = 'booking_return_url'; + public const DEFAULT_RETURN_URL = 'https://www.ep-reisen.de'; public function __construct( private readonly TravelDataService $travelDataService, @@ -146,6 +148,8 @@ class BookingService * * This method removes all booking session data including the main DTO * and any cached snapshots to ensure a completely fresh start. + * Note: RETURN_URL_KEY is intentionally preserved so it remains available + * for redirects after cancel or error flows. */ public function clearBookingSession(Request $request): void { @@ -154,6 +158,36 @@ class BookingService $session->remove(self::BOOKING_CREATE_BASELINE_KEY); } + /** + * Stores the return URL in the session. + * + * Validates that the URL is a valid absolute URL with http/https scheme. + * Falls back to the default return URL if null or invalid. + */ + public function storeReturnUrl(Request $request, ?string $returnUrl): void + { + $url = self::DEFAULT_RETURN_URL; + + if (null !== $returnUrl && '' !== trim($returnUrl)) { + if (false !== filter_var($returnUrl, \FILTER_VALIDATE_URL) + && 1 === preg_match('#^https?://#i', $returnUrl)) { + $url = $returnUrl; + } + } + + $request->getSession()->set(self::RETURN_URL_KEY, $url); + } + + /** + * Retrieves the return URL from the session. + * + * Returns the default URL if not set in session. + */ + public function getReturnUrl(Request $request): string + { + return $request->getSession()->get(self::RETURN_URL_KEY, self::DEFAULT_RETURN_URL); + } + /** * Creates a fresh booking session with the provided travel parameters. * diff --git a/templates/_partials/_alert.html.twig b/templates/_partials/_alert.html.twig index 86a9a16..725dd31 100644 --- a/templates/_partials/_alert.html.twig +++ b/templates/_partials/_alert.html.twig @@ -2,8 +2,8 @@ {% set modal = false %} {% endif %}