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

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