custom/plugins/AsbsDynamicAccessRedirect/src/Subscriber/DynamicAccessSubscriber.php line 50

Open in your IDE?
  1. <?php declare(strict_types=1);
  2. namespace Asbs\DynamicAccessRedirect\Subscriber;
  3. use Shopware\Core\Content\Category\Exception\CategoryNotFoundException;
  4. use Shopware\Core\Content\Product\Exception\ProductNotFoundException;
  5. use Shopware\Core\Framework\DataAbstractionLayer\EntityRepositoryInterface;
  6. use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
  7. use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
  8. use Shopware\Core\System\SalesChannel\SalesChannelContext;
  9. use Shopware\Core\System\SystemConfig\SystemConfigService;
  10. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  11. use Symfony\Component\HttpFoundation\RedirectResponse;
  12. use Symfony\Component\HttpKernel\Event\ExceptionEvent;
  13. use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
  14. use Symfony\Component\HttpKernel\KernelEvents;
  15. use Symfony\Component\Routing\RouterInterface;
  16. class DynamicAccessSubscriber implements EventSubscriberInterface
  17. {
  18.     private EntityRepositoryInterface $categoryRepository;
  19.     private EntityRepositoryInterface $productRepository;
  20.     private RouterInterface $router;
  21.     private SystemConfigService $systemConfigService;
  22.     /**
  23.      * Default redirect URL for fallback
  24.      */
  25.     private const DEFAULT_REDIRECT_URL 'https://doncarne.de/c/beef-club/';
  26.     public function __construct(
  27.         EntityRepositoryInterface $categoryRepository,
  28.         EntityRepositoryInterface $productRepository,
  29.         RouterInterface $router,
  30.         SystemConfigService $systemConfigService
  31.     ) {
  32.         $this->categoryRepository $categoryRepository;
  33.         $this->productRepository $productRepository;
  34.         $this->router $router;
  35.         $this->systemConfigService $systemConfigService;
  36.     }
  37.     public static function getSubscribedEvents(): array
  38.     {
  39.         return [
  40.             KernelEvents::EXCEPTION => ['onKernelException'0]
  41.         ];
  42.     }
  43.     public function onKernelException(ExceptionEvent $event): void
  44.     {
  45.         // Überprüfen, ob das Plugin aktiviert ist
  46.         if (!$this->isPluginEnabled()) {
  47.             return;
  48.         }
  49.         $exception $event->getThrowable();
  50.         $request $event->getRequest();
  51.         
  52.         // Überprüfen, ob ein sales channel context und ein eingeloggter Benutzer vorhanden ist
  53.         if (!$request->attributes->has('sw-sales-channel-context')) {
  54.             return;
  55.         }
  56.         
  57.         /** @var SalesChannelContext $context */
  58.         $context $request->attributes->get('sw-sales-channel-context');
  59.         
  60.         // Nur 404 Exceptions, CategoryNotFoundException und ProductNotFoundException behandeln
  61.         if (!($exception instanceof NotFoundHttpException) && 
  62.             !($exception instanceof CategoryNotFoundException) &&
  63.             !($exception instanceof ProductNotFoundException)) {
  64.             return;
  65.         }
  66.         
  67.         // URL-Pfadmuster überprüfen, die durch Dynamic Access eingeschränkt sein könnten
  68.         $pathInfo $request->getPathInfo();
  69.         
  70.         // Kategorie-Exceptions behandeln - prüfen, ob die Kategorie tatsächlich existiert
  71.         if (($exception instanceof CategoryNotFoundException) || $this->isCategoryPath($pathInfo)) {
  72.             $categoryId $this->extractCategoryIdFromPath($pathInfo);
  73.             if ($categoryId && $this->categoryExists($categoryId$context)) {
  74.                 // Kategorie existiert, aber Benutzer hat keinen Zugriff, zum Beef Club weiterleiten
  75.                 $response = new RedirectResponse($this->getRedirectUrl());
  76.                 $event->setResponse($response);
  77.                 return;
  78.             }
  79.         }
  80.         
  81.         // Produkt-Exceptions behandeln - prüfen, ob das Produkt tatsächlich existiert
  82.         if (($exception instanceof ProductNotFoundException) || $this->isProductPath($pathInfo)) {
  83.             $productId $this->extractProductIdFromPath($pathInfo);
  84.             if ($productId && $this->productExists($productId$context)) {
  85.                 // Produkt existiert, aber Benutzer hat keinen Zugriff, zum Beef Club weiterleiten
  86.                 $response = new RedirectResponse($this->getRedirectUrl());
  87.                 $event->setResponse($response);
  88.                 return;
  89.             }
  90.         }
  91.     }
  92.     
  93.     /**
  94.      * Bestimmt, ob der Pfad wie eine Kategorieseite aussieht
  95.      */
  96.     private function isCategoryPath(string $pathInfo): bool
  97.     {
  98.         // Kategoriepfade haben typischerweise keine Erweiterungen und enthalten nicht /detail/ oder /checkout/
  99.         return !preg_match('/(\.[\w\d]+$|\/detail\/|\/checkout\/)/'$pathInfo);
  100.     }
  101.     
  102.     /**
  103.      * Bestimmt, ob der Pfad wie eine Produktdetailseite aussieht
  104.      */
  105.     private function isProductPath(string $pathInfo): bool
  106.     {
  107.         // Produktpfade enthalten typischerweise /detail/
  108.         return strpos($pathInfo'/detail/') !== false;
  109.     }
  110.     
  111.     /**
  112.      * Extrahiert eine mögliche Kategorie-ID aus einem URL-Pfad
  113.      */
  114.     private function extractCategoryIdFromPath(string $pathInfo): ?string
  115.     {
  116.         // Den letzten Teil der URL extrahieren, der die Kategorie-ID sein sollte
  117.         if (preg_match('/\/([a-f0-9]{32})(?:\/|$)/'$pathInfo$matches)) {
  118.             return $matches[1];
  119.         }
  120.         
  121.         return null;
  122.     }
  123.     
  124.     /**
  125.      * Extrahiert eine mögliche Produkt-ID aus einem URL-Pfad
  126.      */
  127.     private function extractProductIdFromPath(string $pathInfo): ?string
  128.     {
  129.         // Die ID aus einem Pfad wie '/detail/a1b2c3.../' extrahieren
  130.         if (preg_match('/\/detail\/([a-f0-9]{32})(?:\/|$)/'$pathInfo$matches)) {
  131.             return $matches[1];
  132.         }
  133.         
  134.         return null;
  135.     }
  136.     
  137.     /**
  138.      * Überprüft, ob eine Kategorie in der Datenbank existiert, ohne Kundengruppen-Filter anzuwenden
  139.      */
  140.     private function categoryExists(string $categoryIdSalesChannelContext $context): bool
  141.     {
  142.         $criteria = new Criteria([$categoryId]);
  143.         $criteria->addFilter(new EqualsFilter('active'true));
  144.         $criteria->addAssociation('type');
  145.         
  146.         // Direkter Zugriff auf das Repository, um Sales-Channel-Filter zu umgehen
  147.         $result $this->categoryRepository->search($criteria$context->getContext());
  148.         
  149.         return $result->getTotal() > 0;
  150.     }
  151.     
  152.     /**
  153.      * Überprüft, ob ein Produkt in der Datenbank existiert, ohne Kundengruppen-Filter anzuwenden
  154.      */
  155.     private function productExists(string $productIdSalesChannelContext $context): bool
  156.     {
  157.         $criteria = new Criteria([$productId]);
  158.         $criteria->addFilter(new EqualsFilter('active'true));
  159.         
  160.         // Direkter Zugriff auf das Repository, um Sales-Channel-Filter zu umgehen
  161.         $result $this->productRepository->search($criteria$context->getContext());
  162.         
  163.         return $result->getTotal() > 0;
  164.     }
  165.     /**
  166.      * Gibt die konfigurierte Weiterleitungs-URL zurück
  167.      */
  168.     private function getRedirectUrl(): string
  169.     {
  170.         // URL aus den Einstellungen holen und Whitespace trimmen
  171.         $redirectUrl trim($this->systemConfigService->getString(
  172.             'AsbsDynamicAccessRedirect.config.redirectUrl',
  173.             null,
  174.             ''
  175.         ));
  176.         // Fallback zur Standard-URL, wenn keine konfiguriert ist
  177.         if (empty($redirectUrl)) {
  178.             return self::DEFAULT_REDIRECT_URL;
  179.         }
  180.         // Prüfen, ob der Trailing Slash beibehalten werden soll
  181.         $preserveTrailingSlash $this->systemConfigService->getBool(
  182.             'AsbsDynamicAccessRedirect.config.preserveTrailingSlash',
  183.             null,
  184.             true
  185.         );
  186.         // Redirect-URL-Zeichenkette ohne Änderungen extrahieren
  187.         $originalUrl = (string) $redirectUrl;
  188.         // Überprüfen, ob die ursprüngliche URL mit "/" endet
  189.         $endsWithSlash substr($originalUrl, -1) === '/';
  190.         if ($preserveTrailingSlash && $endsWithSlash) {
  191.             // Sicherstellen, dass die URL mit einem Slash endet, indem wir ihn explizit hinzufügen
  192.             // Zuerst alle Slashes am Ende entfernen und dann einen hinzufügen
  193.             return rtrim($originalUrl'/') . '/';
  194.         } elseif (!$preserveTrailingSlash) {
  195.             // Slash entfernen, wenn nicht gewünscht
  196.             return rtrim($originalUrl'/');
  197.         }
  198.         // Wenn kein Slash am Ende war oder Option nicht aktiviert, URL unverändert zurückgeben
  199.         return $originalUrl;
  200.     }
  201.     /**
  202.      * Überprüft, ob das Plugin aktiviert ist
  203.      */
  204.     private function isPluginEnabled(): bool
  205.     {
  206.         // Der zweite Parameter sollte die salesChannelId sein (null für system-global)
  207.         // Der dritte Parameter ist der Standardwert, falls die Konfiguration nicht gefunden wird
  208.         return $this->systemConfigService->getBool(
  209.             'AsbsDynamicAccessRedirect.config.enabled'
  210.             null
  211.             true
  212.         );
  213.     }
  214. }