<?php
declare(strict_types=1);
namespace Rhiem\RhiemUserProducts\Subscriber;
use Doctrine\DBAL\Connection;
use Shopware\Core\Framework\Context;
use Shopware\Core\Framework\Uuid\Uuid;
use Shopware\Core\Framework\Struct\ArrayStruct;
use Shopware\Core\Content\Category\Tree\TreeItem;
use Symfony\Component\DependencyInjection\Container;
use Shopware\Core\Content\Category\CategoryDefinition;
use Shopware\Core\System\SystemConfig\SystemConfigService;
use Shopware\Core\Content\Category\Event\NavigationLoadedEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
use Rhiem\RhiemUserProducts\Entities\ProductGroup\ProductGroupEntity;
use Shopware\Core\Content\Product\SalesChannel\ProductAvailableFilter;
use Shopware\Core\Framework\DataAbstractionLayer\EntityRepositoryInterface;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
use Shopware\Core\Content\ProductStream\Service\ProductStreamBuilderInterface;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\ContainsFilter;
use Shopware\Core\Content\Product\Aggregate\ProductVisibility\ProductVisibilityDefinition;
class OnNavigationLoadedEvent implements EventSubscriberInterface
{
/**
* @var EntityRepositoryInterface
*/
private $productGroupRepository;
/**
* @var SystemConfigService
*/
private $systemConfigService;
/**
* @var ProductStreamBuilderInterface
*/
private $productStreamBuilder;
/**
* @var EntityRepositoryInterface
*/
private $productRepository;
/**
* @var Container
*/
private $container;
/**
* @var string
*/
private $productGroupId;
/**
* @var ProductGroupEntity|null
*/
private $productGroup;
/**
* @var array|null
*/
private $categories;
/**
* @var array|null
*/
private $productGroupCategoryIds;
private $blacklistProductIds;
public const FALLBACK_PRODUCT_GROUP_ID = '0502913fa0a54ee7b24a4128fb2c62bb';
/**
* @param EntityRepositoryInterface $productGroupRepository
* @param SystemConfigService $systemConfigService
* @param EntityRepositoryInterface $productRepository
* @param ProductStreamBuilderInterface $productStreamBuilder
*/
public function __construct(
EntityRepositoryInterface $productGroupRepository,
SystemConfigService $systemConfigService,
EntityRepositoryInterface $productRepository,
ProductStreamBuilderInterface $productStreamBuilder,
Container $container
) {
$this->productGroupRepository = $productGroupRepository;
$this->systemConfigService = $systemConfigService;
$this->productRepository = $productRepository;
$this->productStreamBuilder = $productStreamBuilder;
$this->productGroupId = self::FALLBACK_PRODUCT_GROUP_ID;
$this->container = $container;
$this->blacklistProductIds = [];
}
/**
* @return string[]
*/
public static function getSubscribedEvents(): array
{
return [
NavigationLoadedEvent::class => 'onNavigationLoadedEvent',
];
}
/**
* @param NavigationLoadedEvent $event
*/
public function onNavigationLoadedEvent(NavigationLoadedEvent $event): void
{
$this->setProductGroup($event);
$hideCategories = $this->systemConfigService->get('RhiemUserProducts.config.hideCategories');
if (!$hideCategories) {
return;
}
$event->getNavigation()->setTree($this->filterEmptyTreeCategories($event));
}
/**
* @param NavigationLoadedEvent $event
*/
public function setProductGroup(NavigationLoadedEvent $event): void
{
$customer = $event->getSalesChannelContext()->getCustomer();
if ($customer) {
/**
* @var ArrayStruct $foreignKeys
*/
$foreignKeys = $customer->getExtension('foreignKeys');
$productGroupId = $foreignKeys->get('rhiemProductGroupId');
if ($productGroupId) {
$this->productGroupId = $productGroupId;
$criteria = new Criteria([$this->productGroupId]);
$productGroup = $this->productGroupRepository->search(
$criteria,
$event->getContext()
)->first();
if ($productGroup->getActive()) {
$this->productGroup = $productGroup;
$customer->addExtension('rhiemProductGroup', $this->productGroup);
}
} else {
$customerGroupId = $customer->getGroupId();
}
} else {
$customerGroupId = $event->getSalesChannelContext()->getCurrentCustomerGroup()->getId();
}
if(isset($customerGroupId)) {
$criteria = new Criteria();
$criteria->addFilter(new ContainsFilter('customerGroups', $customerGroupId));
$productGroup = $this->productGroupRepository->search(
$criteria,
$event->getContext()
)->first();
if($productGroup) {
$this->productGroup = $productGroup;
}
}
if (!isset($productGroup)) {
$criteria = new Criteria([$this->productGroupId]);
$productGroup = $this->productGroupRepository->search(
$criteria,
$event->getContext()
)->first();
$this->productGroup = $productGroup;
}
}
/**
* @param NavigationLoadedEvent $event
*
* @return array
*/
public function filterEmptyTreeCategories(NavigationLoadedEvent $event): array
{
/**
* @var array $showCategoriesIds
*/
$showCategoriesIds = $this->systemConfigService->get('RhiemUserProducts.config.showCategories') ?: [];
$tree = $event->getNavigation()->getTree();
/**
* @var Connection $connection
*/
$connection = $this->container->get('Doctrine\DBAL\Connection');
$this->setCategories($connection);
$this->setProductGroupCategoryIds($connection, $event->getContext());
$tree = $this->buildNewTree(
$tree,
$event->getContext(),
$event->getSalesChannelContext()->getSalesChannel()->getId(),
$showCategoriesIds
);
return $tree;
}
public function setProductGroupCategoryIds(Connection $connection, $context): void
{
$criteria = new Criteria([self::FALLBACK_PRODUCT_GROUP_ID]);
$fallbackGroup = $this->productGroupRepository->search(
$criteria,
$context
)->first();
if ($this->productGroup->getGroupMode() === "whitelist") {
$sql = "SELECT p.category_tree FROM rhiem_userproducts_productgroup_product
LEFT JOIN product p on rhiem_userproducts_productgroup_product.product_id = p.id AND rhiem_userproducts_productgroup_product.product_version_id = p.version_id AND (p.child_count IS NULL OR p.child_count = 0)
WHERE rhiem_userproducts_productgroup_product.product_group_id = ? AND p.category_tree IS NOT NULL";
} elseif ($this->productGroup->getGroupMode() === "whitelist plus") {
if ($fallbackGroup->getGroupMode() === "whitelist") {
$sql = "SELECT p.category_tree FROM rhiem_userproducts_productgroup_product
LEFT JOIN product p on rhiem_userproducts_productgroup_product.product_id = p.id AND rhiem_userproducts_productgroup_product.product_version_id = p.version_id AND (p.child_count IS NULL OR p.child_count = 0)
WHERE (rhiem_userproducts_productgroup_product.product_group_id = ? AND p.category_tree IS NOT NULL)
OR (rhiem_userproducts_productgroup_product.product_group_id = ? AND p.category_tree IS NOT NULL)";
} else {
$sql = "SELECT DISTINCT category_tree from product WHERE
(id IN
(SELECT product_id from rhiem_userproducts_productgroup_product
WHERE product_group_id = ?)
OR id NOT IN
(SELECT product_id from rhiem_userproducts_productgroup_product
WHERE product_group_id = ?))
AND (child_count IS NULL OR child_count = 0)";
}
} else {
$sql = "SELECT DISTINCT category_tree from product WHERE id not in
(SELECT product_id from rhiem_userproducts_productgroup_product WHERE product_group_id = ?) AND (child_count IS NULL OR child_count = 0)";
}
if ($this->productGroup->getGroupMode() === "whitelist plus") {
$result = $connection->fetchAll($sql, [
Uuid::fromHexToBytes($this->productGroupId),
Uuid::fromHexToBytes(self::FALLBACK_PRODUCT_GROUP_ID)
]);
} else {
$result = $connection->fetchAll($sql, [Uuid::fromHexToBytes($this->productGroupId)]);
}
if (!$result) {
$this->productGroupCategoryIds = [];
return;
}
$result = array_unique(
array_merge(
...array_map(function ($row) {
if (!$row['category_tree']) {
return [];
}
return \GuzzleHttp\json_decode($row['category_tree']);
}, $result)
)
);
$this->productGroupCategoryIds = $result;
}
public function setCategories(Connection $connection): void
{
$sql = "Select lower(hex(category.id)) as id, cp.type as cmsPageType, lower(hex(category.product_stream_id)) as productStreamId, category.product_assignment_type as productAssignmentType, lower(hex(category.product_stream_id)) as productStreamId from category
LEFT JOIN cms_page cp on category.cms_page_id = cp.id;";
$result = $connection->fetchAll($sql);
$categories = [];
foreach ($result as $row) {
$categories[$row['id']] = $row;
}
$this->categories = $categories;
}
/**
* @param array $tree
* @param array $categoryIds
*
* @return array
*/
protected function collectCategoryIds(array $tree, array $categoryIds): array
{
foreach ($tree as $treeItem) {
$categoryIds[] = $treeItem->getCategory()->getId();
$categoryIds = $this->collectCategoryIds($treeItem->getChildren(), $categoryIds);
}
return array_unique($categoryIds);
}
/**
* @param array $tree
* @param Context $context
* @param string $salesChannelId
* @param array $showCategoriesIds
*
* @return array
*/
protected function buildNewTree(
array $tree,
Context $context,
string $salesChannelId,
array $showCategoriesIds
): array {
foreach ($tree as $key => $treeItem) {
$type = $treeItem->getCategory()->getType();
$showCategory = in_array($treeItem->getCategory()->getId(), $showCategoriesIds);
$children = $treeItem->getChildren();
if (count($children) > 0) {
$children = $this->buildNewTree($children, $context, $salesChannelId, $showCategoriesIds);
if (!$showCategory && $type === "page" && count($children) == 0 && !$this->hasProducts(
$treeItem,
$context,
$salesChannelId
)) {
unset($tree[$key]);
continue;
}
} else {
if (!$showCategory && $type === "page" && !$this->hasProducts($treeItem, $context, $salesChannelId)) {
unset($tree[$key]);
continue;
}
}
$treeItem->setChildren($children);
}
return $tree;
}
/**
* @param TreeItem $treeItem
* @param Context $context
* @param string $salesChannelId
*
* @return bool
*/
protected function hasProducts(TreeItem $treeItem, Context $context, string $salesChannelId): bool
{
$category = $this->categories[$treeItem->getCategory()->getId()];
if ($category['cmsPageType'] && $category['cmsPageType'] !== 'product_list') {
return true;
}
if ($category['productAssignmentType'] === CategoryDefinition::PRODUCT_ASSIGNMENT_TYPE_PRODUCT_STREAM && isset($category['productStreamId'])) {
$criteria = new Criteria();
$criteria->addAssociation('product_groups');
if ($this->productGroup->getGroupMode() === "whitelist") {
$criteria->addFilter(new EqualsFilter('product.product_groups.id', $this->productGroupId));
} elseif ($this->productGroup->getGroupMode() === "whitelist plus") {
$criteria->addFilter(new EqualsFilter('product.product_groups.id', $this->productGroupId));
$criteria->addFilter(new EqualsFilter('product.product_groups.id', self::FALLBACK_PRODUCT_GROUP_ID));
}
$criteria->addFilter(
new ProductAvailableFilter($salesChannelId, ProductVisibilityDefinition::VISIBILITY_ALL)
);
$filters = $this->productStreamBuilder->buildFilters(
$category['productStreamId'],
$context
);
$criteria->addFilter(...$filters);
$productSearchIds = $this->productRepository->searchIds($criteria, $context);
if ($this->productGroup->getGroupMode() === "whitelist") {
$total = $productSearchIds->getTotal();
return $total > 0;
} else {
if (!$this->blacklistProductIds) {
/**
* @var Connection $connection
*/
$connection = $this->container->get('Doctrine\DBAL\Connection');
$sql = "SELECT id FROM product LEFT JOIN rhiem_userproducts_productgroup_product rupp on product.id = rupp.product_id and product.version_id = rupp.product_version_id WHERE rupp.product_group_id = ?";
$result = $connection->fetchAll($sql, [Uuid::fromHexToBytes($this->productGroupId)]);
$this->blacklistProductIds = array_map(function ($row) {
return $row['id'];
}, $result);
}
if ($productSearchIds->getTotal() > count($this->blacklistProductIds)) {
return true;
}
foreach ($productSearchIds->getIds() as $id) {
if (!in_array($id, $this->blacklistProductIds)) {
return true;
}
}
return false;
}
} else {
return in_array($treeItem->getCategory()->getId(), $this->productGroupCategoryIds);
}
}
}