feat: tracking integration with GTM and CMP
This commit is contained in:
@@ -92,8 +92,11 @@ class Step4Controller extends AbstractController
|
||||
);
|
||||
}
|
||||
|
||||
// Success: Store booking number in flash and clear session
|
||||
// Success: Store booking data in flash for conversion tracking
|
||||
$summaryData = $this->summaryDataService->getSummaryData($bookingCreateDto);
|
||||
$this->addFlash('booking_number', $bookingResponse->bookingNumber);
|
||||
$this->addFlash('booking_total', $summaryData->payableAmount);
|
||||
$this->addFlash('booking_travel_name', $bookingCreateDto->travel->label);
|
||||
$this->clearTravelDataCache($bookingCreateDto);
|
||||
$this->bookingService->clearBookingDto($request, BookingDto::MODE_CREATE);
|
||||
|
||||
|
||||
@@ -23,7 +23,10 @@ class SuccessController extends AbstractController
|
||||
#[Route('/bookings/create/success', name: 'app_booking_create_success')]
|
||||
public function success(Request $request): Response
|
||||
{
|
||||
$bookingNumber = $request->getSession()->getFlashBag()->get('booking_number')[0] ?? null;
|
||||
$flashBag = $request->getSession()->getFlashBag();
|
||||
$bookingNumber = $flashBag->get('booking_number')[0] ?? null;
|
||||
$bookingTotal = $flashBag->get('booking_total')[0] ?? null;
|
||||
$travelName = $flashBag->get('booking_travel_name')[0] ?? null;
|
||||
$returnUrl = $this->bookingService->getReturnUrl($request);
|
||||
|
||||
// Redirect to return URL if no booking number (direct access or refresh)
|
||||
@@ -33,6 +36,8 @@ class SuccessController extends AbstractController
|
||||
|
||||
return $this->render('booking/create/success.html.twig', [
|
||||
'bookingNumber' => $bookingNumber,
|
||||
'bookingTotal' => $bookingTotal,
|
||||
'travelName' => $travelName,
|
||||
'returnUrl' => $returnUrl,
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\EventListener;
|
||||
|
||||
use App\Service\DomainConfigProvider;
|
||||
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
|
||||
use Symfony\Component\HttpKernel\Event\RequestEvent;
|
||||
use Symfony\Component\HttpKernel\KernelEvents;
|
||||
@@ -12,13 +13,10 @@ use Symfony\Component\HttpKernel\KernelEvents;
|
||||
class DomainThemeListener
|
||||
{
|
||||
public const THEME_ATTRIBUTE = '_theme';
|
||||
private const DEFAULT_THEME = 'base';
|
||||
public const DOMAIN_CONFIG_ATTRIBUTE = '_domain_config';
|
||||
|
||||
/**
|
||||
* @param array<string, string> $domainThemeMap
|
||||
*/
|
||||
public function __construct(
|
||||
private readonly array $domainThemeMap,
|
||||
private readonly DomainConfigProvider $domainConfigProvider,
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -29,24 +27,9 @@ class DomainThemeListener
|
||||
}
|
||||
|
||||
$request = $event->getRequest();
|
||||
$host = $request->getHost();
|
||||
$config = $this->domainConfigProvider->getConfigForHost($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;
|
||||
$request->attributes->set(self::THEME_ATTRIBUTE, $config->theme);
|
||||
$request->attributes->set(self::DOMAIN_CONFIG_ATTRIBUTE, $config);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Model;
|
||||
|
||||
/**
|
||||
* Value object representing domain-specific configuration.
|
||||
*
|
||||
* Holds theme key, GTM container ID, and CMP (Consent Management Platform) URL
|
||||
* for a specific domain. Used by DomainConfigProvider to resolve configuration
|
||||
* based on the request host.
|
||||
*/
|
||||
final readonly class DomainConfig
|
||||
{
|
||||
public const DEFAULT_THEME = 'base';
|
||||
|
||||
public function __construct(
|
||||
public string $theme = self::DEFAULT_THEME,
|
||||
public string $gtmId = '',
|
||||
public string $cmpUrl = '',
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a DomainConfig instance from a configuration array.
|
||||
*
|
||||
* @param array{theme?: string, gtm_id?: string, cmp_url?: string} $config The configuration array
|
||||
*/
|
||||
public static function fromArray(array $config): self
|
||||
{
|
||||
return new self(
|
||||
theme: $config['theme'] ?? self::DEFAULT_THEME,
|
||||
gtmId: $config['gtm_id'] ?? '',
|
||||
cmpUrl: $config['cmp_url'] ?? '',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a default DomainConfig instance with base theme and empty tracking values.
|
||||
*/
|
||||
public static function default(): self
|
||||
{
|
||||
return new self();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Service;
|
||||
|
||||
use App\Model\DomainConfig;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
|
||||
/**
|
||||
* Resolves domain-specific configuration based on the current request host.
|
||||
*
|
||||
* Matches the request host against configured domain patterns, supporting both
|
||||
* exact matches and subdomain matching (e.g., "example.com" matches "www.example.com").
|
||||
* Returns a default configuration when no matching domain is found.
|
||||
*/
|
||||
class DomainConfigProvider
|
||||
{
|
||||
/**
|
||||
* @var array<string, DomainConfig>
|
||||
*/
|
||||
private array $configCache = [];
|
||||
|
||||
/**
|
||||
* @param array<string, array{theme?: string, gtm_id?: string, cmp_url?: string}> $domainConfig
|
||||
*/
|
||||
public function __construct(
|
||||
private readonly array $domainConfig,
|
||||
private readonly RequestStack $requestStack,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the configuration for the current request host.
|
||||
*/
|
||||
public function getConfig(): DomainConfig
|
||||
{
|
||||
$request = $this->requestStack->getCurrentRequest();
|
||||
|
||||
if (null === $request) {
|
||||
return DomainConfig::default();
|
||||
}
|
||||
|
||||
return $this->getConfigForHost($request->getHost());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the configuration for a specific host.
|
||||
*
|
||||
* @param string $host The host to resolve configuration for
|
||||
*/
|
||||
public function getConfigForHost(string $host): DomainConfig
|
||||
{
|
||||
$host = strtolower($host);
|
||||
|
||||
if (isset($this->configCache[$host])) {
|
||||
return $this->configCache[$host];
|
||||
}
|
||||
|
||||
foreach ($this->domainConfig as $pattern => $config) {
|
||||
$pattern = strtolower($pattern);
|
||||
|
||||
if ($host === $pattern || str_ends_with($host, '.'.$pattern)) {
|
||||
$domainConfig = DomainConfig::fromArray($config);
|
||||
$this->configCache[$host] = $domainConfig;
|
||||
|
||||
return $domainConfig;
|
||||
}
|
||||
}
|
||||
|
||||
$this->configCache[$host] = DomainConfig::default();
|
||||
|
||||
return $this->configCache[$host];
|
||||
}
|
||||
}
|
||||
@@ -40,6 +40,8 @@ class AppExtension extends AbstractExtension
|
||||
new TwigFunction('is_hidden', [AppRuntime::class, 'isHidden']),
|
||||
new TwigFunction('collect_invalid_field_labels', [AppRuntime::class, 'collectInvalidFieldLabels']),
|
||||
new TwigFunction('booking_theme', [AppRuntime::class, 'getBookingTheme']),
|
||||
new TwigFunction('gtm_id', [AppRuntime::class, 'getGtmId']),
|
||||
new TwigFunction('cmp_url', [AppRuntime::class, 'getCmpUrl']),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
+31
-2
@@ -9,6 +9,7 @@ use App\EventListener\DomainThemeListener;
|
||||
use App\Form\Model\BookingDto;
|
||||
use App\Form\Service\CreateFieldStateProvider;
|
||||
use App\Form\Service\EditFieldStateProvider;
|
||||
use App\Model\DomainConfig;
|
||||
use App\Service\ParticipantEligibilityService;
|
||||
use Symfony\Component\Form\FormView;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
@@ -234,14 +235,42 @@ class AppRuntime implements RuntimeExtensionInterface
|
||||
* Defaults to 'base' if no theme is set.
|
||||
*/
|
||||
public function getBookingTheme(): string
|
||||
{
|
||||
return $this->getDomainConfig()->theme;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the GTM container ID for the current domain.
|
||||
*
|
||||
* Returns an empty string if no GTM ID is configured for this domain.
|
||||
*/
|
||||
public function getGtmId(): string
|
||||
{
|
||||
return $this->getDomainConfig()->gtmId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the CMP (Consent Management Platform) URL for the current domain.
|
||||
*
|
||||
* Returns an empty string if no CMP URL is configured for this domain.
|
||||
*/
|
||||
public function getCmpUrl(): string
|
||||
{
|
||||
return $this->getDomainConfig()->cmpUrl;
|
||||
}
|
||||
|
||||
private function getDomainConfig(): DomainConfig
|
||||
{
|
||||
$request = $this->requestStack->getCurrentRequest();
|
||||
|
||||
if (null === $request) {
|
||||
return 'base';
|
||||
return DomainConfig::default();
|
||||
}
|
||||
|
||||
return $request->attributes->get(DomainThemeListener::THEME_ATTRIBUTE, 'base');
|
||||
return $request->attributes->get(
|
||||
DomainThemeListener::DOMAIN_CONFIG_ATTRIBUTE,
|
||||
DomainConfig::default()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user