<?php
namespace LoyxxRichBreadcrumb\Subscriber;
use Doctrine\DBAL\Connection;
use LoyxxRichBreadcrumb\Struct\Breadcrumb;
use LoyxxRichBreadcrumb\Struct\Data;
use LoyxxRichBreadcrumb\Traits\Microdata;
use Shopware\Core\Content\Seo\SeoUrl\SeoUrlEntity;
use Shopware\Core\Content\Seo\SeoUrlPlaceholderHandlerInterface;
use Shopware\Core\Framework\Adapter\Cache\CacheClearer;
use Shopware\Core\Framework\DataAbstractionLayer\EntityRepositoryInterface;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\ContainsFilter;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\MultiFilter;
use Shopware\Core\Framework\Struct\ArrayStruct;
use Shopware\Core\System\SystemConfig\SystemConfigService;
use Shopware\Storefront\Controller\NavigationController;
use Shopware\Storefront\Controller\ProductController;
use Shopware\Storefront\Framework\Routing\RequestTransformer;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\ControllerEvent;
use Symfony\Component\HttpKernel\KernelEvents;
/**
* Class ProductPageLoadedEvent
* @package LoyxxRichBreadcrumb\Subscriber
*/
class ProductPageLoadedEvent implements EventSubscriberInterface
{
use Microdata;
public const SESSION_SEO_CATEGORY = 'seoCategory';
public const SESSION_SEO_CATEGORY_ID = 'seoCategoryId';
/**
* @var SeoUrlPlaceholderHandlerInterface
*/
private $seoUrlPlaceholderHandler;
/**
* @var SystemConfigService
*/
private $systemConfigService;
/**
* @var EntityRepositoryInterface
*/
private $seoUrlRepository;
/**
* @var Connection
*/
private $connection;
/**
* @var EntityRepositoryInterface
*/
private $categoryRepository;
/**
* @var CacheClearer
*/
private $cacheClearer;
/**
* ProductPageLoadedEvent constructor.
* @param SeoUrlPlaceholderHandlerInterface $seoUrlPlaceholderHandler
* @param SystemConfigService $systemConfigService
* @param EntityRepositoryInterface $seoUrlRepository
* @param Connection $connection
*/
public function __construct(
SeoUrlPlaceholderHandlerInterface $seoUrlPlaceholderHandler,
SystemConfigService $systemConfigService,
EntityRepositoryInterface $seoUrlRepository,
EntityRepositoryInterface $categoryRepository,
Connection $connection,
CacheClearer $cacheClearer
)
{
$this->seoUrlPlaceholderHandler = $seoUrlPlaceholderHandler;
$this->systemConfigService = $systemConfigService;
$this->seoUrlRepository = $seoUrlRepository;
$this->connection = $connection;
$this->categoryRepository = $categoryRepository;
$this->cacheClearer = $cacheClearer;
}
/**
* @return array|string[]
*/
public static function getSubscribedEvents(): array
{
return [
\Shopware\Storefront\Page\Product\ProductPageLoadedEvent::class => 'onProductPageLoadedEvent',
KernelEvents::CONTROLLER => 'onControllerEvent'
];
}
/**
* @param \Shopware\Storefront\Page\Product\ProductPageLoadedEvent $event
*/
public function onProductPageLoadedEvent(\Shopware\Storefront\Page\Product\ProductPageLoadedEvent $event)
{
// create microdata for breadcrumb
$host = $event->getRequest()->attributes->get(RequestTransformer::SALES_CHANNEL_ABSOLUTE_BASE_URL) .
$event->getRequest()->attributes->get(RequestTransformer::SALES_CHANNEL_BASE_URL);
$uri = $event->getRequest()->getRequestUri();
if ( $this->systemConfigService->get(PageLoadedEventSubscriber::BUNDLE_NAME . '.config.modifyProductDetailBreadcrumb')){
$this->checkAndUpdateSeoBreadcrumbNavigation($event, $host);
}
$navigationTree = $event->getPage()->getHeader()->getNavigation()->getTree();
$product = $event->getPage()->getProduct();
$productBreadcrumb = [];
$this->createBreadcrumbTree($navigationTree, $product, $productBreadcrumb);
/**
* Hide breadcrumb on home page
*
* $breadcrumbIsVisible = $this->systemConfigService->get(PageLoadedEventSubscriber::BUNDLE_NAME.'.config.isInvisible');
* if ($this->systemConfigService->get(PageLoadedEventSubscriber::BUNDLE_NAME . '.config.isHiddenOnHomePage') && $event->getRequest()->attributes->get('_route') === 'frontend.home.page'){
* $breadcrumbIsVisible = false;
* }
* */
$breadcrumbStruct = new Breadcrumb(
$this->createMicrodata(
$event,
$productBreadcrumb,
$host,
$event->getSalesChannelContext(),
$uri
),
$this->systemConfigService->get(PageLoadedEventSubscriber::BUNDLE_NAME . '.config.isInvisible'),
$this->systemConfigService->get(PageLoadedEventSubscriber::BUNDLE_NAME . '.config.showProductTitle'),
$this->systemConfigService->get(PageLoadedEventSubscriber::BUNDLE_NAME . '.config.separator'),
$this->systemConfigService->get(PageLoadedEventSubscriber::BUNDLE_NAME . '.config.isVisibleHomeLink'),
$this->systemConfigService->get(PageLoadedEventSubscriber::BUNDLE_NAME . '.config.isHiddenOnHomePage'),
$this->systemConfigService->get(PageLoadedEventSubscriber::BUNDLE_NAME . '.config.homeLabel'),
$host,
$uri
);
$event->getPage()->addExtension('breadcrumb', $breadcrumbStruct);
$event->getPage()->getProduct()->addExtension('breadcrumb', $breadcrumbStruct);
$event->getSalesChannelContext()->addExtensions([
'breadcrumb' => $breadcrumbStruct,
'breadcrumbProduct' => new ArrayStruct(['product' => $event->getPage()->getProduct()])
]);
}
public function checkAndUpdateSeoBreadcrumbNavigation(\Shopware\Storefront\Page\Product\ProductPageLoadedEvent $event, string $host)
{
$referer = $event->getRequest()->server->get('HTTP_REFERER');
$session = $event->getRequest()->getSession();
$sessionCategoryId = $session->get(self::SESSION_SEO_CATEGORY_ID);
$categoryTree = $event->getPage()->getProduct()->getCategoryTree();
if ($categoryTree && $sessionCategoryId && in_array($sessionCategoryId, $categoryTree)){
$category = $this->categoryRepository->search(new Criteria([$sessionCategoryId]), $event->getContext())->first();
if ($category){
$event->getRequest()->getSession()->set(static::SESSION_SEO_CATEGORY, $category);
}
}else{
$session->remove(self::SESSION_SEO_CATEGORY_ID);
$session->remove(self::SESSION_SEO_CATEGORY);
}
if (($referer && $referer !== $host) && is_null($sessionCategoryId)) {
$referer = str_replace($host, "", $referer);
// remove ? and # from fragment from the URI
$referer = ltrim(substr(substr($referer, 0, strrpos($referer, '?') ?: strlen($referer)), 0, strrpos($referer, '#') ?: strlen($referer)), "/");
if ($referer){
// get the navigation id if it present in the seoUrl page
$criteria = new Criteria();
$criteria->setLimit(1);
$criteria->setTotalCountMode(1);
$criteria->addFilter(new MultiFilter(MultiFilter::CONNECTION_OR, [
new EqualsFilter('seoPathInfo', "$referer"),
new EqualsFilter('seoPathInfo', "{$referer}/")
]));
/** @var SeoUrlEntity $seoUrl */
$seoUrl = $this->seoUrlRepository->search($criteria, $event->getContext())->first();
if(is_null($seoUrl)){
// the received $lastPart was a uuid
$criteria = new Criteria();
$criteria->setLimit(1);
$criteria->setTotalCountMode(1);
$criteria->addFilter(new ContainsFilter('pathInfo', $referer));
$seoUrl = $this->seoUrlRepository->search($criteria, $event->getContext())->first();
}
if ($seoUrl){
$categoryId = $seoUrl->getForeignKey();
// check existence of the category
$rows = $this->connection->executeQuery("SELECT count(id) as row_count FROM category WHERE hex(id) = ?", [$categoryId]);
$row = $rows->fetch(\PDO::FETCH_ASSOC);
if (isset($row['row_count']) && $row['row_count']){
$category = $this->categoryRepository->search(new Criteria([$categoryId]), $event->getContext())->first();
if ($category){
$event->getRequest()->getSession()->set(static::SESSION_SEO_CATEGORY, $category);
}
}
}
}
}
}
/**
* @param array $navigationTree
* @param \Shopware\Core\Content\Product\SalesChannel\SalesChannelProductEntity $product
* @param $productBreadcrumb
*/
private function createBreadcrumbTree(array $navigationTree, \Shopware\Core\Content\Product\SalesChannel\SalesChannelProductEntity $product, &$productBreadcrumb)
{
if ($navigationTree) {
$productCategoryTree = $product->getCategoryTree();
if ($productCategoryTree){
$lastProductCategoryTree = end($productCategoryTree);
foreach ($navigationTree as $item) {
if ($item->getCategory()->getId() == $lastProductCategoryTree) {
$breadcrumb = $item->getCategory()->getPlainBreadcrumb($item->getCategory()->getId());
$breadcrumb = array_slice($breadcrumb, 1);
if ($breadcrumb) {
foreach ($breadcrumb as $key => $value) {
$productBreadcrumb[$key] = $value;
}
}
} else if ($item->getChildren()) {
$this->createBreadcrumbTree($item->getChildren(), $product, $productBreadcrumb);
}
}
}
}
}
public function onControllerEvent(ControllerEvent $event): void {
if ( $this->systemConfigService->get(PageLoadedEventSubscriber::BUNDLE_NAME . '.config.modifyProductDetailBreadcrumb')){
$controller = $event->getController();
$clearCache = is_array($controller) && count($controller) === 2 && $controller[0] instanceof NavigationController && $controller[1] === 'index';
if($clearCache){ // clear cache only for the navigation and product detail page
$this->cacheClearer->clear();
}
}
}
}