<?php declare(strict_types=1);
namespace Uandi\EFB\Storefront\Subscriber;
use Shopware\Core\Checkout\Cart\Cart;
use Shopware\Core\Checkout\Cart\LineItem\LineItem;
use Shopware\Core\Content\Product\Aggregate\ProductCrossSelling\ProductCrossSellingCollection;
use Shopware\Core\Content\Product\ProductEntity;
use Shopware\Core\Framework\DataAbstractionLayer\EntityRepository;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
use Shopware\Core\Framework\Struct\ArrayStruct;
use Shopware\Core\Framework\Struct\Struct;
use Shopware\Core\System\SalesChannel\SalesChannelContext;
use Shopware\Storefront\Page\Checkout\Cart\CheckoutCartPageLoadedEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Uandi\EFB\Storefront\Service\CrossSellingLoader;
use Uandi\EFB\Storefront\Trait\DecodeStockInformation;
class CartPageSubscriber implements EventSubscriberInterface
{
use DecodeStockInformation;
private const CROSS_SELLING_LIMIT = 5;
private const MAX_ITEMS_FOR_CROSS_SELLING = 20;
private CrossSellingLoader $crossSellingLoader;
private EntityRepository $productRepository;
public static function getSubscribedEvents(): array
{
return [
CheckoutCartPageLoadedEvent::class => 'onCartPageLoadedEvent',
];
}
public function __construct(
CrossSellingLoader $crossSellingLoader,
EntityRepository $productRepository
) {
$this->crossSellingLoader = $crossSellingLoader;
$this->productRepository = $productRepository;
}
public function onCartPageLoadedEvent(CheckoutCartPageLoadedEvent $event)
{
$cart = $event->getPage()->getCart();
$context = $event->getSalesChannelContext();
$productIds = $this->getProductIds($cart);
if (empty($productIds)) {
return;
}
$cart->addExtensions([
'crossSelling' => $this->buildCrossSelling($productIds, $context),
'stockInfo' => $this->fetchStockInformation($productIds, $context),
]);
}
private function fetchStockInformation(array $productIds, SalesChannelContext $context): ?Struct
{
$criteria = new Criteria($productIds);
$products = $this->productRepository->search($criteria, $context->getContext());
return new ArrayStruct($products->map(function (ProductEntity $productEntity) use ($context) {
return $this->decodeStockInfo($productEntity, $context);
}));
}
private function buildCrossSelling(array $productIds, SalesChannelContext $context): ?Struct
{
if (count($productIds) > static::MAX_ITEMS_FOR_CROSS_SELLING) {
return null;
}
$crossSellingCollection = $this->crossSellingLoader->fetchAssignedCrossSellings(
$productIds,
$context->getContext()
);
if (empty($crossSellingCollection->getElements())) {
return null;
}
$crossSellingCollection = $this->filterForUniqueName($crossSellingCollection);
if ($crossSellingCollection->count() > static::CROSS_SELLING_LIMIT) {
$crossSellingCollection = new ProductCrossSellingCollection(
$this->getRandomCrossSelling($crossSellingCollection)
);
}
return $this->crossSellingLoader->loadCrossSellings(
$crossSellingCollection,
$context
);
}
private function getProductIds(Cart $cart): array
{
$products = $cart->getLineItems()->filterFlatByType(LineItem::PRODUCT_LINE_ITEM_TYPE);
return array_map(function (LineItem $lineItem) {
return $lineItem->getReferencedId();
}, $products);
}
private function filterForUniqueName(
ProductCrossSellingCollection $crossSellingCollection
): ProductCrossSellingCollection {
foreach ($crossSellingCollection as $crossSelling) {
$name = $crossSelling->getName();
if (false === array_key_exists($name, $filtered ?? [])) {
$filtered[$name] = $crossSelling;
}
}
return new ProductCrossSellingCollection($filtered ?? []);
}
public function getRandomCrossSelling(ProductCrossSellingCollection $crossSellingCollection): array
{
$result = [];
$crossSellingElements = $crossSellingCollection->getElements();
$randomValues = array_rand($crossSellingElements, static::CROSS_SELLING_LIMIT);
foreach ($randomValues as $randomValue) {
$result[] = $crossSellingElements[$randomValue];
}
return $result;
}
}