<?php declare(strict_types=1);
namespace EtrpCurrencyGeoIp\Subscriber;
use EtrpCurrencyGeoIp\Service\GeoipServices;
use Shopware\Core\PlatformRequest;
use Shopware\Core\System\SystemConfig\SystemConfigService;
use Shopware\Core\System\SalesChannel\SalesChannelContext;
use Shopware\Storefront\Framework\Routing\RequestTransformer;
use Shopware\Storefront\Framework\Routing\Router;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Cookie;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpKernel\Event\ResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
class DetectCountryUser implements EventSubscriberInterface
{
/**
* @var GeoipServices
*/
private $GeoipServices;
/**
* @var Router
*/
private $router;
/**
* @var SystemConfigService
*/
private $systemConfigService;
/**
* DetectCountryUser constructor.
* @param GeoipServices $GeoipServices
* @param Router $router
* @param SystemConfigService $systemConfigService
*/
public function __construct(
GeoipServices $GeoipServices,
Router $router,
SystemConfigService $systemConfigService
) {
$this->GeoipServices = $GeoipServices;
$this->router = $router;
$this->systemConfigService = $systemConfigService;
}
/**
* @return array[]
*/
public static function getSubscribedEvents(): array
{
return [
KernelEvents::RESPONSE => ['isoCurrentUser', 9999],
];
}
/**
* @param ResponseEvent $event
*/
public function isoCurrentUser(ResponseEvent $event): void
{
// Escludi i sub-request interni di Symfony/Shopware (redirect lingua, dominio, ecc.)
// Senza questo check il guard intercetta redirect interni privi di SalesChannelContext
// → customer=null → loop infinito di redirect
if (!$event->isMainRequest()) {
return;
}
// Verifica che siamo in un contesto storefront
$salesChannelContext = $event->getRequest()->attributes->get(PlatformRequest::ATTRIBUTE_SALES_CHANNEL_CONTEXT_OBJECT);
if (!$salesChannelContext instanceof SalesChannelContext) {
return;
}
$salesChannel = $salesChannelContext->getSalesChannel();
if ($salesChannel === null) {
return;
}
$salesChannelId = $event->getRequest()->attributes->get('sw-sales-channel-id');
$salesChannelName = $salesChannel->getTranslated()['name'];
$currentDomain = $event->getRequest()->attributes->get(RequestTransformer::SALES_CHANNEL_BASE_URL);
$currentDomain = $currentDomain === '' ? '/it' : $currentDomain;
$currentUri = $event->getRequest()->getRequestUri();
$currentRouteName = $event->getRequest()->attributes->get('_route');
$maindomain = $event->getRequest()->attributes->get(RequestTransformer::SALES_CHANNEL_ABSOLUTE_BASE_URL);
// ---------------------------------------------------------------
// FLUSSO B2B
// Attivato quando il nome del sales channel contiene "B2B"
// ---------------------------------------------------------------
if (str_contains($salesChannelName, 'B2B')) {
$customer = $salesChannelContext->getCustomer();
if ($customer === null) {
// ── Step 1: geo-detect per indirizzare al corretto store B2B ──
// Usa un cookie dedicato (b2b_geodomain) per evitare lookup ad ogni richiesta.
$b2bGeoPath = $event->getRequest()->cookies->get('b2b_geodomain');
if ($b2bGeoPath === null) {
$ip = $event->getRequest()->getClientIp();
if ('127.0.0.1' === $ip) {
$ip = '94.89.28.10'; // override sviluppo locale
}
$countryIso = $this->GeoipServices->ip2country($ip);
$b2bGeoPath = $this->b2bDomainForCountry($countryIso ?? '');
}
// Se siamo sul percorso sbagliato → redirect al percorso geo-corretto
// (il login guard entrerà poi in gioco sulla nuova URL)
if ($currentDomain !== $b2bGeoPath) {
$newUrl = $maindomain . $b2bGeoPath . $currentUri;
$redirectResponse = new RedirectResponse($newUrl);
$redirectResponse->headers->setCookie(Cookie::create('b2b_geodomain', $b2bGeoPath));
$event->setResponse($redirectResponse);
return;
}
// Sul percorso corretto: rinnova il cookie
$event->getResponse()->headers->setCookie(Cookie::create('b2b_geodomain', $b2bGeoPath));
// ── Step 2: login guard ──
// Rotte pubbliche accessibili senza login nel B2B
$allowedRoutes = [
'frontend.home.page',
'frontend.login',
'frontend.account.login', // POST login handler — CRITICO
'frontend.account.login.page', // GET pagina login
'frontend.account.register.page',
'frontend.account.register.save', // POST registrazione
'frontend.account.recover.page',
'frontend.account.recover.password.page',
'frontend.account.recover.request',
'frontend.account.recover.password.reset',
];
if (in_array($currentRouteName, $allowedRoutes, true)) {
return;
}
// Richieste AJAX passano senza redirect
if ($event->getRequest()->attributes->getBoolean('XmlHttpRequest', false)) {
return;
}
// Utente non loggato su pagina protetta → redirect al login
$event->setResponse(new RedirectResponse($this->router->generate('frontend.account.login.page')));
return;
}
// Utente loggato: determina il sales channel dal customer group
$customerGroupId = $customer->getGroupId();
$domainForCustomer = $this->domainForCustomer($customerGroupId);
if ($domainForCustomer !== false) {
// Il gruppo ha un dominio associato → redirect se necessario
if ($currentDomain !== $domainForCustomer) {
$newUrl = $maindomain . $domainForCustomer . $currentUri;
$event->setResponse(new RedirectResponse($newUrl));
}
return;
}
// Gruppo senza dominio specifico (es. amministratori) → account home
if ($currentRouteName !== 'frontend.account.home.page') {
if ($event->getRequest()->attributes->getBoolean('XmlHttpRequest', false)) {
return;
}
$event->setResponse(new RedirectResponse($this->router->generate('frontend.account.home.page')));
}
return;
}
// ---------------------------------------------------------------
// FLUSSO B2C
// Geo-redirect basato sull'IP dell'utente.
// Controllato dal flag "active" nel pannello admin (il flusso B2B
// gira sempre indipendentemente da questo toggle).
// ---------------------------------------------------------------
$isActive = $this->systemConfigService->getBool('EtrpCurrencyGeoIp.config.active', $salesChannelId);
if (!$isActive) {
return;
}
$ip = $event->getRequest()->getClientIp();
// Override per sviluppo locale
if ('127.0.0.1' === $ip) {
$ip = '94.89.28.10';
}
// Usa il cookie per evitare la geo-lookup ad ogni richiesta
$cookieset = $event->getRequest()->cookies->get('domainref');
if ($cookieset === null) {
$countryIso = $this->GeoipServices->ip2country($ip);
$domainForCountry = $this->domainForCountry($countryIso ?? '');
} else {
$domainForCountry = $cookieset;
}
if ($currentDomain !== $domainForCountry) {
$newUrl = $maindomain . $domainForCountry . $currentUri;
// FIX: il cookie viene impostato sulla RedirectResponse stessa,
// altrimenti andrebbe perso quando la risposta originale viene sostituita
$redirectResponse = new RedirectResponse($newUrl);
$redirectResponse->headers->setCookie(Cookie::create('domainref', $domainForCountry));
$event->setResponse($redirectResponse);
return;
}
// Già sul dominio corretto: imposta/rinnova il cookie
$event->getResponse()->headers->setCookie(Cookie::create('domainref', $domainForCountry));
}
/**
* Restituisce il path del sales channel B2B in base al codice ISO del paese
* per utenti non ancora autenticati (geo-detect iniziale).
*
* - Italia → /it (b2b.alexmax.net/it oppure root se prefix vuoto)
* - USA / CA → /usa (b2b.alexmax.net/usa)
* - Tutto il resto → /en (b2b.alexmax.net/en)
*
* @param string $country Codice ISO del paese (es. "IT", "US")
* @return string
*/
public function b2bDomainForCountry(string $country): string
{
switch ($country) {
case 'IT':
return '/it';
case 'US':
case 'CA':
return '/usa';
default:
return '/en';
}
}
/**
* Restituisce il path del sales channel in base al codice ISO del paese.
*
* @param string $country Codice ISO del paese (es. "IT", "GB", "US")
* @return string
*/
public function domainForCountry(string $country): string
{
switch ($country) {
case 'GB':
case 'UK':
return '/uk';
case 'US':
case 'CA':
return '/usa';
case 'IT':
return '/it';
default:
return '/en';
}
}
/**
* Restituisce il path del sales channel in base al customer group ID B2B.
* Ritorna false se il gruppo non ha un dominio dedicato (es. gruppi admin/interni).
*
* @param string $categoryUser UUID del customer group
* @return string|false
*/
public function domainForCustomer(string $categoryUser)
{
switch ($categoryUser) {
// Gruppi USA / DAP
case 'ce2c04e5fa89404ebc2b9f133a81e3e1': // B2B-DAP
case '0173864dfe1744ceb3fbc7dce2b843c6': // B2B-USA
return '/usa';
// Gruppi Italia
case '8d37666518ff479c8453855c061e3571': // B2B-Italia
case 'e88dfa92e9e54de194cea0d5332f27ca': // B2B-ita-all
case '01a3a5fa086343d6bed217f542874213': // B2B-iva-sempre
case '6386fa194c64468197b076897e22f048': // B2B-Italia-alessia
return '/it';
// Gruppi Europa / World → canale inglese
case '93275f3e244c424ca9594b8a5cdf93df':
case 'fd292a1f125e45ada94979f8dd10e720': // B2B-Europa
case '335c4ac7b66246b9b2fd85c0588c1d4b': // B2B-Europa-all ← aggiunto
case '4823802da3eb4d14bcf90baa57b63072': // B2B-World
return '/en';
// Gruppi senza dominio dedicato (redirect ad account home)
case 'abd998f9c25a450fa64662348294d4a3': // B2B request
case '57fd39492c974bc3aaa4e3c6d635c903': // General
case '0ac146b10d064ceea5fe63032520d122': // Retailer
return false;
default:
return '/en';
}
}
}