<?php
declare(strict_types=1);
namespace NetInventors\NetiNextEasyCoupon\Subscriber;
use NetInventors\NetiNextEasyCoupon\Core\Content\Product\Aggregate\EasyCouponProductEntity;
use NetInventors\NetiNextEasyCoupon\Core\Content\Product\Extension\ExtraOption\EcProductExtraOptionCollection;
use NetInventors\NetiNextEasyCoupon\Core\Content\Product\Extension\ExtraOption\EcProductExtraOptionEntity;
use NetInventors\NetiNextEasyCoupon\Service\ExtraOptionsService;
use NetInventors\NetiNextEasyCoupon\Struct\LineItemStruct;
use NetInventors\NetiNextEasyCoupon\Struct\PluginConfigStruct;
use Shopware\Core\Checkout\Cart\Event\BeforeLineItemAddedEvent;
use Shopware\Core\Checkout\Cart\LineItem\LineItem;
use Shopware\Core\Content\Product\Events\ProductCrossSellingIdsCriteriaEvent;
use Shopware\Core\Content\Product\Events\ProductListingCriteriaEvent;
use Shopware\Core\Content\Product\Events\ProductSearchCriteriaEvent;
use Shopware\Core\Content\Product\ProductEntity;
use Shopware\Core\Content\Product\ProductEvents;
use Shopware\Core\Content\Product\SalesChannel\SalesChannelProductEntity;
use Shopware\Core\Framework\DataAbstractionLayer\EntityRepositoryInterface;
use Shopware\Core\Framework\DataAbstractionLayer\Event\EntityLoadedEvent;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
use Shopware\Core\Framework\Uuid\Uuid;
use Shopware\Core\System\SalesChannel\Entity\SalesChannelEntitySearchResultLoadedEvent;
use Shopware\Storefront\Page\Product\ProductPageCriteriaEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Contracts\EventDispatcher\Event;
class ProductExtensionLoader implements EventSubscriberInterface
{
private PluginConfigStruct $pluginConfig;
private EntityRepositoryInterface $productRepository;
private ExtraOptionsService $extraOptionsService;
public function __construct(
PluginConfigStruct $pluginConfig,
EntityRepositoryInterface $productRepository,
ExtraOptionsService $extraOptionsService
) {
$this->pluginConfig = $pluginConfig;
$this->productRepository = $productRepository;
$this->extraOptionsService = $extraOptionsService;
}
public static function getSubscribedEvents(): array
{
return [
ProductPageCriteriaEvent::class => 'onProductCriteriaEvent',
ProductListingCriteriaEvent::class => 'onProductCriteriaEvent',
ProductSearchCriteriaEvent::class => 'onProductCriteriaEvent',
ProductCrossSellingIdsCriteriaEvent::class => 'onProductCriteriaEvent',
ProductEvents::PRODUCT_LOADED_EVENT => 'onProductLoaded',
BeforeLineItemAddedEvent::class => 'beforeLineItemAdded',
'sales_channel.product.search.result.loaded' => 'addCalculatedPrices',
];
}
public function addCalculatedPrices(SalesChannelEntitySearchResultLoadedEvent $event): void
{
$extraOptions = new EcProductExtraOptionCollection();
$products = $event->getResult()->getEntities();
/** @var SalesChannelProductEntity $product */
foreach ($products->getElements() as $product) {
/** @var EasyCouponProductEntity|null $ecProductExtension */
$ecProductExtension = $product->getExtension('netiEasyCouponProduct');
if (!$ecProductExtension) {
continue;
}
/** @var EcProductExtraOptionCollection|null $extraOptionExtension */
$extraOptionExtension = $ecProductExtension->getExtension('extraOptions');
if (!$extraOptionExtension) {
continue;
}
foreach ($extraOptionExtension->getElements() as $extraOption) {
if (!$extraOption->isActive()) {
continue;
}
$extraOptions->add($extraOption);
}
}
if (0 === $extraOptions->count()) {
return;
}
$prices = $this->extraOptionsService->getOptionPrices($extraOptions, $event->getSalesChannelContext());
foreach ($extraOptions->getElements() as $extraOption) {
$extraOptionId = $extraOption->getId();
$optionPrice = $prices[$extraOptionId];
if (isset($optionPrice['displayPrice']) && \is_numeric($optionPrice['displayPrice'])) {
$extraOption->setCalculatedPrice((float) $optionPrice['displayPrice']);
}
if (isset($optionPrice['listPrice']) && \is_numeric($optionPrice['listPrice'])) {
$extraOption->setListPrice((float) $optionPrice['listPrice']);
}
if (isset($optionPrice['regulationPrice']) && \is_numeric($optionPrice['regulationPrice'])) {
$extraOption->setRegulationPrice((float) $optionPrice['regulationPrice']);
}
}
}
/**
* @param BeforeLineItemAddedEvent $event
*
* @return void
* @throws \Exception
*/
public function beforeLineItemAdded(BeforeLineItemAddedEvent $event): void
{
if (!$this->pluginConfig->isActive()) {
return;
}
$lineItem = $event->getLineItem();
if (LineItem::PRODUCT_LINE_ITEM_TYPE !== $lineItem->getType()) {
return;
}
if ($lineItem->hasChildren()) {
// The event is also triggered on login.
// At that point the line item children have already been added, so we do not need to do this a second time.
return;
}
/** @psalm-suppress MixedAssignment - It is pretty obvious, that $payload should be an array */
$payload = $lineItem->getPayloadValue(LineItemStruct::PAYLOAD_NAME);
if (!is_array($payload)) {
$payload = [];
}
$criteria = new Criteria([ $lineItem->getReferencedId() ]);
$criteria->addAssociation('netiEasyCouponProduct.extraOptions');
$product = $this->productRepository->search($criteria, $event->getContext())->first();
if (!$product instanceof ProductEntity) {
return;
}
/** @var EasyCouponProductEntity|null $ecProductExtension */
$ecProductExtension = $product->getExtension('netiEasyCouponProduct');
if (!$ecProductExtension instanceof EasyCouponProductEntity) {
return;
}
/** @var EcProductExtraOptionCollection|null $extras */
$extras = $ecProductExtension->getExtension('extraOptions');
if (!$extras instanceof EcProductExtraOptionCollection) {
return;
}
/** @var array<string, string> $extraOptions */
$extraOptions = $payload['extraOption'] ?? [];
foreach ($extras->getElements() as $extra) {
if (
(
EcProductExtraOptionEntity::SELECTION_TYPE_PRESELECTED_AND_UNCHANGEABLE
=== $extra->getSelectionType()
&& $extra->isActive()
)
|| isset($extraOptions[$extra->getId()])
) {
$extraOptions[$extra->getId()] = $extra->getPosition();
}
}
if ([] !== $extraOptions) {
$lineItem->addChild($this->createVoucherChildLineItem($lineItem));
}
\asort($extraOptions);
foreach ($extraOptions as $id => $_) {
$extraOption = $extras->get($id);
if (!$extraOption instanceof EcProductExtraOptionEntity) {
throw new \Exception('EasyCoupon extra option data missing');
}
$lineItem->addChild($this->createExtraOptionLineItem($lineItem, $extraOption));
}
$payload['extraOption'] = $extraOptions;
$lineItem->setPayloadValue(LineItemStruct::PAYLOAD_NAME, $payload);
}
/**
* @param ProductPageCriteriaEvent|ProductListingCriteriaEvent|ProductSearchCriteriaEvent $event
*
* @return void
*/
public function onProductCriteriaEvent(Event $event): void
{
if (!$this->pluginConfig->isActive()) {
return;
}
$event->getCriteria()->addAssociation('netiEasyCouponProduct.extraOptions');
}
public function onProductLoaded(EntityLoadedEvent $event): void
{
foreach ($event->getEntities() as $product) {
if (!$product instanceof SalesChannelProductEntity) {
continue;
}
$productExtention = $product->getExtension('netiEasyCouponProduct');
if (!$productExtention instanceof EasyCouponProductEntity) {
continue;
}
$optionsExtension = $productExtention->getExtension('extraOptions');
if (!$optionsExtension instanceof EcProductExtraOptionCollection) {
continue;
}
foreach ($optionsExtension->getElements() as $extraOption) {
if (!$extraOption->isActive()) {
$optionsExtension->remove($extraOption->getId());
}
}
}
}
protected function createExtraOptionLineItem(
LineItem $parentLineItem,
EcProductExtraOptionEntity $extraOption
): LineItem {
$lineItem = new LineItem(Uuid::randomHex(), '', $extraOption->getId());
$lineItem->setRemovable(false)
->setStackable(true)
->setQuantity($parentLineItem->getQuantity())
->setGood(false)
->setPayloadValue('customFields', null)
->setPayloadValue('shippingOption', $extraOption->getShippingType())
->setType(LineItemStruct::EXTRA_OPTION);
$translated = $extraOption->getTranslated();
if (isset($translated['label']) && is_string($translated['label'])) {
$lineItem->setLabel($translated['label']);
}
if ($extraOption->getOptionType() === EcProductExtraOptionEntity::OPTION_TYPE_POSTAL) {
$lineItem->setType(LineItemStruct::EXTRA_OPTION_POSTAL);
}
return $lineItem;
}
protected function createVoucherChildLineItem(
LineItem $parentLineItem
): LineItem {
$voucherChild = new LineItem(Uuid::randomHex(), '', $parentLineItem->getId());
$voucherChild->setRemovable(false)
->setStackable(true)
->setQuantity($parentLineItem->getQuantity())
->setGood(false)
->setLabel('voucher')
->setType(LineItemStruct::EXTRA_OPTION_VOUCHER);
$payload = $voucherChild->getPayload();
foreach ($payload as $payloadName => $_) {
/** @var string $payloadName */
$voucherChild->removePayloadValue($payloadName);
}
return $voucherChild;
}
}