custom/plugins/EtrpCurrencyGeoIp/src/Subscriber/DetectCountryUser.php line 63

Open in your IDE?
  1. <?php declare(strict_types=1);
  2. namespace EtrpCurrencyGeoIp\Subscriber;
  3. use EtrpCurrencyGeoIp\Service\GeoipServices;
  4. use Shopware\Core\PlatformRequest;
  5. use Shopware\Core\System\SystemConfig\SystemConfigService;
  6. use Shopware\Core\System\SalesChannel\SalesChannelContext;
  7. use Shopware\Storefront\Framework\Routing\RequestTransformer;
  8. use Shopware\Storefront\Framework\Routing\Router;
  9. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  10. use Symfony\Component\HttpFoundation\Cookie;
  11. use Symfony\Component\HttpFoundation\RedirectResponse;
  12. use Symfony\Component\HttpKernel\Event\ResponseEvent;
  13. use Symfony\Component\HttpKernel\KernelEvents;
  14. class DetectCountryUser implements EventSubscriberInterface
  15. {
  16.     /**
  17.      * @var GeoipServices
  18.      */
  19.     private $GeoipServices;
  20.     /**
  21.      * @var Router
  22.      */
  23.     private $router;
  24.     /**
  25.      * @var SystemConfigService
  26.      */
  27.     private $systemConfigService;
  28.     /**
  29.      * DetectCountryUser constructor.
  30.      * @param GeoipServices $GeoipServices
  31.      * @param Router $router
  32.      * @param SystemConfigService $systemConfigService
  33.      */
  34.     public function __construct(
  35.         GeoipServices $GeoipServices,
  36.         Router $router,
  37.         SystemConfigService $systemConfigService
  38.     ) {
  39.         $this->GeoipServices = $GeoipServices;
  40.         $this->router = $router;
  41.         $this->systemConfigService = $systemConfigService;
  42.     }
  43.     /**
  44.      * @return array[]
  45.      */
  46.     public static function getSubscribedEvents(): array
  47.     {
  48.         return [
  49.             KernelEvents::RESPONSE => ['isoCurrentUser', 9999],
  50.         ];
  51.     }
  52.     /**
  53.      * @param ResponseEvent $event
  54.      */
  55.     public function isoCurrentUser(ResponseEvent $event): void
  56.     {
  57.         // Escludi i sub-request interni di Symfony/Shopware (redirect lingua, dominio, ecc.)
  58.         // Senza questo check il guard intercetta redirect interni privi di SalesChannelContext
  59.         // → customer=null → loop infinito di redirect
  60.         if (!$event->isMainRequest()) {
  61.             return;
  62.         }
  63.         // Verifica che siamo in un contesto storefront
  64.         $salesChannelContext = $event->getRequest()->attributes->get(PlatformRequest::ATTRIBUTE_SALES_CHANNEL_CONTEXT_OBJECT);
  65.         if (!$salesChannelContext instanceof SalesChannelContext) {
  66.             return;
  67.         }
  68.         $salesChannel = $salesChannelContext->getSalesChannel();
  69.         if ($salesChannel === null) {
  70.             return;
  71.         }
  72.         $salesChannelId = $event->getRequest()->attributes->get('sw-sales-channel-id');
  73.         $salesChannelName = $salesChannel->getTranslated()['name'];
  74.         $currentDomain = $event->getRequest()->attributes->get(RequestTransformer::SALES_CHANNEL_BASE_URL);
  75.         $currentDomain = $currentDomain === '' ? '/it' : $currentDomain;
  76.         $currentUri    = $event->getRequest()->getRequestUri();
  77.         $currentRouteName = $event->getRequest()->attributes->get('_route');
  78.         $maindomain    = $event->getRequest()->attributes->get(RequestTransformer::SALES_CHANNEL_ABSOLUTE_BASE_URL);
  79.         // ---------------------------------------------------------------
  80.         // FLUSSO B2B
  81.         // Attivato quando il nome del sales channel contiene "B2B"
  82.         // ---------------------------------------------------------------
  83.         if (str_contains($salesChannelName, 'B2B')) {
  84.             $customer = $salesChannelContext->getCustomer();
  85.             if ($customer === null) {
  86.                 // ── Step 1: geo-detect per indirizzare al corretto store B2B ──
  87.                 // Usa un cookie dedicato (b2b_geodomain) per evitare lookup ad ogni richiesta.
  88.                 $b2bGeoPath = $event->getRequest()->cookies->get('b2b_geodomain');
  89.                 if ($b2bGeoPath === null) {
  90.                     $ip = $event->getRequest()->getClientIp();
  91.                     if ('127.0.0.1' === $ip) {
  92.                         $ip = '94.89.28.10'; // override sviluppo locale
  93.                     }
  94.                     $countryIso = $this->GeoipServices->ip2country($ip);
  95.                     $b2bGeoPath = $this->b2bDomainForCountry($countryIso ?? '');
  96.                 }
  97.                 // Se siamo sul percorso sbagliato → redirect al percorso geo-corretto
  98.                 // (il login guard entrerà poi in gioco sulla nuova URL)
  99.                 if ($currentDomain !== $b2bGeoPath) {
  100.                     $newUrl = $maindomain . $b2bGeoPath . $currentUri;
  101.                     $redirectResponse = new RedirectResponse($newUrl);
  102.                     $redirectResponse->headers->setCookie(Cookie::create('b2b_geodomain', $b2bGeoPath));
  103.                     $event->setResponse($redirectResponse);
  104.                     return;
  105.                 }
  106.                 // Sul percorso corretto: rinnova il cookie
  107.                 $event->getResponse()->headers->setCookie(Cookie::create('b2b_geodomain', $b2bGeoPath));
  108.                 // ── Step 2: login guard ──
  109.                 // Rotte pubbliche accessibili senza login nel B2B
  110.                 $allowedRoutes = [
  111.                     'frontend.home.page',
  112.                     'frontend.login',
  113.                     'frontend.account.login',              // POST login handler — CRITICO
  114.                     'frontend.account.login.page',         // GET pagina login
  115.                     'frontend.account.register.page',
  116.                     'frontend.account.register.save',      // POST registrazione
  117.                     'frontend.account.recover.page',
  118.                     'frontend.account.recover.password.page',
  119.                     'frontend.account.recover.request',
  120.                     'frontend.account.recover.password.reset',
  121.                 ];
  122.                 if (in_array($currentRouteName, $allowedRoutes, true)) {
  123.                     return;
  124.                 }
  125.                 // Richieste AJAX passano senza redirect
  126.                 if ($event->getRequest()->attributes->getBoolean('XmlHttpRequest', false)) {
  127.                     return;
  128.                 }
  129.                 // Utente non loggato su pagina protetta → redirect al login
  130.                 $event->setResponse(new RedirectResponse($this->router->generate('frontend.account.login.page')));
  131.                 return;
  132.             }
  133.             // Utente loggato: determina il sales channel dal customer group
  134.             $customerGroupId  = $customer->getGroupId();
  135.             $domainForCustomer = $this->domainForCustomer($customerGroupId);
  136.             if ($domainForCustomer !== false) {
  137.                 // Il gruppo ha un dominio associato → redirect se necessario
  138.                 if ($currentDomain !== $domainForCustomer) {
  139.                     $newUrl = $maindomain . $domainForCustomer . $currentUri;
  140.                     $event->setResponse(new RedirectResponse($newUrl));
  141.                 }
  142.                 return;
  143.             }
  144.             // Gruppo senza dominio specifico (es. amministratori) → account home
  145.             if ($currentRouteName !== 'frontend.account.home.page') {
  146.                 if ($event->getRequest()->attributes->getBoolean('XmlHttpRequest', false)) {
  147.                     return;
  148.                 }
  149.                 $event->setResponse(new RedirectResponse($this->router->generate('frontend.account.home.page')));
  150.             }
  151.             return;
  152.         }
  153.         // ---------------------------------------------------------------
  154.         // FLUSSO B2C
  155.         // Geo-redirect basato sull'IP dell'utente.
  156.         // Controllato dal flag "active" nel pannello admin (il flusso B2B
  157.         // gira sempre indipendentemente da questo toggle).
  158.         // ---------------------------------------------------------------
  159.         $isActive = $this->systemConfigService->getBool('EtrpCurrencyGeoIp.config.active', $salesChannelId);
  160.         if (!$isActive) {
  161.             return;
  162.         }
  163.         $ip = $event->getRequest()->getClientIp();
  164.         // Override per sviluppo locale
  165.         if ('127.0.0.1' === $ip) {
  166.             $ip = '94.89.28.10';
  167.         }
  168.         // Usa il cookie per evitare la geo-lookup ad ogni richiesta
  169.         $cookieset = $event->getRequest()->cookies->get('domainref');
  170.         if ($cookieset === null) {
  171.             $countryIso      = $this->GeoipServices->ip2country($ip);
  172.             $domainForCountry = $this->domainForCountry($countryIso ?? '');
  173.         } else {
  174.             $domainForCountry = $cookieset;
  175.         }
  176.         if ($currentDomain !== $domainForCountry) {
  177.             $newUrl = $maindomain . $domainForCountry . $currentUri;
  178.             // FIX: il cookie viene impostato sulla RedirectResponse stessa,
  179.             // altrimenti andrebbe perso quando la risposta originale viene sostituita
  180.             $redirectResponse = new RedirectResponse($newUrl);
  181.             $redirectResponse->headers->setCookie(Cookie::create('domainref', $domainForCountry));
  182.             $event->setResponse($redirectResponse);
  183.             return;
  184.         }
  185.         // Già sul dominio corretto: imposta/rinnova il cookie
  186.         $event->getResponse()->headers->setCookie(Cookie::create('domainref', $domainForCountry));
  187.     }
  188.     /**
  189.      * Restituisce il path del sales channel B2B in base al codice ISO del paese
  190.      * per utenti non ancora autenticati (geo-detect iniziale).
  191.      *
  192.      * - Italia    → /it   (b2b.alexmax.net/it  oppure root se prefix vuoto)
  193.      * - USA / CA  → /usa  (b2b.alexmax.net/usa)
  194.      * - Tutto il resto → /en (b2b.alexmax.net/en)
  195.      *
  196.      * @param string $country Codice ISO del paese (es. "IT", "US")
  197.      * @return string
  198.      */
  199.     public function b2bDomainForCountry(string $country): string
  200.     {
  201.         switch ($country) {
  202.             case 'IT':
  203.                 return '/it';
  204.             case 'US':
  205.             case 'CA':
  206.                 return '/usa';
  207.             default:
  208.                 return '/en';
  209.         }
  210.     }
  211.     /**
  212.      * Restituisce il path del sales channel in base al codice ISO del paese.
  213.      *
  214.      * @param string $country Codice ISO del paese (es. "IT", "GB", "US")
  215.      * @return string
  216.      */
  217.     public function domainForCountry(string $country): string
  218.     {
  219.         switch ($country) {
  220.             case 'GB':
  221.             case 'UK':
  222.                 return '/uk';
  223.             case 'US':
  224.             case 'CA':
  225.                 return '/usa';
  226.             case 'IT':
  227.                 return '/it';
  228.             default:
  229.                 return '/en';
  230.         }
  231.     }
  232.     /**
  233.      * Restituisce il path del sales channel in base al customer group ID B2B.
  234.      * Ritorna false se il gruppo non ha un dominio dedicato (es. gruppi admin/interni).
  235.      *
  236.      * @param string $categoryUser UUID del customer group
  237.      * @return string|false
  238.      */
  239.     public function domainForCustomer(string $categoryUser)
  240.     {
  241.         switch ($categoryUser) {
  242.             // Gruppi USA / DAP
  243.             case 'ce2c04e5fa89404ebc2b9f133a81e3e1': // B2B-DAP
  244.             case '0173864dfe1744ceb3fbc7dce2b843c6': // B2B-USA
  245.                 return '/usa';
  246.             // Gruppi Italia
  247.             case '8d37666518ff479c8453855c061e3571': // B2B-Italia
  248.             case 'e88dfa92e9e54de194cea0d5332f27ca': // B2B-ita-all
  249.             case '01a3a5fa086343d6bed217f542874213': // B2B-iva-sempre
  250.             case '6386fa194c64468197b076897e22f048': // B2B-Italia-alessia
  251.                 return '/it';
  252.             
  253.             // Gruppi Europa / World → canale inglese
  254.                 case '93275f3e244c424ca9594b8a5cdf93df':
  255.             case 'fd292a1f125e45ada94979f8dd10e720': // B2B-Europa
  256.             case '335c4ac7b66246b9b2fd85c0588c1d4b': // B2B-Europa-all ← aggiunto
  257.             case '4823802da3eb4d14bcf90baa57b63072': // B2B-World
  258.                 return '/en';
  259.             // Gruppi senza dominio dedicato (redirect ad account home)
  260.             case 'abd998f9c25a450fa64662348294d4a3': // B2B request
  261.             case '57fd39492c974bc3aaa4e3c6d635c903': // General
  262.             case '0ac146b10d064ceea5fe63032520d122': // Retailer
  263.                 return false;
  264.             default:
  265.                 return '/en';
  266.         }
  267.     }
  268. }