<?php
declare(strict_types=1);
namespace EsmComputer\Storefront\Subscriber;
use Shopware\Core\Checkout\Promotion\PromotionEntity;
use Shopware\Core\Content\Category\CategoryCollection;
use Shopware\Core\Content\Category\CategoryEntity;
use Shopware\Core\Content\Category\Service\NavigationLoader;
use Shopware\Core\Content\Category\Tree\Tree;
use Shopware\Core\Content\Category\Tree\TreeItem;
use Shopware\Core\Content\Product\Events\ProductListingCriteriaEvent;
use Shopware\Core\Content\Product\Events\ProductListingResultEvent;
use Shopware\Core\Content\Product\ProductEvents;
use Shopware\Core\Content\Product\SalesChannel\SalesChannelProductEntity;
use Shopware\Core\Framework\Context;
use Shopware\Core\Framework\DataAbstractionLayer\EntityRepository;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\RangeFilter;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Sorting\FieldSorting;
use Shopware\Core\Framework\Struct\ArrayStruct;
use Shopware\Core\System\SalesChannel\Entity\SalesChannelEntityLoadedEvent;
use Shopware\Storefront\Page\Navigation\NavigationPageLoadedEvent;
use Shopware\Storefront\Pagelet\Footer\FooterPageletLoadedEvent;
use Shopware\Storefront\Pagelet\Header\HeaderPageletLoadedEvent;
use Shopware\Storefront\Theme\ThemeEntity;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use function array_key_exists;
use function array_merge;
use function is_array;
class TemplateSubscriber implements EventSubscriberInterface
{
protected EntityRepository $themeRepository;
protected EntityRepository $manufacturerRepository;
protected EntityRepository $productRepository;
protected EntityRepository $categoryRepository;
protected EntityRepository $productReviewRepository;
protected EntityRepository $promotionRepository;
protected NavigationLoader $navigationLoader;
public function __construct(
EntityRepository $themeRepository,
EntityRepository $manufacturerRepository,
EntityRepository $productRepository,
EntityRepository $categoryRepository,
EntityRepository $productReviewRepository,
EntityRepository $promotionRepository,
NavigationLoader $navigationLoader
)
{
$this->themeRepository = $themeRepository;
$this->manufacturerRepository = $manufacturerRepository;
$this->productRepository = $productRepository;
$this->categoryRepository = $categoryRepository;
$this->productReviewRepository = $productReviewRepository;
$this->promotionRepository = $promotionRepository;
$this->navigationLoader = $navigationLoader;
}
public static function getSubscribedEvents(): array
{
return [
HeaderPageletLoadedEvent::class => 'onHeaderPageletLoaded',
FooterPageletLoadedEvent::class => 'onFooterPageletLoaded',
NavigationPageLoadedEvent::class => 'onNavigationPageLoaded',
ProductListingResultEvent::class => 'onProductListingResult',
'sales_channel.product.loaded' => ['onProductLoaded', -70],
ProductEvents::PRODUCT_LISTING_CRITERIA => 'onProductListingLoaded'
];
}
public function onHeaderPageletLoaded(HeaderPageletLoadedEvent $event): void
{
$criteria = new Criteria();
$criteria->addFilter(new EqualsFilter('technicalName', 'EsmComputer'));
/** @var ThemeEntity $theme */
$theme = $this->themeRepository->search($criteria, Context::createDefaultContext())->getEntities()->first();
$themeConfig = $theme->getBaseConfig();
$themeConfig['fields'] = array_merge($themeConfig['fields'], $theme->getConfigValues() ?? []);
$leftCategory = $themeConfig['fields']['sw-topbar-menu-category-left']['value'];
if($leftCategory) {
$leftCategory = $this->loadCategory($leftCategory);
$event->getPagelet()->addExtension('leftCategory', $leftCategory);
}
$rightCategory = $themeConfig['fields']['sw-topbar-menu-category-right']['value'];
if($rightCategory) {
$rightCategory = $this->loadCategory($rightCategory);
$event->getPagelet()->addExtension('rightCategory', $rightCategory);
}
$navigation = $event->getPagelet()->getNavigation();
$treeItems = $navigation->getTree();
foreach($treeItems as $category) {
$groupChildren = [];
foreach ($category->getChildren() as $child) {
if($child->getCategory()->getCustomFields() && array_key_exists('custom_category_menu_group', $child->getCategory()->getCustomFields()) && ($group = $child->getCategory()->getCustomFields()['custom_category_menu_group'])) {
$groupChildren[$group][] = $child;
}
}
$groupChildren = new ArrayStruct($groupChildren);
$category->getCategory()->addExtension('groupChildren', $groupChildren);
}
}
public function onNavigationPageLoaded(NavigationPageLoadedEvent $event): void
{
$navigationId = $event->getRequest()->attributes->get('navigationId');
if(!$navigationId) {
return;
}
$criteria = new Criteria();
$criteria->addFilter(new EqualsFilter('id', $navigationId));
$criteria->addAssociation('customFields');
/** @var CategoryEntity $category */
$category = $this->categoryRepository->search($criteria, $event->getContext())->getEntities()->first();
// Use parent header background if no background image is defined in current category
if($category && is_array($category->getCustomFields()) && !array_key_exists('custom_category_background', $category->getCustomFields())) {
$criteria = new Criteria();
$criteria->addFilter(new EqualsFilter('id', $category->getParentId()));
$criteria->addAssociation('customFields');
$parentCategory = $this->categoryRepository->search($criteria, $event->getContext())->getEntities()->first();
if($parentCategory->getCustomFields() && array_key_exists('custom_category_background', $parentCategory->getCustomFields())) {
$customFields = $category->getCustomFields();
$customFields['custom_category_background'] = $parentCategory->getCustomFields()['custom_category_background'];
$category->setCustomFields($customFields);
}
}
$event->getPage()->addExtension('category', $category);
}
public function onProductLoaded(SalesChannelEntityLoadedEvent $event): void
{
$products = $event->getEntities();
/** @var SalesChannelProductEntity $product */
foreach($products as $product) {
$this->addRatingToProduct($product, $event);
$this->addBadgeToProduct($product, $event);
}
}
// Used to add category on category pages
public function onProductListingResult(ProductListingResultEvent $event): void
{
if($event->getRequest()->attributes->get('navigationId')) {
$category = $this->getCategoryByNavigation($event->getRequest()->attributes->get('navigationId'), $event->getContext());
/** @var SalesChannelProductEntity $product */
foreach ($event->getResult() as $product) {
$product->addExtension('category', $category);
}
}
}
private function getCategoryByNavigation(string $navigationId, Context $context): ?CategoryEntity
{
if(!$navigationId) {
return null;
}
$criteria = new Criteria();
$criteria->addFilter(new EqualsFilter('id', $navigationId));
$criteria->addAssociation('customFields');
/** @var CategoryEntity $category */
$category = $this->categoryRepository->search($criteria, $context)->getEntities()->first();
// Use parent hint if no hint is defined in current category
if($category && is_array($category->getCustomFields()) && !array_key_exists('custom_category_single_item', $category->getCustomFields())) {
$criteria = new Criteria();
$criteria->addFilter(new EqualsFilter('id', $category->getParentId()));
$criteria->addAssociation('customFields');
$parentCategory = $this->categoryRepository->search($criteria, $context)->getEntities()->first();
if($parentCategory && $parentCategory->getCustomFields() && array_key_exists('custom_category_single_item', $parentCategory->getCustomFields())) {
$customFields = $category->getCustomFields();
$customFields['custom_category_single_item'] = $parentCategory->getCustomFields()['custom_category_single_item'];
$category->setCustomFields($customFields);
}
}
return $category;
}
// Used for listing pages
public function onProductListingLoaded(ProductListingCriteriaEvent $event): void
{
// Manufacturer image is displayed in listing
$event->getCriteria()->addAssociation('manufacturer.media');
// A-/B-Ware is displayed in listing
$event->getCriteria()->addAssociation('properties');
}
private function addRatingToProduct($product, $event): void
{
$criteria = new Criteria();
$criteria->addFilter(new EqualsFilter('productId', $product->getId()));
$criteria->addFilter(new RangeFilter('points', [RangeFilter::GTE => 4]));
$criteria->addFilter(new EqualsFilter('status', 1));
$criteria->addSorting(new FieldSorting('points', FieldSorting::DESCENDING));
$criteria->setLimit(1);
$rating = $this->productReviewRepository->search($criteria, $event->getContext())->getEntities()->first();
$product->addExtension('rating', $rating);
}
private function addBadgeToProduct(SalesChannelProductEntity $product, $event): void
{
if($product->getExtension('acrisPromotion')) {
$promotions = [];
$uvp = [];
foreach ($product->getExtension('acrisPromotion')['promotionIds'] as $promotionId) {
$criteria = new Criteria([$promotionId]);
/** @var PromotionEntity $promotion */
$promotion = $this->promotionRepository->search($criteria, $event->getContext())->first();
if($promotion) {
$promotions[] = $promotion->getName();
if($promotion->getCustomFields() && array_key_exists('custom_promotion_show_discount', $promotion->getCustomFields()) && $promotion->getCustomFields()['custom_promotion_show_discount']) {
$uvp[] = true;
}
}
}
$product->addExtension('customBadges', new ArrayStruct($promotions));
$product->addExtension('displayUVP', new ArrayStruct($uvp));
}
}
public function onFooterPageletLoaded(FooterPageletLoadedEvent $event): void
{
$criteria = new Criteria();
$criteria->addFilter(new EqualsFilter('technicalName', 'EsmComputer'));
/** @var ThemeEntity $theme */
$theme = $this->themeRepository->search($criteria, Context::createDefaultContext())->getEntities()->first();
$themeConfig = $theme->getBaseConfig();
if($themeConfig['fields'] && $theme->getConfigValues()) {
$themeConfig['fields'] = array_merge($themeConfig['fields'], $theme->getConfigValues());
}
$leftCategory = $themeConfig['fields']['sw-footer-menu-category-left']['value'];
if($leftCategory) {
$leftCategory = $this->loadCategory($leftCategory);
$event->getPagelet()->addExtension('leftCategory', $leftCategory);
}
$rightCategory = $themeConfig['fields']['sw-footer-menu-category-right']['value'];
if($rightCategory) {
$rightCategory = $this->loadCategory($rightCategory);
$event->getPagelet()->addExtension('rightCategory', $rightCategory);
}
}
private function loadCategory($categoryId): Tree
{
$criteria = new Criteria();
$criteria->addFilter(new EqualsFilter('id', $categoryId));
$criteria->addAssociation('children');
$criteria->addSorting(new FieldSorting('autoIncrement'));
/** @var CategoryEntity $category */
$category = $this->categoryRepository->search($criteria, Context::createDefaultContext())->getEntities()->first();
if($category) {
$parent = new TreeItem(null, []);
$parent->setCategory($category);
$parent->setChildren(
$this->buildTree($categoryId, $category->getChildren()->getElements())
);
$tree = [];
$tree[$categoryId] = $parent;
return new Tree(null, $tree);
}
return new Tree(null, []);
}
/**
* From: Shopware\Core\Content\Category\Service\NavigationLoader
*
* @param CategoryEntity[] $categories
*
* @return TreeItem[]
*/
private function buildTree(?string $parentId, array $categories): array
{
$children = new CategoryCollection();
foreach ($categories as $key => $category) {
if ($category->getParentId() !== $parentId) {
continue;
}
unset($categories[$key]);
$children->add($category);
}
$children->sortByPosition();
$items = [];
foreach ($children as $child) {
if (!$child->getActive() || !$child->getVisible()) {
continue;
}
$item = new TreeItem(null, []);
$item->setCategory($child);
$item->setChildren(
$this->buildTree($child->getId(), $categories)
);
$items[$child->getId()] = $item;
}
return $items;
}
}