custom/plugins/NetiNextEasyCoupon/src/Subscriber/ProductExtensionLoader.php line 217

Open in your IDE?
  1. <?php
  2. declare(strict_types=1);
  3. namespace NetInventors\NetiNextEasyCoupon\Subscriber;
  4. use NetInventors\NetiNextEasyCoupon\Core\Content\Product\Aggregate\EasyCouponProductEntity;
  5. use NetInventors\NetiNextEasyCoupon\Core\Content\Product\Extension\ExtraOption\EcProductExtraOptionCollection;
  6. use NetInventors\NetiNextEasyCoupon\Core\Content\Product\Extension\ExtraOption\EcProductExtraOptionEntity;
  7. use NetInventors\NetiNextEasyCoupon\Service\ExtraOptionsService;
  8. use NetInventors\NetiNextEasyCoupon\Struct\LineItemStruct;
  9. use NetInventors\NetiNextEasyCoupon\Struct\PluginConfigStruct;
  10. use Shopware\Core\Checkout\Cart\Event\BeforeLineItemAddedEvent;
  11. use Shopware\Core\Checkout\Cart\LineItem\LineItem;
  12. use Shopware\Core\Content\Product\Events\ProductCrossSellingIdsCriteriaEvent;
  13. use Shopware\Core\Content\Product\Events\ProductListingCriteriaEvent;
  14. use Shopware\Core\Content\Product\Events\ProductSearchCriteriaEvent;
  15. use Shopware\Core\Content\Product\ProductEntity;
  16. use Shopware\Core\Content\Product\ProductEvents;
  17. use Shopware\Core\Content\Product\SalesChannel\SalesChannelProductEntity;
  18. use Shopware\Core\Framework\DataAbstractionLayer\EntityRepositoryInterface;
  19. use Shopware\Core\Framework\DataAbstractionLayer\Event\EntityLoadedEvent;
  20. use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
  21. use Shopware\Core\Framework\Uuid\Uuid;
  22. use Shopware\Core\System\SalesChannel\Entity\SalesChannelEntitySearchResultLoadedEvent;
  23. use Shopware\Storefront\Page\Product\ProductPageCriteriaEvent;
  24. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  25. use Symfony\Contracts\EventDispatcher\Event;
  26. class ProductExtensionLoader implements EventSubscriberInterface
  27. {
  28.     private PluginConfigStruct        $pluginConfig;
  29.     private EntityRepositoryInterface $productRepository;
  30.     private ExtraOptionsService       $extraOptionsService;
  31.     public function __construct(
  32.         PluginConfigStruct        $pluginConfig,
  33.         EntityRepositoryInterface $productRepository,
  34.         ExtraOptionsService       $extraOptionsService
  35.     ) {
  36.         $this->pluginConfig        $pluginConfig;
  37.         $this->productRepository   $productRepository;
  38.         $this->extraOptionsService $extraOptionsService;
  39.     }
  40.     public static function getSubscribedEvents(): array
  41.     {
  42.         return [
  43.             ProductPageCriteriaEvent::class              => 'onProductCriteriaEvent',
  44.             ProductListingCriteriaEvent::class           => 'onProductCriteriaEvent',
  45.             ProductSearchCriteriaEvent::class            => 'onProductCriteriaEvent',
  46.             ProductCrossSellingIdsCriteriaEvent::class   => 'onProductCriteriaEvent',
  47.             ProductEvents::PRODUCT_LOADED_EVENT          => 'onProductLoaded',
  48.             BeforeLineItemAddedEvent::class              => 'beforeLineItemAdded',
  49.             'sales_channel.product.search.result.loaded' => 'addCalculatedPrices',
  50.         ];
  51.     }
  52.     public function addCalculatedPrices(SalesChannelEntitySearchResultLoadedEvent $event): void
  53.     {
  54.         $extraOptions = new EcProductExtraOptionCollection();
  55.         $products     $event->getResult()->getEntities();
  56.         /** @var SalesChannelProductEntity $product */
  57.         foreach ($products->getElements() as $product) {
  58.             /** @var EasyCouponProductEntity|null $ecProductExtension */
  59.             $ecProductExtension $product->getExtension('netiEasyCouponProduct');
  60.             if (!$ecProductExtension) {
  61.                 continue;
  62.             }
  63.             /** @var EcProductExtraOptionCollection|null $extraOptionExtension */
  64.             $extraOptionExtension $ecProductExtension->getExtension('extraOptions');
  65.             if (!$extraOptionExtension) {
  66.                 continue;
  67.             }
  68.             foreach ($extraOptionExtension->getElements() as $extraOption) {
  69.                 if (!$extraOption->isActive()) {
  70.                     continue;
  71.                 }
  72.                 $extraOptions->add($extraOption);
  73.             }
  74.         }
  75.         if (=== $extraOptions->count()) {
  76.             return;
  77.         }
  78.         $prices $this->extraOptionsService->getOptionPrices($extraOptions$event->getSalesChannelContext());
  79.         foreach ($extraOptions->getElements() as $extraOption) {
  80.             $extraOptionId $extraOption->getId();
  81.             $optionPrice   $prices[$extraOptionId];
  82.             if (isset($optionPrice['displayPrice']) && \is_numeric($optionPrice['displayPrice'])) {
  83.                 $extraOption->setCalculatedPrice((float) $optionPrice['displayPrice']);
  84.             }
  85.             if (isset($optionPrice['listPrice']) && \is_numeric($optionPrice['listPrice'])) {
  86.                 $extraOption->setListPrice((float) $optionPrice['listPrice']);
  87.             }
  88.             if (isset($optionPrice['regulationPrice']) && \is_numeric($optionPrice['regulationPrice'])) {
  89.                 $extraOption->setRegulationPrice((float) $optionPrice['regulationPrice']);
  90.             }
  91.         }
  92.     }
  93.     /**
  94.      * @param BeforeLineItemAddedEvent $event
  95.      *
  96.      * @return void
  97.      * @throws \Exception
  98.      */
  99.     public function beforeLineItemAdded(BeforeLineItemAddedEvent $event): void
  100.     {
  101.         if (!$this->pluginConfig->isActive()) {
  102.             return;
  103.         }
  104.         $lineItem $event->getLineItem();
  105.         if (LineItem::PRODUCT_LINE_ITEM_TYPE !== $lineItem->getType()) {
  106.             return;
  107.         }
  108.         if ($lineItem->hasChildren()) {
  109.             // The event is also triggered on login.
  110.             // At that point the line item children have already been added, so we do not need to do this a second time.
  111.             return;
  112.         }
  113.         /** @psalm-suppress MixedAssignment - It is pretty obvious, that $payload should be an array */
  114.         $payload $lineItem->getPayloadValue(LineItemStruct::PAYLOAD_NAME);
  115.         if (!is_array($payload)) {
  116.             $payload = [];
  117.         }
  118.         $criteria = new Criteria([ $lineItem->getReferencedId() ]);
  119.         $criteria->addAssociation('netiEasyCouponProduct.extraOptions');
  120.         $product $this->productRepository->search($criteria$event->getContext())->first();
  121.         if (!$product instanceof ProductEntity) {
  122.             return;
  123.         }
  124.         /** @var EasyCouponProductEntity|null $ecProductExtension */
  125.         $ecProductExtension $product->getExtension('netiEasyCouponProduct');
  126.         if (!$ecProductExtension instanceof EasyCouponProductEntity) {
  127.             return;
  128.         }
  129.         /** @var EcProductExtraOptionCollection|null $extras */
  130.         $extras $ecProductExtension->getExtension('extraOptions');
  131.         if (!$extras instanceof EcProductExtraOptionCollection) {
  132.             return;
  133.         }
  134.         /** @var array<string, string> $extraOptions */
  135.         $extraOptions $payload['extraOption'] ?? [];
  136.         foreach ($extras->getElements() as $extra) {
  137.             if (
  138.                 (
  139.                     EcProductExtraOptionEntity::SELECTION_TYPE_PRESELECTED_AND_UNCHANGEABLE
  140.                     === $extra->getSelectionType()
  141.                     && $extra->isActive()
  142.                 )
  143.                 || isset($extraOptions[$extra->getId()])
  144.             ) {
  145.                 $extraOptions[$extra->getId()] = $extra->getPosition();
  146.             }
  147.         }
  148.         if ([] !== $extraOptions) {
  149.             $lineItem->addChild($this->createVoucherChildLineItem($lineItem));
  150.         }
  151.         \asort($extraOptions);
  152.         foreach ($extraOptions as $id => $_) {
  153.             $extraOption $extras->get($id);
  154.             if (!$extraOption instanceof EcProductExtraOptionEntity) {
  155.                 throw new \Exception('EasyCoupon extra option data missing');
  156.             }
  157.             $lineItem->addChild($this->createExtraOptionLineItem($lineItem$extraOption));
  158.         }
  159.         $payload['extraOption'] = $extraOptions;
  160.         $lineItem->setPayloadValue(LineItemStruct::PAYLOAD_NAME$payload);
  161.     }
  162.     /**
  163.      * @param ProductPageCriteriaEvent|ProductListingCriteriaEvent|ProductSearchCriteriaEvent $event
  164.      *
  165.      * @return void
  166.      */
  167.     public function onProductCriteriaEvent(Event $event): void
  168.     {
  169.         if (!$this->pluginConfig->isActive()) {
  170.             return;
  171.         }
  172.         $event->getCriteria()->addAssociation('netiEasyCouponProduct.extraOptions');
  173.     }
  174.     public function onProductLoaded(EntityLoadedEvent $event): void
  175.     {
  176.         foreach ($event->getEntities() as $product) {
  177.             if (!$product instanceof SalesChannelProductEntity) {
  178.                 continue;
  179.             }
  180.             $productExtention $product->getExtension('netiEasyCouponProduct');
  181.             if (!$productExtention instanceof EasyCouponProductEntity) {
  182.                 continue;
  183.             }
  184.             $optionsExtension $productExtention->getExtension('extraOptions');
  185.             if (!$optionsExtension instanceof EcProductExtraOptionCollection) {
  186.                 continue;
  187.             }
  188.             foreach ($optionsExtension->getElements() as $extraOption) {
  189.                 if (!$extraOption->isActive()) {
  190.                     $optionsExtension->remove($extraOption->getId());
  191.                 }
  192.             }
  193.         }
  194.     }
  195.     protected function createExtraOptionLineItem(
  196.         LineItem                   $parentLineItem,
  197.         EcProductExtraOptionEntity $extraOption
  198.     ): LineItem {
  199.         $lineItem = new LineItem(Uuid::randomHex(), ''$extraOption->getId());
  200.         $lineItem->setRemovable(false)
  201.             ->setStackable(true)
  202.             ->setQuantity($parentLineItem->getQuantity())
  203.             ->setGood(false)
  204.             ->setPayloadValue('customFields'null)
  205.             ->setPayloadValue('shippingOption'$extraOption->getShippingType())
  206.             ->setType(LineItemStruct::EXTRA_OPTION);
  207.         $translated $extraOption->getTranslated();
  208.         if (isset($translated['label']) && is_string($translated['label'])) {
  209.             $lineItem->setLabel($translated['label']);
  210.         }
  211.         if ($extraOption->getOptionType() === EcProductExtraOptionEntity::OPTION_TYPE_POSTAL) {
  212.             $lineItem->setType(LineItemStruct::EXTRA_OPTION_POSTAL);
  213.         }
  214.         return $lineItem;
  215.     }
  216.     protected function createVoucherChildLineItem(
  217.         LineItem $parentLineItem
  218.     ): LineItem {
  219.         $voucherChild = new LineItem(Uuid::randomHex(), ''$parentLineItem->getId());
  220.         $voucherChild->setRemovable(false)
  221.             ->setStackable(true)
  222.             ->setQuantity($parentLineItem->getQuantity())
  223.             ->setGood(false)
  224.             ->setLabel('voucher')
  225.             ->setType(LineItemStruct::EXTRA_OPTION_VOUCHER);
  226.         $payload $voucherChild->getPayload();
  227.         foreach ($payload as $payloadName => $_) {
  228.             /** @var string $payloadName */
  229.             $voucherChild->removePayloadValue($payloadName);
  230.         }
  231.         return $voucherChild;
  232.     }
  233. }