vendor/thelia/core/lib/Thelia/Action/Coupon.php line 265

Open in your IDE?
  1. <?php
  2. /*
  3.  * This file is part of the Thelia package.
  4.  * http://www.thelia.net
  5.  *
  6.  * (c) OpenStudio <info@thelia.net>
  7.  *
  8.  * For the full copyright and license information, please view the LICENSE
  9.  * file that was distributed with this source code.
  10.  */
  11. namespace Thelia\Action;
  12. use Propel\Runtime\Propel;
  13. use Symfony\Component\EventDispatcher\EventDispatcherInterface;
  14. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  15. use Symfony\Component\HttpFoundation\RequestStack;
  16. use Symfony\Contracts\EventDispatcher\Event;
  17. use Thelia\Condition\ConditionCollection;
  18. use Thelia\Condition\ConditionFactory;
  19. use Thelia\Condition\Implementation\ConditionInterface;
  20. use Thelia\Condition\Implementation\MatchForEveryone;
  21. use Thelia\Core\Event\Coupon\CouponConsumeEvent;
  22. use Thelia\Core\Event\Coupon\CouponCreateOrUpdateEvent;
  23. use Thelia\Core\Event\Coupon\CouponDeleteEvent;
  24. use Thelia\Core\Event\Order\OrderEvent;
  25. use Thelia\Core\Event\TheliaEvents;
  26. use Thelia\Coupon\CouponFactory;
  27. use Thelia\Coupon\CouponManager;
  28. use Thelia\Coupon\Type\CouponInterface;
  29. use Thelia\Model\Coupon as CouponModel;
  30. use Thelia\Model\CouponCountry;
  31. use Thelia\Model\CouponCountryQuery;
  32. use Thelia\Model\CouponModule;
  33. use Thelia\Model\CouponModuleQuery;
  34. use Thelia\Model\CouponQuery;
  35. use Thelia\Model\Event\AddressEvent;
  36. use Thelia\Model\Map\OrderCouponTableMap;
  37. use Thelia\Model\OrderCoupon;
  38. use Thelia\Model\OrderCouponCountry;
  39. use Thelia\Model\OrderCouponModule;
  40. use Thelia\Model\OrderCouponQuery;
  41. /**
  42.  * Process Coupon Events.
  43.  *
  44.  * @author  Guillaume MOREL <gmorel@openstudio.fr>, Franck Allimant <franck@cqfdev.fr>
  45.  */
  46. class Coupon extends BaseAction implements EventSubscriberInterface
  47. {
  48.     /** @var RequestStack */
  49.     protected $requestStack;
  50.     /** @var CouponFactory */
  51.     protected $couponFactory;
  52.     /** @var CouponManager */
  53.     protected $couponManager;
  54.     /** @var ConditionInterface */
  55.     protected $noConditionRule;
  56.     /** @var ConditionFactory */
  57.     protected $conditionFactory;
  58.     public function __construct(
  59.         RequestStack $requestStack,
  60.         CouponFactory $couponFactory,
  61.         CouponManager $couponManager,
  62.         MatchForEveryone $noConditionRule,
  63.         ConditionFactory $conditionFactory
  64.     ) {
  65.         $this->requestStack $requestStack;
  66.         $this->couponFactory $couponFactory;
  67.         $this->couponManager $couponManager;
  68.         $this->noConditionRule $noConditionRule;
  69.         $this->conditionFactory $conditionFactory;
  70.     }
  71.     /**
  72.      * Occurring when a Coupon is about to be created.
  73.      *
  74.      * @param CouponCreateOrUpdateEvent $event Event creation or update Coupon
  75.      * @param $eventName
  76.      */
  77.     public function create(CouponCreateOrUpdateEvent $event$eventNameEventDispatcherInterface $dispatcher): void
  78.     {
  79.         $coupon = new CouponModel();
  80.         $this->createOrUpdate($coupon$event$dispatcher);
  81.     }
  82.     /**
  83.      * Occurring when a Coupon is about to be updated.
  84.      *
  85.      * @param CouponCreateOrUpdateEvent $event Event creation or update Coupon
  86.      * @param $eventName
  87.      */
  88.     public function update(CouponCreateOrUpdateEvent $event$eventNameEventDispatcherInterface $dispatcher): void
  89.     {
  90.         $coupon $event->getCouponModel();
  91.         $this->createOrUpdate($coupon$event$dispatcher);
  92.     }
  93.     public function delete(CouponDeleteEvent $event): void
  94.     {
  95.         $coupon $event->getCoupon();
  96.         if (null === $coupon) {
  97.             throw new \InvalidArgumentException(
  98.                 'The coupon should not be null'
  99.             );
  100.         }
  101.         $coupon->delete();
  102.         $event->setCoupon(null);
  103.     }
  104.     /**
  105.      * Occurring when a Coupon condition is about to be updated.
  106.      *
  107.      * @param CouponCreateOrUpdateEvent $event Event creation or update Coupon condition
  108.      * @param $eventName
  109.      */
  110.     public function updateCondition(CouponCreateOrUpdateEvent $event$eventNameEventDispatcherInterface $dispatcher): void
  111.     {
  112.         $modelCoupon $event->getCouponModel();
  113.         $this->createOrUpdateCondition($modelCoupon$event$dispatcher);
  114.     }
  115.     /**
  116.      * Clear all coupons in session.
  117.      *
  118.      * @param $eventName
  119.      */
  120.     public function clearAllCoupons(Event $event$eventNameEventDispatcherInterface $dispatcher): void
  121.     {
  122.         // Tell coupons to clear any data they may have stored
  123.         $this->couponManager->clear();
  124.         $this->getSession()->setConsumedCoupons([]);
  125.         $this->updateOrderDiscount($event$eventName$dispatcher);
  126.     }
  127.     /**
  128.      * Occurring when a Coupon condition is about to be consumed.
  129.      *
  130.      * @param CouponConsumeEvent $event Event consuming Coupon
  131.      * @param $eventName
  132.      */
  133.     public function consume(CouponConsumeEvent $event$eventNameEventDispatcherInterface $dispatcher): void
  134.     {
  135.         $totalDiscount 0;
  136.         $isValid false;
  137.         /** @var CouponInterface $coupon */
  138.         $coupon $this->couponFactory->buildCouponFromCode($event->getCode());
  139.         if ($coupon) {
  140.             $isValid $coupon->isMatching();
  141.             if ($isValid) {
  142.                 $this->couponManager->pushCouponInSession($event->getCode());
  143.                 $totalDiscount $this->couponManager->getDiscount();
  144.                 $this->getSession()
  145.                     ->getSessionCart($dispatcher)
  146.                     ->setDiscount($totalDiscount)
  147.                     ->save();
  148.                 $this->getSession()
  149.                     ->getOrder()
  150.                     ->setDiscount($totalDiscount)
  151.                 ;
  152.             }
  153.         }
  154.         $event->setIsValid($isValid);
  155.         $event->setDiscount($totalDiscount);
  156.     }
  157.     public function updateOrderDiscount(Event $event$eventNameEventDispatcherInterface $dispatcher): void
  158.     {
  159.         $discount $this->couponManager->getDiscount();
  160.         $this->getSession()
  161.             ->getSessionCart($dispatcher)
  162.             ->setDiscount($discount)
  163.             ->save();
  164.         $this->getSession()
  165.             ->getOrder()
  166.             ->setDiscount($discount);
  167.     }
  168.     /**
  169.      * Call the Model and delegate the create or delete action
  170.      * Feed the Event with the updated model.
  171.      *
  172.      * @param CouponModel               $coupon Model to save
  173.      * @param CouponCreateOrUpdateEvent $event  Event containing data
  174.      */
  175.     protected function createOrUpdate(CouponModel $couponCouponCreateOrUpdateEvent $eventEventDispatcherInterface $dispatcher): void
  176.     {
  177.         // Set default condition if none found
  178.         /** @var ConditionInterface $noConditionRule */
  179.         $noConditionRule $this->noConditionRule;
  180.         /** @var ConditionFactory $conditionFactory */
  181.         $conditionFactory $this->conditionFactory;
  182.         $couponRuleCollection = new ConditionCollection();
  183.         $couponRuleCollection[] = $noConditionRule;
  184.         $defaultSerializedRule $conditionFactory->serializeConditionCollection(
  185.             $couponRuleCollection
  186.         );
  187.         $coupon->createOrUpdate(
  188.             $event->getCode(),
  189.             $event->getTitle(),
  190.             $event->getEffects(),
  191.             $event->getServiceId(),
  192.             $event->isRemovingPostage(),
  193.             $event->getShortDescription(),
  194.             $event->getDescription(),
  195.             $event->isEnabled(),
  196.             $event->getExpirationDate(),
  197.             $event->isAvailableOnSpecialOffers(),
  198.             $event->isCumulative(),
  199.             $event->getMaxUsage(),
  200.             $defaultSerializedRule,
  201.             $event->getLocale(),
  202.             $event->getFreeShippingForCountries(),
  203.             $event->getFreeShippingForMethods(),
  204.             $event->getPerCustomerUsageCount(),
  205.             $event->getStartDate()
  206.         );
  207.         $event->setCouponModel($coupon);
  208.     }
  209.     /**
  210.      * Call the Model and delegate the create or delete action
  211.      * Feed the Event with the updated model.
  212.      *
  213.      * @param CouponModel               $coupon Model to save
  214.      * @param CouponCreateOrUpdateEvent $event  Event containing data
  215.      */
  216.     protected function createOrUpdateCondition(CouponModel $couponCouponCreateOrUpdateEvent $eventEventDispatcherInterface $dispatcher): void
  217.     {
  218.         /** @var ConditionFactory $conditionFactory */
  219.         $conditionFactory $this->conditionFactory;
  220.         $coupon->createOrUpdateConditions(
  221.             $conditionFactory->serializeConditionCollection($event->getConditions()),
  222.             $event->getLocale()
  223.         );
  224.         $event->setCouponModel($coupon);
  225.     }
  226.     public function testFreePostage(OrderEvent $event): void
  227.     {
  228.         $order $event->getOrder();
  229.         if ($this->couponManager->isCouponRemovingPostage($order)) {
  230.             $order->setPostage(0);
  231.             $event->setOrder($order);
  232.             $event->stopPropagation();
  233.         }
  234.     }
  235.     /**
  236.      * @throws \Exception if something goes wrong
  237.      *
  238.      * @param $eventName
  239.      */
  240.     public function afterOrder(OrderEvent $event$eventNameEventDispatcherInterface $dispatcher): void
  241.     {
  242.         /** @var CouponInterface[] $consumedCoupons */
  243.         $consumedCoupons $this->couponManager->getCouponsKept();
  244.         if (\is_array($consumedCoupons) && \count($consumedCoupons) > 0) {
  245.             $con Propel::getWriteConnection(OrderCouponTableMap::DATABASE_NAME);
  246.             $con->beginTransaction();
  247.             try {
  248.                 foreach ($consumedCoupons as $couponCode) {
  249.                     $couponQuery CouponQuery::create();
  250.                     $couponModel $couponQuery->findOneByCode($couponCode->getCode());
  251.                     $couponModel->setLocale($this->getSession()->getLang()->getLocale());
  252.                     /* decrease coupon quantity */
  253.                     $this->couponManager->decrementQuantity($couponModel$event->getOrder()->getCustomerId());
  254.                     /* memorize coupon */
  255.                     $orderCoupon = new OrderCoupon();
  256.                     $orderCoupon->setOrder($event->getOrder())
  257.                         ->setCode($couponModel->getCode())
  258.                         ->setType($couponModel->getType())
  259.                         ->setAmount($couponCode->exec())
  260.                         ->setTitle($couponModel->getTitle())
  261.                         ->setShortDescription($couponModel->getShortDescription())
  262.                         ->setDescription($couponModel->getDescription())
  263.                         ->setStartDate($couponModel->getStartDate())
  264.                         ->setExpirationDate($couponModel->getExpirationDate())
  265.                         ->setIsCumulative($couponModel->getIsCumulative())
  266.                         ->setIsRemovingPostage($couponModel->getIsRemovingPostage())
  267.                         ->setIsAvailableOnSpecialOffers($couponModel->getIsAvailableOnSpecialOffers())
  268.                         ->setSerializedConditions($couponModel->getSerializedConditions())
  269.                         ->setPerCustomerUsageCount($couponModel->getPerCustomerUsageCount())
  270.                     ;
  271.                     $orderCoupon->save();
  272.                     // Copy order coupon free shipping data for countries and modules
  273.                     $couponCountries CouponCountryQuery::create()->filterByCouponId($couponModel->getId())->find();
  274.                     /** @var CouponCountry $couponCountry */
  275.                     foreach ($couponCountries as $couponCountry) {
  276.                         $occ = new OrderCouponCountry();
  277.                         $occ
  278.                             ->setCouponId($orderCoupon->getId())
  279.                             ->setCountryId($couponCountry->getCountryId())
  280.                             ->save();
  281.                     }
  282.                     $couponModules CouponModuleQuery::create()->filterByCouponId($couponModel->getId())->find();
  283.                     /** @var CouponModule $couponModule */
  284.                     foreach ($couponModules as $couponModule) {
  285.                         $ocm = new OrderCouponModule();
  286.                         $ocm
  287.                             ->setCouponId($orderCoupon->getId())
  288.                             ->setModuleId($couponModule->getModuleId())
  289.                             ->save();
  290.                     }
  291.                 }
  292.                 $con->commit();
  293.             } catch (\Exception  $ex) {
  294.                 $con->rollBack();
  295.                 throw $ex;
  296.             }
  297.         }
  298.         // Clear all coupons.
  299.         $dispatcher->dispatch((new Event()), TheliaEvents::COUPON_CLEAR_ALL);
  300.     }
  301.     /**
  302.      * Cancels order coupons usage when order is canceled or refunded,
  303.      * or use canceled coupons again if the order is no longer canceled or refunded.
  304.      *
  305.      * @param string $eventName
  306.      *
  307.      * @throws \Exception
  308.      * @throws \Propel\Runtime\Exception\PropelException
  309.      */
  310.     public function orderStatusChange(OrderEvent $event$eventNameEventDispatcherInterface $dispatcher): void
  311.     {
  312.         // The order has been canceled or refunded ?
  313.         if ($event->getOrder()->isCancelled() || $event->getOrder()->isRefunded()) {
  314.             // Cancel usage of all coupons for this order
  315.             $usedCoupons OrderCouponQuery::create()
  316.                 ->filterByUsageCanceled(false)
  317.                 ->findByOrderId($event->getOrder()->getId());
  318.             $customerId $event->getOrder()->getCustomerId();
  319.             /** @var OrderCoupon $usedCoupon */
  320.             foreach ($usedCoupons as $usedCoupon) {
  321.                 if (null !== $couponModel CouponQuery::create()->findOneByCode($usedCoupon->getCode())) {
  322.                     // If the coupon still exists, restore one usage to the usage count.
  323.                     $this->couponManager->incrementQuantity($couponModel$customerId);
  324.                 }
  325.                 // Mark coupon usage as canceled in the OrderCoupon table
  326.                 $usedCoupon->setUsageCanceled(true)->save();
  327.             }
  328.         } else {
  329.             // Mark canceled coupons for this order as used again
  330.             $usedCoupons OrderCouponQuery::create()
  331.                 ->filterByUsageCanceled(true)
  332.                 ->findByOrderId($event->getOrder()->getId());
  333.             $customerId $event->getOrder()->getCustomerId();
  334.             /** @var OrderCoupon $usedCoupon */
  335.             foreach ($usedCoupons as $usedCoupon) {
  336.                 if (null !== $couponModel CouponQuery::create()->findOneByCode($usedCoupon->getCode())) {
  337.                     // If the coupon still exists, mark the coupon as used
  338.                     $this->couponManager->decrementQuantity($couponModel$customerId);
  339.                 }
  340.                 // The coupon is no longer canceled
  341.                 $usedCoupon->setUsageCanceled(false)->save();
  342.             }
  343.         }
  344.     }
  345.     /**
  346.      * {@inheritdoc}
  347.      */
  348.     public static function getSubscribedEvents()
  349.     {
  350.         return [
  351.             TheliaEvents::COUPON_CREATE => ['create'128],
  352.             TheliaEvents::COUPON_UPDATE => ['update'128],
  353.             TheliaEvents::COUPON_DELETE => ['delete'128],
  354.             TheliaEvents::COUPON_CONSUME => ['consume'128],
  355.             TheliaEvents::COUPON_CLEAR_ALL => ['clearAllCoupons'128],
  356.             TheliaEvents::COUPON_CONDITION_UPDATE => ['updateCondition'128],
  357.             TheliaEvents::ORDER_SET_POSTAGE => ['testFreePostage'132],
  358.             TheliaEvents::ORDER_BEFORE_PAYMENT => ['afterOrder'128],
  359.             TheliaEvents::ORDER_UPDATE_STATUS => ['orderStatusChange'10],
  360.             TheliaEvents::CART_ADDITEM => ['updateOrderDiscount'10],
  361.             TheliaEvents::CART_UPDATEITEM => ['updateOrderDiscount'10],
  362.             TheliaEvents::CART_DELETEITEM => ['updateOrderDiscount'10],
  363.             TheliaEvents::CUSTOMER_LOGIN => ['updateOrderDiscount'10],
  364.             AddressEvent::POST_UPDATE => ['updateOrderDiscount'10],
  365.         ];
  366.     }
  367.     /**
  368.      * Returns the session from the current request.
  369.      *
  370.      * @return \Thelia\Core\HttpFoundation\Session\Session
  371.      */
  372.     protected function getSession()
  373.     {
  374.         return $this->requestStack->getCurrentRequest()->getSession();
  375.     }
  376. }