custom/plugins/NetiNextStoreLocator/src/Subscriber/CheckoutFinishPageSubscriber.php line 64

Open in your IDE?
  1. <?php
  2. declare(strict_types=1);
  3. namespace NetInventors\NetiNextStoreLocator\Subscriber;
  4. use Doctrine\DBAL\Connection;
  5. use NetInventors\NetiNextStoreLocator\Components\GeoLocation\GeoLocation;
  6. use NetInventors\NetiNextStoreLocator\Components\GeoLocation\Struct\Address;
  7. use NetInventors\NetiNextStoreLocator\Components\GeoLocation\Struct\Coordinates;
  8. use NetInventors\NetiNextStoreLocator\Core\Content\Store\StoreEntity;
  9. use NetInventors\NetiNextStoreLocator\Service\PluginConfig;
  10. use NetInventors\NetiNextStoreLocator\Struct\CheckoutFinishStoresStruct;
  11. use NetInventors\NetiNextStoreLocator\Struct\StoreDistanceStruct;
  12. use Psr\Cache\CacheItemPoolInterface;
  13. use Psr\Log\LoggerInterface;
  14. use Shopware\Core\Checkout\Order\Aggregate\OrderDelivery\OrderDeliveryCollection;
  15. use Shopware\Core\Checkout\Order\Aggregate\OrderDelivery\OrderDeliveryEntity;
  16. use Shopware\Core\Checkout\Order\OrderEntity;
  17. use Shopware\Core\Framework\Context;
  18. use Shopware\Core\Framework\DataAbstractionLayer\EntityRepositoryInterface;
  19. use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
  20. use Shopware\Core\System\SalesChannel\SalesChannelContext;
  21. use Shopware\Storefront\Page\Checkout\Finish\CheckoutFinishPageLoadedEvent;
  22. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  23. class CheckoutFinishPageSubscriber implements EventSubscriberInterface
  24. {
  25.     private PluginConfig              $pluginConfig;
  26.     private GeoLocation               $geoLocation;
  27.     private Connection                $connection;
  28.     private EntityRepositoryInterface $storeRepository;
  29.     private LoggerInterface           $logger;
  30.     private CacheItemPoolInterface    $cache;
  31.     public function __construct(
  32.         PluginConfig              $pluginConfig,
  33.         GeoLocation               $geoLocation,
  34.         Connection                $connection,
  35.         EntityRepositoryInterface $storeRepository,
  36.         LoggerInterface           $logger,
  37.         CacheItemPoolInterface    $cache
  38.     ) {
  39.         $this->pluginConfig    $pluginConfig;
  40.         $this->geoLocation     $geoLocation;
  41.         $this->connection      $connection;
  42.         $this->storeRepository $storeRepository;
  43.         $this->logger          $logger;
  44.         $this->cache           $cache;
  45.     }
  46.     public static function getSubscribedEvents(): array
  47.     {
  48.         return [
  49.             CheckoutFinishPageLoadedEvent::class => 'onCheckoutFinishPageLoaded',
  50.         ];
  51.     }
  52.     public function onCheckoutFinishPageLoaded(CheckoutFinishPageLoadedEvent $event): void
  53.     {
  54.         if (
  55.             false === $this->pluginConfig->isActive()
  56.             || false === $this->pluginConfig->isShowStoresOnOrderFinish()
  57.         ) {
  58.             return;
  59.         }
  60.         $page  $event->getPage();
  61.         $order $page->getOrder();
  62.         try {
  63.             $coordinates $this->getCoordinates($order);
  64.             if ($coordinates instanceof Coordinates) {
  65.                 $stores $this->getNearestStores($coordinates$event->getSalesChannelContext());
  66.                 $struct = new CheckoutFinishStoresStruct($stores);
  67.                 $page->addExtension('netiStores'$struct);
  68.             }
  69.         } catch (\Exception $ex) {
  70.             $this->logger->error(
  71.                 'Unable to fetch nearest stores for the order finish page',
  72.                 [
  73.                     'message' => $ex->getMessage(),
  74.                 ]
  75.             );
  76.         }
  77.     }
  78.     private function getNearestStores(Coordinates $coordinatesSalesChannelContext $salesChannelContext): array
  79.     {
  80.         $context $salesChannelContext->getContext();
  81.         $limit   max(0$this->pluginConfig->getOrderFinishStoreCount());
  82.         $sql     "
  83.             SELECT 
  84.               LOWER(HEX(s.id)) AS id,
  85.               (
  86.                 :unit * ACOS(COS(RADIANS(:lat)) * COS(RADIANS(s.latitude)) * 
  87.                 COS(RADIANS(s.longitude) - RADIANS(:lng)) + SIN(RADIANS(:lat)) * SIN(RADIANS(s.latitude)))
  88.               ) AS distance
  89.             FROM neti_store_locator s
  90.             LEFT JOIN neti_store_sales_channel c ON c.store_id = s.id
  91.             WHERE active = 1
  92.               AND HEX(c.sales_channel_id) = :salesChannelId
  93.               AND s.latitude IS NOT NULL
  94.               AND s.longitude IS NOT NULL
  95.             ORDER BY distance ASC
  96.             LIMIT $limit
  97.         ";
  98.         $stores $this->connection->fetchAllKeyValue(
  99.             $sql,
  100.             [
  101.                 'unit'           => $this->pluginConfig->getDistanceUnit() === 'km' 6371 3959,
  102.                 'lat'            => $coordinates->getLatitude(),
  103.                 'lng'            => $coordinates->getLongitude(),
  104.                 'salesChannelId' => $salesChannelContext->getSalesChannelId(),
  105.             ]
  106.         );
  107.         /**
  108.          * @psalm-suppress MixedArgumentTypeCoercion
  109.          *
  110.          * This is the correct way to search for a IDs
  111.          */
  112.         $criteria = new Criteria(array_keys($stores));
  113.         $result   $this->storeRepository->search($criteria$context)->getElements();
  114.         /** @var StoreEntity $store */
  115.         foreach ($result as $store) {
  116.             $distanceStruct = new StoreDistanceStruct((float)$stores[$store->getId()]);
  117.             $store->addExtension('netiDistance'$distanceStruct);
  118.         }
  119.         return $result;
  120.     }
  121.     private function getCoordinates(OrderEntity $order): ?Coordinates
  122.     {
  123.         $address null;
  124.         if ($this->pluginConfig->getOrderFinishAddressType() === PluginConfig::ORDER_FINISH_ADDRESS_TYPE_SHIPPING) {
  125.             $delivery   null;
  126.             $deliveries $order->getDeliveries();
  127.             if ($deliveries instanceof OrderDeliveryCollection) {
  128.                 /** @var null|OrderDeliveryEntity $delivery */
  129.                 $delivery $deliveries->first();
  130.             }
  131.             if ($delivery instanceof OrderDeliveryEntity) {
  132.                 $address $delivery->getShippingOrderAddress();
  133.             }
  134.         } else {
  135.             $address $order->getBillingAddress();
  136.         }
  137.         if (null === $address) {
  138.             return null;
  139.         }
  140.         $cacheKey 'neti_store_locator_address_coordinates_' $address->getId();
  141.         $item     $this->cache->getItem($cacheKey);
  142.         if ($item->isHit()) {
  143.             /** @var Coordinates|mixed $coordinates */
  144.             $coordinates $item->get();
  145.             if ($coordinates instanceof Coordinates) {
  146.                 return $coordinates;
  147.             }
  148.         }
  149.         $addressStruct = new Address();
  150.         $addressStruct->setStreet($address->getStreet());
  151.         $addressStruct->setZipCode($address->getZipcode());
  152.         $addressStruct->setCity($address->getCity());
  153.         $addressStruct->setCountryId($address->getCountryId());
  154.         $coordinates $this->geoLocation->getCoords($addressStruct);
  155.         $item->set($coordinates);
  156.         $this->cache->save($item);
  157.         return $coordinates;
  158.     }
  159. }