<?php declare(strict_types=1);namespace Semes\RefererBreadcrumb\Subscriber;use Shopware\Core\Framework\DataAbstractionLayer\Exception\InconsistentCriteriaIdsException;use Shopware\Storefront\Page\Product\ProductPageLoadedEvent;use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;use Symfony\Component\EventDispatcher\EventSubscriberInterface;use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\OrFilter;use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\ContainsFilter;use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsAnyFilter;use Shopware\Core\Content\Category\CategoryEntity;use Shopware\Core\Content\Product\Events\ProductDetailRouteCacheTagsEvent;use Symfony\Component\HttpKernel\KernelEvents;use Shopware\Core\System\SalesChannel\SalesChannelContext;use Symfony\Component\HttpFoundation\Session\Session;use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage;class FrontendSubscriber implements EventSubscriberInterface{ public $categoryRepository; public $systemConfigService; public $cacheInvalidator; public $refererCategory; public $requestStack; public $session = false; /** * @return array */ public static function getSubscribedEvents(): array { return [ ProductPageLoadedEvent::class => 'onProductPageLoaded', ProductDetailRouteCacheTagsEvent::class => 'onProductDetailRouteCacheTagsEvent', //KernelEvents::CONTROLLER => 'invalidateCache', KernelEvents::REQUEST => 'invalidateCache' ]; } public function getCurrentSession() { if($this->session) return $this->session; $currentRequest = $this->requestStack->getCurrentRequest(); if (!$currentRequest->hasSession()) { $currentRequest->setSession(new Session(new MockArraySessionStorage())); } $this->session = $this->requestStack->getSession(); return $this->session; } /** * * @param ProductDetailRouteCacheTagsEvent $event * @return void * @throws InconsistentCriteriaIdsException */ public function onProductDetailRouteCacheTagsEvent($event) { $event->addTags(['referer-category']); } /** * * @param ProductPageLoadedEvent $event * @return void * @throws InconsistentCriteriaIdsException */ public function onProductPageLoaded($event) { $page = $event->getPage(); $salesChannelContext = $event->getSalesChannelContext(); $salesChannel = $salesChannelContext->getSalesChannel(); // get product categories $product = $page->getProduct(); if(!$product) return; // try to get path from referer if($refererCategory = $this->getRefererCategory($salesChannelContext, $salesChannel, $product, $product->parentId ?? $product->id)){ $page->getHeader()->getNavigation()->setActive($refererCategory); // set active navigation item $product->setSeoCategory($refererCategory); // sets breadcrumb return; } } private function getRefererCategory($salesChannelContext, $salesChannel, $product, $sessionIdentificator = '') { if(!is_null($this->refererCategory)) return $this->refererCategory; $url = $_SERVER['HTTP_REFERER'] ?? ''; foreach ($salesChannel->getDomains() as $domain) { $url = stripos($url, $domain->getUrl()) === 0 ? (str_replace($domain->getUrl(), '', $url)) : $url; } $url = explode('?', $url, 2); $url = $url[0]; $url = trim(strtolower($url), '/'); $this->refererCategory = $this->getCategoryByUrl($url, $salesChannelContext, $salesChannel, $product, $sessionIdentificator); $this->getCurrentSession()->set('refererCategory'. $sessionIdentificator, $this->refererCategory); return $this->refererCategory; } /** * * @param string $url * @param mixed $context * @return CategoryEntity|false * @throws InconsistentCriteriaIdsException */ private function getCategoryByUrl($url, $salesChannelContext, $salesChannel, $product, $sessionIdentificator = '') { $allowedBreadcrumbCategories = $this->systemConfigService->get("SemesRefererBreadcrumb.config.allowedBreadcrumbCategories") ?? 'all'; $criteria = new Criteria(); $criteria->addAssociation('seoUrls'); $criteria->addFilter(new EqualsFilter('active', true)); $categoryUrls = []; if($allowedBreadcrumbCategories == 'onlyProductCategories'){ $categoryUrls = $product->getCategoryIds(); } elseif($allowedBreadcrumbCategories == 'onlyProductCategoriesAndParents'){ $categoryUrls = $product->getCategoryTree(); } if($categoryUrls){ $criteria->addFilter(new EqualsAnyFilter('id', $categoryUrls)); } $criteria->addFilter($this->getSalesChannelFilter($salesChannelContext)); $categories = $this->categoryRepository->search($criteria, $salesChannelContext->getContext()); foreach ($categories as $category) { /* @var $category CategoryEntity */ foreach ($category->getSeoUrls() as $seoUrl) { if($seoUrl->getSalesChannelId() != $salesChannel->getId()) continue; if(trim(strtolower($seoUrl->getSeoPathInfo()), '/') == $url){ return $category; } if(trim(strtolower($seoUrl->getPathInfo()), '/') == $url){ return $category; } } } return $this->getCurrentSession()->get('refererCategory'. $sessionIdentificator) ?? false; } private function getSalesChannelFilter(SalesChannelContext $context) { $ids = array_filter([ $context->getSalesChannel()->getNavigationCategoryId(), $context->getSalesChannel()->getServiceCategoryId(), $context->getSalesChannel()->getFooterCategoryId(), ]); return new OrFilter(array_map(static function (string $id) { return new ContainsFilter('path', '|' . $id . '|'); }, $ids)); } public function invalidateCache() { $this->cacheInvalidator->invalidate(['referer-category']); }}