<?php
declare(strict_types=1);
namespace LDSCustom\Subscriber;
use Shopware\Core\Content\Product\ProductEvents;
use Shopware\Storefront\Page\Product\ProductPageLoadedEvent;
use Shopware\Core\Framework\DataAbstractionLayer\Event\EntityLoadedEvent;
use Shopware\Core\System\SalesChannel\Entity\SalesChannelEntityLoadedEvent;
use Shopware\Core\Checkout\Cart\LineItem\LineItem;
use Shopware\Core\Content\Product\Cart\ProductLineItemFactory;
use Shopware\Core\Checkout\Cart\SalesChannel\CartService;
use Shopware\Core\System\SalesChannel\SalesChannelContext;
use Shopware\Core\Checkout\Cart\Cart;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Shopware\Core\Framework\Struct\ArrayEntity;
use Shopware\Core\Framework\Util\Random;
use Shopware\Core\Checkout\Cart\CartBehavior;
use Shopware\Core\Checkout\Cart\Delivery\Struct\DeliveryInformation;
use Shopware\Core\Checkout\Cart\Delivery\Struct\DeliveryTime;
use Shopware\Core\Checkout\Cart\LineItem\QuantityInformation;
use Shopware\Core\Checkout\Cart\Price\Struct\CalculatedPrice;
use Shopware\Core\Checkout\Cart\Price\Struct\QuantityPriceDefinition;
use Shopware\Core\Defaults;
use Shopware\Core\Checkout\Cart\Processor;
use Shopware\Core\Checkout\Cart\CartDataCollectorInterface;
use Shopware\Core\Checkout\Cart\CartProcessorInterface;
use Shopware\Core\Checkout\Promotion\Cart\PromotionProcessor;
use Shopware\Core\Checkout\Cart\Price\Struct\ReferencePriceDefinition;
use Shopware\Core\Checkout\Cart\Price\AmountCalculator;
use Shopware\Core\Checkout\Cart\CartRuleLoader;
class ProductSubscriber implements EventSubscriberInterface
{
private ProductLineItemFactory $factory;
/**
* @var CartDataCollectorInterface
*/
private $promotionCollector;
/**
* @var CartProcessorInterface
*/
private $promotionProcessor;
/**
* @var AmountCalculator
*/
private $amountCalculator;
private CartRuleLoader $cartRuleLoader;
/**
* @var array|null
*/
private $promotionsAuto;
public function __construct(
ProductLineItemFactory $factory,
CartDataCollectorInterface $promotionCollector,
CartProcessorInterface $promotionProcessor,
AmountCalculator $amountCalculator,
CartRuleLoader $cartRuleLoader
) {
$this->factory = $factory;
$this->promotionCollector = $promotionCollector;
$this->promotionProcessor = $promotionProcessor;
$this->amountCalculator = $amountCalculator;
$this->cartRuleLoader = $cartRuleLoader;
}
public static function getSubscribedEvents()
{
return [
ProductPageLoadedEvent::class => ['loaded', -50],
];
}
public function loaded(ProductPageLoadedEvent $event): void
{
$salesChannelContext = $event->getSalesChannelContext();
$page = $event->getPage();
$product = $page->getProduct();
$behavior = new CartBehavior($salesChannelContext->getPermissions());
$cart = new Cart($salesChannelContext->getSalesChannel()->getTypeId(), Random::getAlphanumericString(32));
$cart->add($this->CreateLineItem($product, $behavior));
$amount = $this->amountCalculator->calculate(
$cart->getLineItems()->getPrices(),
$cart->getDeliveries()->getShippingCosts(),
$salesChannelContext
);
$cart->setPrice($amount);
$this->promotionProcessor->process($cart->getData(), $cart, $cart, $salesChannelContext, $behavior);
$this->promotionCollector->collect($cart->getData(), $cart, $salesChannelContext, $behavior);
$validated = $this->cartRuleLoader->loadByCart($salesChannelContext, $cart, $behavior);
$cart = $validated->getCart();
$salesChannelContext->addExtension('promotions', $cart);
}
private function CreateLineItem($product, $behavior): LineItem
{
$lineItem = $this->factory->create($product->getId(), ['quantity' => 1]);
$lineItem->setPrice($product->getCalculatedPrice());
$label = trim($lineItem->getLabel() ?? '');
// set the label if its empty or the context does not have the permission to overwrite it
if ($label === '' || !$behavior->hasPermission(ProductCartProcessor::ALLOW_PRODUCT_LABEL_OVERWRITES)) {
$lineItem->setLabel($product->getTranslation('name'));
}
if ($product->getCover()) {
$lineItem->setCover($product->getCover()->getMedia());
}
/* DeliveryTime */
$deliveryTime = null;
if ($product->getDeliveryTime() !== null) {
$deliveryTime = DeliveryTime::createFromEntity($product->getDeliveryTime());
}
$lineItem->setDeliveryInformation(
new DeliveryInformation(
(int) $product->getAvailableStock(),
(float) $product->getWeight(),
$product->getShippingFree() === true,
$product->getRestockTime(),
$deliveryTime,
$product->getHeight(),
$product->getWidth(),
$product->getLength()
)
);
$lineItem->setPriceDefinition(
$this->getPriceDefinition($product, $lineItem->getQuantity())
);
$quantityInformation = new QuantityInformation();
$quantityInformation->setMinPurchase(
$product->getMinPurchase() ?? 1
);
$quantityInformation->setMaxPurchase(
$product->getCalculatedMaxPurchase()
);
$quantityInformation->setPurchaseSteps(
$product->getPurchaseSteps() ?? 1
);
$lineItem->setQuantityInformation($quantityInformation);
$purchasePrices = null;
$purchasePricesCollection = $product->getPurchasePrices();
if ($purchasePricesCollection !== null) {
$purchasePrices = $purchasePricesCollection->getCurrencyPrice(Defaults::CURRENCY);
}
$payload = [
'isCloseout' => $product->getIsCloseout(),
'customFields' => $product->getCustomFields(),
'createdAt' => $product->getCreatedAt()->format(Defaults::STORAGE_DATE_TIME_FORMAT),
'releaseDate' => $product->getReleaseDate() ? $product->getReleaseDate()->format(Defaults::STORAGE_DATE_TIME_FORMAT) : null,
'isNew' => $product->isNew(),
'markAsTopseller' => $product->getMarkAsTopseller(),
'purchasePrices' => $purchasePrices ? json_encode($purchasePrices) : null,
'productNumber' => $product->getProductNumber(),
'manufacturerId' => $product->getManufacturerId(),
'taxId' => $product->getTaxId(),
'tagIds' => $product->getTagIds(),
'categoryIds' => $product->getCategoryTree(),
'propertyIds' => $product->getPropertyIds(),
'optionIds' => $product->getOptionIds(),
'streamIds' => $product->getStreamIds(),
'parentId' => $product->getParentId(),
'stock' => $product->getStock(),
];
$payload['options'] = $product->getVariation();
$lineItem->replacePayload($payload);
return $lineItem;
}
private function getPriceDefinition($product, int $quantity): QuantityPriceDefinition
{
// we don't need to recalculate the product - it is already calculated when we get it from the listing
if ($product->getCalculatedPrices()->count() === 0) {
return $this->buildPriceDefinition($product->getCalculatedPrice(), $quantity);
}
// keep loop reference to $price variable to get last quantity price in case of "null"
$price = $product->getCalculatedPrice();
foreach ($product->getCalculatedPrices() as $price) {
if ($quantity <= $price->getQuantity()) {
break;
}
}
return $this->buildPriceDefinition($price, $quantity);
}
private function buildPriceDefinition(CalculatedPrice $price, int $quantity): QuantityPriceDefinition
{
$definition = new QuantityPriceDefinition($price->getUnitPrice(), $price->getTaxRules(), $quantity);
if ($price->getListPrice() !== null) {
$definition->setListPrice($price->getListPrice()->getPrice());
}
if ($price->getReferencePrice() !== null) {
$definition->setReferencePriceDefinition(
new ReferencePriceDefinition(
$price->getReferencePrice()->getPurchaseUnit(),
$price->getReferencePrice()->getReferenceUnit(),
$price->getReferencePrice()->getUnitName()
)
);
}
return $definition;
}
}