<?php declare(strict_types=1);
namespace DonCarneTheme\Subscriber;
use Shopware\Core\Content\Category\Exception\CategoryNotFoundException;
use Shopware\Core\Content\Product\Exception\ProductNotFoundException;
use Shopware\Core\Framework\DataAbstractionLayer\EntityRepositoryInterface;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
use Shopware\Core\System\SalesChannel\SalesChannelContext;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpKernel\Event\ExceptionEvent;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\Routing\RouterInterface;
class DynamicAccessSubscriber implements EventSubscriberInterface
{
private EntityRepositoryInterface $categoryRepository;
private EntityRepositoryInterface $productRepository;
private RouterInterface $router;
/**
* The URL for the Beef Club login page
*/
private const BEEF_CLUB_URL = 'https://doncarne.de/c/beef-club/';
public function __construct(
EntityRepositoryInterface $categoryRepository,
EntityRepositoryInterface $productRepository,
RouterInterface $router
) {
$this->categoryRepository = $categoryRepository;
$this->productRepository = $productRepository;
$this->router = $router;
}
public static function getSubscribedEvents(): array
{
return [
KernelEvents::EXCEPTION => ['onKernelException', 0]
];
}
public function onKernelException(ExceptionEvent $event): void
{
$exception = $event->getThrowable();
$request = $event->getRequest();
// Check if we have a sales channel context and a logged-in user
if (!$request->attributes->has('sw-sales-channel-context')) {
return;
}
/** @var SalesChannelContext $context */
$context = $request->attributes->get('sw-sales-channel-context');
// Only handle 404 exceptions, CategoryNotFoundException, and ProductNotFoundException
if (!($exception instanceof NotFoundHttpException) &&
!($exception instanceof CategoryNotFoundException) &&
!($exception instanceof ProductNotFoundException)) {
return;
}
// Check for typical URL patterns that might be restricted by Dynamic Access
$pathInfo = $request->getPathInfo();
// Handle category not found exceptions - check if category actually exists
if (($exception instanceof CategoryNotFoundException) || $this->isCategoryPath($pathInfo)) {
$categoryId = $this->extractCategoryIdFromPath($pathInfo);
if ($categoryId && $this->categoryExists($categoryId, $context)) {
// Category exists but user doesn't have access, redirect to Beef Club
$response = new RedirectResponse(self::BEEF_CLUB_URL);
$event->setResponse($response);
return;
}
}
// Handle product not found exceptions - check if product actually exists
if (($exception instanceof ProductNotFoundException) || $this->isProductPath($pathInfo)) {
$productId = $this->extractProductIdFromPath($pathInfo);
if ($productId && $this->productExists($productId, $context)) {
// Product exists but user doesn't have access, redirect to Beef Club
$response = new RedirectResponse(self::BEEF_CLUB_URL);
$event->setResponse($response);
return;
}
}
}
/**
* Determine if the path looks like a category page
*/
private function isCategoryPath(string $pathInfo): bool
{
// Category paths typically don't have extensions and don't contain /detail/ or /checkout/
return !preg_match('/(\.[\w\d]+$|\/detail\/|\/checkout\/)/', $pathInfo);
}
/**
* Determine if the path looks like a product detail page
*/
private function isProductPath(string $pathInfo): bool
{
// Product paths typically contain /detail/
return strpos($pathInfo, '/detail/') !== false;
}
/**
* Extract a potential category ID from a URL path
*/
private function extractCategoryIdFromPath(string $pathInfo): ?string
{
// Extract the last segment of the URL which should be the category ID
if (preg_match('/\/([a-f0-9]{32})(?:\/|$)/', $pathInfo, $matches)) {
return $matches[1];
}
return null;
}
/**
* Extract a potential product ID from a URL path
*/
private function extractProductIdFromPath(string $pathInfo): ?string
{
// Extract the ID from a path like '/detail/a1b2c3.../'
if (preg_match('/\/detail\/([a-f0-9]{32})(?:\/|$)/', $pathInfo, $matches)) {
return $matches[1];
}
return null;
}
/**
* Check if a category exists in the database without applying customer group filters
*/
private function categoryExists(string $categoryId, SalesChannelContext $context): bool
{
$criteria = new Criteria([$categoryId]);
$criteria->addFilter(new EqualsFilter('active', true));
$criteria->addAssociation('type');
// Query directly against the repository to bypass sales channel filters
$result = $this->categoryRepository->search($criteria, $context->getContext());
return $result->getTotal() > 0;
}
/**
* Check if a product exists in the database without applying customer group filters
*/
private function productExists(string $productId, SalesChannelContext $context): bool
{
$criteria = new Criteria([$productId]);
$criteria->addFilter(new EqualsFilter('active', true));
// Query directly against the repository to bypass sales channel filters
$result = $this->productRepository->search($criteria, $context->getContext());
return $result->getTotal() > 0;
}
}