<?php
namespace CioCustomerPermissionGroups\Subscriber;
use CioCustomerPermissionGroups\Service\CustomerPermissionService;
use Shopware\Core\Checkout\Cart\Exception\CustomerNotLoggedInException;
use Shopware\Core\Checkout\Customer\CustomerEntity;
use Shopware\Core\Checkout\Customer\SalesChannel\AbstractLoginRoute;
use Shopware\Core\Checkout\Customer\SalesChannel\AbstractLogoutRoute;
use Shopware\Core\Checkout\Customer\SalesChannel\LogoutRoute;
use Shopware\Core\Content\Category\CategoryEntity;
use Shopware\Core\Content\Category\Event\CategoryRouteCacheKeyEvent;
use Shopware\Core\Content\Category\Event\NavigationLoadedEvent;
use Shopware\Core\Content\Category\Exception\CategoryNotFoundException;
use Shopware\Core\Content\Category\Tree\TreeItem;
use Shopware\Core\Content\Product\Events\ProductListingResultEvent;
use Shopware\Core\Content\Product\Events\ProductListingRouteCacheKeyEvent;
use Shopware\Core\Content\Product\Events\ProductSuggestCriteriaEvent;
use Shopware\Core\Content\Product\Exception\ProductNotFoundException;
use Shopware\Core\Content\Product\SalesChannel\SalesChannelProductEntity;
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\Validation\DataBag\DataBag;
use Shopware\Core\Framework\Validation\DataBag\RequestDataBag;
use Shopware\Storefront\Event\StorefrontRenderEvent;
use Shopware\Storefront\Page\Navigation\NavigationPageLoadedEvent;
use Shopware\Storefront\Page\Product\ProductPage;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class CategoriesPermissionsSubscriber implements EventSubscriberInterface
{
private EntityRepository $categoryRepository;
private EventDispatcherInterface $eventDispatcher;
private CustomerPermissionService $customerPermissionService;
private AbstractLogoutRoute $logoutRoute;
public function __construct(EntityRepository $categoryRepository, EventDispatcherInterface $eventDispatcher, CustomerPermissionService $customerPermissionService, AbstractLogoutRoute $logoutRoute)
{
$this->categoryRepository = $categoryRepository;
$this->eventDispatcher = $eventDispatcher;
$this->customerPermissionService = $customerPermissionService;
$this->logoutRoute = $logoutRoute;
}
public static function getSubscribedEvents(): array
{
// Return the events to listen to as array like this: <event to listen to> => <method to execute>
return [
NavigationLoadedEvent::class => 'onNavigationLoadedEvent',
NavigationPageLoadedEvent::class => 'onNavigationPageLoadedEvent',
StorefrontRenderEvent::class => 'onSalesChannelProductLoaded',
CategoryRouteCacheKeyEvent::class => 'onCategoryRouteCacheKey',
ProductListingRouteCacheKeyEvent::class => 'onProductListingRouteCacheKey',
ProductSuggestCriteriaEvent::class => 'onProductSuggestCriteriaEvent'
];
}
public function onProductListingRouteCacheKey(ProductListingRouteCacheKeyEvent $event)
{
$event->addPart(session_id());
}
public function onCategoryRouteCacheKey(CategoryRouteCacheKeyEvent $categoryRouteCacheKeyEvent)
{
$categoryRouteCacheKeyEvent->addPart(session_id());
}
public function onNavigationLoadedEvent(NavigationLoadedEvent $event)
{
$event->getNavigation()->setTree(array_filter($event->getNavigation()->getTree(), function (TreeItem $treeItem) use ($event) {
$category = $treeItem->getCategory();
if ($this->categoryAccessAllowedByEntities($category, $event->getSalesChannelContext()->getCustomer())) {
$this->filterTreeChilds($treeItem, $event->getSalesChannelContext()->getCustomer());
return true;
}
return false;
}));
}
public function onNavigationPageLoadedEvent(NavigationPageLoadedEvent $event)
{
$customer = $event->getSalesChannelContext()->getCustomer();
if ($customer === null) {
throw new CustomerNotLoggedInException();
}
if (!$this->categoryAccessAllowed($event->getSalesChannelContext()->getSalesChannel()->getNavigationCategoryId(), $event->getSalesChannelContext()->getCustomer(), $event->getContext())) {
$this->logoutRoute->logout($event->getSalesChannelContext(), new RequestDataBag());
throw new CustomerNotLoggedInException();
}
if (!$this->categoryAccessAllowed($event->getPage()->getNavigationId(), $customer, $event->getContext())) {
throw new CategoryNotFoundException($event->getPage()->getNavigationId());
}
}
public function onSalesChannelProductLoaded(StorefrontRenderEvent $event)
{
if ($event->getSalesChannelContext()->getCustomer() !== null) {
if (!$this->categoryAccessAllowed($event->getSalesChannelContext()->getSalesChannel()->getNavigationCategoryId(), $event->getSalesChannelContext()->getCustomer(), $event->getContext())) {
$this->logoutRoute->logout($event->getSalesChannelContext(), new RequestDataBag());
throw new CustomerNotLoggedInException();
}
}
$page = array_key_exists('page', $event->getParameters()) ? $event->getParameters()['page'] : null;
if ($page instanceof ProductPage) {
$product = $page->getProduct();
if ($product instanceof SalesChannelProductEntity) {
if ($event->getSalesChannelContext()->getCustomer() === null) {
throw new CustomerNotLoggedInException();
}
foreach ($product->getCategoryIds() as $id) {
if ($this->categoryAccessAllowed($id, $event->getSalesChannelContext()->getCustomer(), $event->getContext())) {
return;
}
}
throw new ProductNotFoundException($page->getProduct()->getId());
}
}
}
protected function categoryAccessAllowed(string $categoryId, $customer, $context): bool
{
$customerGroups = [];
if ($customer instanceof CustomerEntity) {
$customerGroups = $this->customerPermissionService->getCustomerPermissionGroupIds($customer);
}
if ($categoryId) {
$categoryCriteria = new Criteria([$categoryId]);
$category = $this->categoryRepository->search($categoryCriteria, $context);
/** @var CategoryEntity $category */
if ($category = $category->first()) {
$categoryGroups = is_array($category->getCustomFields()) && array_key_exists('custom_acl_groups', $category->getCustomFields()) ? $category->getCustomFields()['custom_acl_groups'] : [];
if (count(array_intersect($customerGroups, $categoryGroups)) < 1 && count($categoryGroups) > 0) {
return false;
}
}
}
return true;
}
protected function categoryAccessAllowedByEntities($category, $customer): bool
{
$customerGroups = [];
if ($customer instanceof CustomerEntity) {
$customerGroups = $this->customerPermissionService->getCustomerPermissionGroupIds($customer);
}
if ($category) {
$categoryGroups = is_array($category->getCustomFields()) && array_key_exists('custom_acl_groups', $category->getCustomFields()) ? $category->getCustomFields()['custom_acl_groups'] : [];
if (count(array_intersect($customerGroups, $categoryGroups)) < 1 && count($categoryGroups) > 0) {
return false;
}
}
return true;
}
protected function filterTreeChilds(TreeItem $treeItem, $customer)
{
if (is_null($treeItem->getChildren()) || count($treeItem->getChildren()) < 1) {
return;
}
$treeItem->setChildren(array_filter($treeItem->getChildren(), function (TreeItem $treeItem) use ($customer) {
$category = $treeItem->getCategory();
if ($this->categoryAccessAllowedByEntities($category, $customer)) {
$this->filterTreeChilds($treeItem, $customer);
return true;
}
return false;
}));
}
public function onProductListingResultEvent(ProductListingResultEvent $event)
{
$customerRoleGroups = [];
$removedProducts = [];
$customer = $event->getSalesChannelContext()->getCustomer();
if ($customer instanceof CustomerEntity) {
$customerRoleGroups = $this->customerPermissionService->getCustomerPermissionGroupIds($customer);
}
/** @var SalesChannelProductEntity $product */
foreach ($event->getResult()->getElements() as $key => $product) {
//dd($product->getCategoryIds());
$categoryIds = $product->getCategoryIds();
/** @var CategoryEntity $lowestCategory */
//$lowestCategory = $this->categoryRepository->search(new Criteria([$lowestCategoryId]), $event->getContext())->first();
if ($this->removeProductFromResult($categoryIds, $customerRoleGroups, $event->getContext())) {
$removedProducts[] = $key;
}
}
foreach ($removedProducts as $removedProduct) {
//$event->getResult()->remove($removedProduct);
}
}
protected function removeProductFromResult(array $categoryIds, array $customerRoleGroups, $context)
{
foreach ($categoryIds as $categoryId) {
$category = $this->categoryRepository->search(new Criteria([$categoryId]), $context)->first();
if ($category && is_array($category->getCustomFields()) && array_key_exists('custom_acl_groups', $category->getCustomFields()) && is_array($category->getCustomFields()['custom_acl_groups'])) {
if (count(array_intersect($category->getCustomFields()['custom_acl_groups'], $customerRoleGroups)) !== 0 || count($category->getCustomFields()['custom_acl_groups']) === 0) {
return false;
}
}
}
return true;
}
public function onProductSuggestCriteriaEvent(ProductSuggestCriteriaEvent $event)
{
$event->getCriteria()->setTitle('search::product::suggestion');
}
}