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

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