<?php
namespace System4ShopTheme\Subscriber;
use Shopware\Core\Checkout\Cart\LineItem\LineItem;
use Shopware\Core\Content\Product\SalesChannel\CrossSelling\CachedProductCrossSellingRoute;
use Shopware\Core\Framework\DataAbstractionLayer\EntityRepository;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Shopware\Storefront\Page\Checkout\Cart\CheckoutCartPageLoadedEvent;
use Symfony\Component\HttpFoundation\Request;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
use System4ShopTheme\Struct\ColorVariantCollection;
use System4ShopTheme\Struct\ColorVariantStruct;
class CartCrossSellingSubscriber implements EventSubscriberInterface
{
private CachedProductCrossSellingRoute $crossSellingLoader;
private EntityRepository $productRepository;
public function __construct(CachedProductCrossSellingRoute $crossSellingLoader, EntityRepository $productRepository)
{
$this->crossSellingLoader = $crossSellingLoader;
$this->productRepository = $productRepository;
}
public static function getSubscribedEvents(): array
{
return [
CheckoutCartPageLoadedEvent::class => 'onCartPageLoaded',
];
}
public function onCartPageLoaded(CheckoutCartPageLoadedEvent $event): void
{
$cart = $event->getPage()->getCart();
$salesChannelContext = $event->getSalesChannelContext();
$hasProducts = $cart->getLineItems()->filterType(LineItem::PRODUCT_LINE_ITEM_TYPE)->count() > 0;
if ($hasProducts) {
$products = $cart->getLineItems()->filterType(LineItem::PRODUCT_LINE_ITEM_TYPE)->getElements();
$reversedItems = array_reverse($products);
foreach ($reversedItems as $item) {
$productId = $item->getReferencedId();
$crossSellings = $this->crossSellingLoader
->load($productId, new Request(), $salesChannelContext, new Criteria())
->getResult();
if ($crossSellings->count() > 0) {
break;
}
}
if ($crossSellings->count()) {
$crossSellingProducts = $crossSellings->first()?->getProducts();
if ($crossSellingProducts) {
$limitedProducts = $crossSellingProducts->slice(0, 4);
foreach ($limitedProducts as $product) {
$criteria = new Criteria([$product->getParentId()]);
$criteria->addAssociation('options.group')
->addAssociation('children')
->addAssociation('children.options.group')
->addAssociation('children.seoUrls');
$parentProduct = $this->productRepository->search(
$criteria,
$event->getSalesChannelContext()->getContext()
)->first();
if (!$parentProduct) {
continue;
}
$colorVariants = $this->getColorVariants($parentProduct);
$product->addExtension('colorVariants', $colorVariants);
}
$event->getPage()->addExtension('cartCrossSellings', $limitedProducts);
}
}
}
}
private function getColorVariants($parentProduct): ColorVariantCollection
{
$colorVariants = new ColorVariantCollection();
$children = $parentProduct->getChildren();
$processedColors = []; // Track processed colors
if (!$children) {
return $colorVariants;
}
foreach ($children as $variant) {
$options = $variant->getOptions();
if (!$options) {
continue;
}
foreach ($options as $option) {
$group = $option->getGroup();
if (!$group) {
continue;
}
if (
($group->getDisplayType() === 'color' ||
strtolower($group->getTranslation('name')) === 'color')
&& $variant->getAvailable() && $variant->getActive() // Only add available variants
) {
// Create unique key based on color name and hex code
$colorKey = $option->getName() . '-' . $option->getColorHexCode();
// Skip if we've already processed this color
if (isset($processedColors[$colorKey])) {
continue;
}
$colorVariants->add(new ColorVariantStruct(
$variant->getId(),
$option->getTranslation('name'),
$option->getColorHexCode(),
$variant->getAvailable(),
$variant->getStock()
));
// Mark this color as processed
$processedColors[$colorKey] = true;
}
}
}
return $colorVariants;
}
}