feat: domain-based theme-detection
This commit is contained in:
+4
-1
@@ -4,7 +4,10 @@ docroot: public
|
|||||||
php_version: "8.3"
|
php_version: "8.3"
|
||||||
webserver_type: apache-fpm
|
webserver_type: apache-fpm
|
||||||
xdebug_enabled: false
|
xdebug_enabled: false
|
||||||
additional_hostnames: []
|
additional_hostnames:
|
||||||
|
- mysnz
|
||||||
|
- mysbw
|
||||||
|
- myser
|
||||||
additional_fqdns: []
|
additional_fqdns: []
|
||||||
database:
|
database:
|
||||||
type: mysql
|
type: mysql
|
||||||
|
|||||||
@@ -85,3 +85,6 @@ OAUTH_ENCRYPTION_KEY=580084fd179e67399467f59ee96658ac
|
|||||||
# postgresql+advisory://db_user:db_password@localhost/db_name
|
# postgresql+advisory://db_user:db_password@localhost/db_name
|
||||||
LOCK_DSN=flock
|
LOCK_DSN=flock
|
||||||
###< symfony/lock ###
|
###< symfony/lock ###
|
||||||
|
|
||||||
|
# Domain-to-theme mapping (JSON format)
|
||||||
|
DOMAIN_THEME_MAP='{"ski-boarderweek.de":"sbw","semesterende-skireisen.de":"ser","snowzone.net":"snz"}'
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ parameters:
|
|||||||
path_to_keys: '%kernel.project_dir%/config/secret'
|
path_to_keys: '%kernel.project_dir%/config/secret'
|
||||||
bpn_debug: '%env(APP_BPN_DEBUG)%'
|
bpn_debug: '%env(APP_BPN_DEBUG)%'
|
||||||
default_booking_status: '%env(DEFAULT_BOOKING_STATUS)%'
|
default_booking_status: '%env(DEFAULT_BOOKING_STATUS)%'
|
||||||
|
domain_theme_map: '%env(json:DOMAIN_THEME_MAP)%'
|
||||||
|
|
||||||
# Body dimensions choices for BodyDimensionsType
|
# Body dimensions choices for BodyDimensionsType
|
||||||
body_dimensions.height_choices:
|
body_dimensions.height_choices:
|
||||||
@@ -146,3 +147,7 @@ services:
|
|||||||
tags:
|
tags:
|
||||||
- { name: monolog.processor }
|
- { name: monolog.processor }
|
||||||
|
|
||||||
|
App\EventListener\DomainThemeListener:
|
||||||
|
arguments:
|
||||||
|
$domainThemeMap: '%domain_theme_map%'
|
||||||
|
|
||||||
|
|||||||
@@ -103,9 +103,6 @@ class IndexController extends AbstractController
|
|||||||
// Store optional return URL in session (defaults to main EP site)
|
// Store optional return URL in session (defaults to main EP site)
|
||||||
$this->bookingService->storeReturnUrl($request, $params->returnUrl);
|
$this->bookingService->storeReturnUrl($request, $params->returnUrl);
|
||||||
|
|
||||||
// Store theme for consistent styling throughout the booking flow
|
|
||||||
$this->bookingService->storeTheme($request, $params->theme);
|
|
||||||
|
|
||||||
// Create fresh booking session with the provided parameters
|
// Create fresh booking session with the provided parameters
|
||||||
$bookingDto = $this->bookingService->startFreshBooking($request, $dateId, $hotelId, $agencyId);
|
$bookingDto = $this->bookingService->startFreshBooking($request, $dateId, $hotelId, $agencyId);
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\EventListener;
|
||||||
|
|
||||||
|
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
|
||||||
|
use Symfony\Component\HttpKernel\Event\RequestEvent;
|
||||||
|
use Symfony\Component\HttpKernel\KernelEvents;
|
||||||
|
|
||||||
|
#[AsEventListener(event: KernelEvents::REQUEST, priority: 200)]
|
||||||
|
class DomainThemeListener
|
||||||
|
{
|
||||||
|
public const THEME_ATTRIBUTE = '_theme';
|
||||||
|
private const DEFAULT_THEME = 'base';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<string, string> $domainThemeMap
|
||||||
|
*/
|
||||||
|
public function __construct(
|
||||||
|
private readonly array $domainThemeMap,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __invoke(RequestEvent $event): void
|
||||||
|
{
|
||||||
|
if (false === $event->isMainRequest()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$request = $event->getRequest();
|
||||||
|
$host = $request->getHost();
|
||||||
|
|
||||||
|
$theme = $this->resolveTheme($host);
|
||||||
|
$request->attributes->set(self::THEME_ATTRIBUTE, $theme);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function resolveTheme(string $host): string
|
||||||
|
{
|
||||||
|
$host = strtolower($host);
|
||||||
|
|
||||||
|
foreach ($this->domainThemeMap as $pattern => $theme) {
|
||||||
|
$pattern = strtolower($pattern);
|
||||||
|
|
||||||
|
if ($host === $pattern || str_ends_with($host, '.' . $pattern)) {
|
||||||
|
return $theme;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return self::DEFAULT_THEME;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -21,7 +21,6 @@ readonly class BookingQueryParams
|
|||||||
public int $hotelId,
|
public int $hotelId,
|
||||||
|
|
||||||
public ?string $agency = null,
|
public ?string $agency = null,
|
||||||
public string $theme = 'base',
|
|
||||||
|
|
||||||
#[SerializedName('r')]
|
#[SerializedName('r')]
|
||||||
public ?string $returnUrl = null,
|
public ?string $returnUrl = null,
|
||||||
|
|||||||
@@ -22,8 +22,6 @@ class BookingService
|
|||||||
public const BOOKING_EDIT_KEY = 'booking_edit';
|
public const BOOKING_EDIT_KEY = 'booking_edit';
|
||||||
public const RETURN_URL_KEY = 'booking_return_url';
|
public const RETURN_URL_KEY = 'booking_return_url';
|
||||||
public const DEFAULT_RETURN_URL = 'https://www.ep-reisen.de';
|
public const DEFAULT_RETURN_URL = 'https://www.ep-reisen.de';
|
||||||
public const THEME_KEY = 'booking_theme';
|
|
||||||
public const DEFAULT_THEME = 'base';
|
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly TravelDataService $travelDataService,
|
private readonly TravelDataService $travelDataService,
|
||||||
@@ -195,33 +193,6 @@ class BookingService
|
|||||||
return $request->getSession()->get(self::RETURN_URL_KEY, self::DEFAULT_RETURN_URL);
|
return $request->getSession()->get(self::RETURN_URL_KEY, self::DEFAULT_RETURN_URL);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Stores the theme identifier in the session.
|
|
||||||
*
|
|
||||||
* Sanitizes the theme value to allow only alphanumeric characters and hyphens.
|
|
||||||
* Falls back to the default theme if empty or invalid.
|
|
||||||
*/
|
|
||||||
public function storeTheme(Request $request, string $theme): void
|
|
||||||
{
|
|
||||||
$sanitized = preg_replace('/[^a-zA-Z0-9-]/', '', $theme);
|
|
||||||
|
|
||||||
if ('' === $sanitized) {
|
|
||||||
$sanitized = self::DEFAULT_THEME;
|
|
||||||
}
|
|
||||||
|
|
||||||
$request->getSession()->set(self::THEME_KEY, $sanitized);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the theme identifier from the session.
|
|
||||||
*
|
|
||||||
* Returns the default theme if not set in session.
|
|
||||||
*/
|
|
||||||
public function getTheme(Request $request): string
|
|
||||||
{
|
|
||||||
return $request->getSession()->get(self::THEME_KEY, self::DEFAULT_THEME);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a fresh booking session with the provided travel parameters.
|
* Creates a fresh booking session with the provided travel parameters.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -5,10 +5,10 @@ declare(strict_types=1);
|
|||||||
namespace App\Twig;
|
namespace App\Twig;
|
||||||
|
|
||||||
use App\BusProNet\DataProvider\CountryDataProvider;
|
use App\BusProNet\DataProvider\CountryDataProvider;
|
||||||
|
use App\EventListener\DomainThemeListener;
|
||||||
use App\Form\Model\BookingDto;
|
use App\Form\Model\BookingDto;
|
||||||
use App\Form\Service\CreateFieldStateProvider;
|
use App\Form\Service\CreateFieldStateProvider;
|
||||||
use App\Form\Service\EditFieldStateProvider;
|
use App\Form\Service\EditFieldStateProvider;
|
||||||
use App\Service\BookingService;
|
|
||||||
use App\Service\ParticipantEligibilityService;
|
use App\Service\ParticipantEligibilityService;
|
||||||
use Symfony\Component\Form\FormView;
|
use Symfony\Component\Form\FormView;
|
||||||
use Symfony\Component\HttpFoundation\RequestStack;
|
use Symfony\Component\HttpFoundation\RequestStack;
|
||||||
@@ -228,18 +228,20 @@ class AppRuntime implements RuntimeExtensionInterface
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the current booking theme from session.
|
* Returns the current booking theme from request attribute.
|
||||||
*
|
*
|
||||||
|
* The theme is resolved by DomainThemeListener based on the request host.
|
||||||
* Defaults to 'base' if no theme is set.
|
* Defaults to 'base' if no theme is set.
|
||||||
*/
|
*/
|
||||||
public function getBookingTheme(): string
|
public function getBookingTheme(): string
|
||||||
{
|
{
|
||||||
$request = $this->requestStack->getCurrentRequest();
|
$request = $this->requestStack->getCurrentRequest();
|
||||||
|
|
||||||
if (null === $request) {
|
if (null === $request) {
|
||||||
return BookingService::DEFAULT_THEME;
|
return 'base';
|
||||||
}
|
}
|
||||||
|
|
||||||
return $request->getSession()->get(BookingService::THEME_KEY, BookingService::DEFAULT_THEME);
|
return $request->attributes->get(DomainThemeListener::THEME_ATTRIBUTE, 'base');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -0,0 +1,119 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Tests\EventListener;
|
||||||
|
|
||||||
|
use App\EventListener\DomainThemeListener;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\HttpKernel\Event\RequestEvent;
|
||||||
|
use Symfony\Component\HttpKernel\HttpKernelInterface;
|
||||||
|
|
||||||
|
class DomainThemeListenerTest extends TestCase
|
||||||
|
{
|
||||||
|
private HttpKernelInterface $kernel;
|
||||||
|
|
||||||
|
protected function setUp(): void
|
||||||
|
{
|
||||||
|
$this->kernel = $this->createMock(HttpKernelInterface::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSetsThemeAttributeForMatchingDomain(): void
|
||||||
|
{
|
||||||
|
$listener = new DomainThemeListener([
|
||||||
|
'sbw-reisen' => 'sbw',
|
||||||
|
'ser-reisen' => 'ser',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$request = Request::create('https://www.sbw-reisen.de/bookings/create');
|
||||||
|
$event = new RequestEvent($this->kernel, $request, HttpKernelInterface::MAIN_REQUEST);
|
||||||
|
|
||||||
|
$listener($event);
|
||||||
|
|
||||||
|
$this->assertSame('sbw', $request->attributes->get(DomainThemeListener::THEME_ATTRIBUTE));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSetsDefaultThemeForUnknownDomain(): void
|
||||||
|
{
|
||||||
|
$listener = new DomainThemeListener([
|
||||||
|
'sbw-reisen' => 'sbw',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$request = Request::create('https://www.ep-reisen.de/bookings/create');
|
||||||
|
$event = new RequestEvent($this->kernel, $request, HttpKernelInterface::MAIN_REQUEST);
|
||||||
|
|
||||||
|
$listener($event);
|
||||||
|
|
||||||
|
$this->assertSame('base', $request->attributes->get(DomainThemeListener::THEME_ATTRIBUTE));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testMatchesPartialDomain(): void
|
||||||
|
{
|
||||||
|
$listener = new DomainThemeListener([
|
||||||
|
'ser-reisen' => 'ser',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$request = Request::create('https://portal.ser-reisen.de/bookings');
|
||||||
|
$event = new RequestEvent($this->kernel, $request, HttpKernelInterface::MAIN_REQUEST);
|
||||||
|
|
||||||
|
$listener($event);
|
||||||
|
|
||||||
|
$this->assertSame('ser', $request->attributes->get(DomainThemeListener::THEME_ATTRIBUTE));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testIgnoresSubRequests(): void
|
||||||
|
{
|
||||||
|
$listener = new DomainThemeListener([
|
||||||
|
'sbw-reisen' => 'sbw',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$request = Request::create('https://www.sbw-reisen.de/bookings/create');
|
||||||
|
$event = new RequestEvent($this->kernel, $request, HttpKernelInterface::SUB_REQUEST);
|
||||||
|
|
||||||
|
$listener($event);
|
||||||
|
|
||||||
|
$this->assertFalse($request->attributes->has(DomainThemeListener::THEME_ATTRIBUTE));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testFirstMatchWins(): void
|
||||||
|
{
|
||||||
|
$listener = new DomainThemeListener([
|
||||||
|
'reisen' => 'generic',
|
||||||
|
'sbw-reisen' => 'sbw',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$request = Request::create('https://www.sbw-reisen.de/bookings/create');
|
||||||
|
$event = new RequestEvent($this->kernel, $request, HttpKernelInterface::MAIN_REQUEST);
|
||||||
|
|
||||||
|
$listener($event);
|
||||||
|
|
||||||
|
$this->assertSame('generic', $request->attributes->get(DomainThemeListener::THEME_ATTRIBUTE));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testEmptyMapReturnsDefaultTheme(): void
|
||||||
|
{
|
||||||
|
$listener = new DomainThemeListener([]);
|
||||||
|
|
||||||
|
$request = Request::create('https://www.sbw-reisen.de/bookings/create');
|
||||||
|
$event = new RequestEvent($this->kernel, $request, HttpKernelInterface::MAIN_REQUEST);
|
||||||
|
|
||||||
|
$listener($event);
|
||||||
|
|
||||||
|
$this->assertSame('base', $request->attributes->get(DomainThemeListener::THEME_ATTRIBUTE));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testMatchesLocalhostForDevelopment(): void
|
||||||
|
{
|
||||||
|
$listener = new DomainThemeListener([
|
||||||
|
'localhost' => 'sbw',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$request = Request::create('http://localhost/bookings/create');
|
||||||
|
$event = new RequestEvent($this->kernel, $request, HttpKernelInterface::MAIN_REQUEST);
|
||||||
|
|
||||||
|
$listener($event);
|
||||||
|
|
||||||
|
$this->assertSame('sbw', $request->attributes->get(DomainThemeListener::THEME_ATTRIBUTE));
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user