vendor/thelia/core/lib/Thelia/Action/Cart.php line 60

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 Symfony\Component\EventDispatcher\EventDispatcherInterface;
  13. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  14. use Symfony\Component\HttpFoundation\RequestStack;
  15. use Thelia\Core\Event\Cart\CartCreateEvent;
  16. use Thelia\Core\Event\Cart\CartDuplicationEvent;
  17. use Thelia\Core\Event\Cart\CartEvent;
  18. use Thelia\Core\Event\Cart\CartPersistEvent;
  19. use Thelia\Core\Event\Cart\CartRestoreEvent;
  20. use Thelia\Core\Event\Currency\CurrencyChangeEvent;
  21. use Thelia\Core\Event\TheliaEvents;
  22. use Thelia\Model\Base\CustomerQuery;
  23. use Thelia\Model\Base\ProductSaleElementsQuery;
  24. use Thelia\Model\Cart as CartModel;
  25. use Thelia\Model\CartItem;
  26. use Thelia\Model\CartItemQuery;
  27. use Thelia\Model\CartQuery;
  28. use Thelia\Model\ConfigQuery;
  29. use Thelia\Model\Currency as CurrencyModel;
  30. use Thelia\Model\Customer as CustomerModel;
  31. use Thelia\Model\ProductSaleElements;
  32. use Thelia\Model\Tools\ProductPriceTools;
  33. use Thelia\Tools\TokenProvider;
  34. /**
  35.  * Class Cart where all actions are manage like adding, modifying or delete items.
  36.  *
  37.  * Class Cart
  38.  *
  39.  * @author Manuel Raynaud <manu@raynaud.io>
  40.  */
  41. class Cart extends BaseAction implements EventSubscriberInterface
  42. {
  43.     /** @var RequestStack */
  44.     protected $requestStack;
  45.     /** @var TokenProvider */
  46.     protected $tokenProvider;
  47.     public function __construct(RequestStack $requestStackTokenProvider $tokenProvider)
  48.     {
  49.         $this->requestStack $requestStack;
  50.         $this->tokenProvider $tokenProvider;
  51.     }
  52.     public function persistCart(CartPersistEvent $event): void
  53.     {
  54.         $cart $event->getCart();
  55.         if ($cart->isNew()) {
  56.             $cart
  57.                 ->setToken($this->generateCartCookieIdentifier())
  58.                 ->save();
  59.             $this->getSession()->setSessionCart($cart);
  60.         }
  61.     }
  62.     /**
  63.      * add an article in the current cart.
  64.      *
  65.      * @param $eventName
  66.      */
  67.     public function addItem(CartEvent $event$eventNameEventDispatcherInterface $dispatcher): void
  68.     {
  69.         $cart $event->getCart();
  70.         $newness $event->getNewness();
  71.         $append $event->getAppend();
  72.         $quantity $event->getQuantity();
  73.         $currency $cart->getCurrency();
  74.         $customer $cart->getCustomer();
  75.         $discount 0;
  76.         if ($cart->isNew()) {
  77.             $persistEvent = new CartPersistEvent($cart);
  78.             $dispatcher->dispatch($persistEventTheliaEvents::CART_PERSIST);
  79.         }
  80.         if (null !== $customer && $customer->getDiscount() > 0) {
  81.             $discount $customer->getDiscount();
  82.         }
  83.         $productSaleElementsId $event->getProductSaleElementsId();
  84.         $productId $event->getProduct();
  85.         // Search for an identical item in the cart
  86.         $findItemEvent = clone $event;
  87.         $dispatcher->dispatch($findItemEventTheliaEvents::CART_FINDITEM);
  88.         $cartItem $findItemEvent->getCartItem();
  89.         if ($cartItem === null || $newness) {
  90.             $productSaleElements ProductSaleElementsQuery::create()->findPk($productSaleElementsId);
  91.             if (null !== $productSaleElements) {
  92.                 $productPrices $productSaleElements->getPricesByCurrency($currency$discount);
  93.                 $cartItem $this->doAddItem($dispatcher$cart$productId$productSaleElements$quantity$productPrices);
  94.             }
  95.         } elseif ($append && $cartItem !== null) {
  96.             $cartItem->addQuantity($quantity)->save();
  97.         }
  98.         $event->setCartItem($cartItem);
  99.     }
  100.     /**
  101.      * Delete specify article present into cart.
  102.      */
  103.     public function deleteItem(CartEvent $event): void
  104.     {
  105.         if (null !== $cartItemId $event->getCartItemId()) {
  106.             $cart $event->getCart();
  107.             CartItemQuery::create()
  108.                 ->filterByCartId($cart->getId())
  109.                 ->filterById($cartItemId)
  110.                 ->delete();
  111.             // Force an update of the Cart object to provide
  112.             // to other listeners an updated CartItem collection.
  113.             $cart->clearCartItems();
  114.         }
  115.     }
  116.     /**
  117.      * Clear the cart.
  118.      */
  119.     public function clear(CartEvent $event): void
  120.     {
  121.         if (null !== $cart $event->getCart()) {
  122.             $cart->delete();
  123.         }
  124.     }
  125.     /**
  126.      * Modify article's quantity.
  127.      *
  128.      * don't use Form here just test the Request.
  129.      *
  130.      * @param $eventName
  131.      */
  132.     public function changeItem(CartEvent $event$eventNameEventDispatcherInterface $dispatcher): void
  133.     {
  134.         if ((null !== $cartItemId $event->getCartItemId()) && (null !== $quantity $event->getQuantity())) {
  135.             $cart $event->getCart();
  136.             $cartItem CartItemQuery::create()
  137.                 ->filterByCartId($cart->getId())
  138.                 ->filterById($cartItemId)
  139.                 ->findOne();
  140.             if ($cartItem) {
  141.                 $event->setCartItem(
  142.                     $this->updateQuantity($dispatcher$cartItem$quantity)
  143.                 );
  144.             }
  145.         }
  146.     }
  147.     public function updateCart(CurrencyChangeEvent $event$eventNameEventDispatcherInterface $dispatcher): void
  148.     {
  149.         $cart $event->getRequest()->getSession()->getSessionCart($dispatcher);
  150.         if (null !== $cart) {
  151.             $this->updateCartPrices($cart$event->getCurrency());
  152.         }
  153.     }
  154.     /**
  155.      * Refresh article's price.
  156.      */
  157.     public function updateCartPrices(CartModel $cartCurrencyModel $currency): void
  158.     {
  159.         $customer $cart->getCustomer();
  160.         $discount 0;
  161.         if (null !== $customer && $customer->getDiscount() > 0) {
  162.             $discount $customer->getDiscount();
  163.         }
  164.         // cart item
  165.         foreach ($cart->getCartItems() as $cartItem) {
  166.             $productSaleElements $cartItem->getProductSaleElements();
  167.             $productPrice $productSaleElements->getPricesByCurrency($currency$discount);
  168.             $cartItem
  169.                 ->setPrice($productPrice->getPrice())
  170.                 ->setPromoPrice($productPrice->getPromoPrice());
  171.             $cartItem->save();
  172.         }
  173.         // update the currency cart
  174.         $cart->setCurrencyId($currency->getId());
  175.         $cart->save();
  176.     }
  177.     /**
  178.      * increase the quantity for an existing cartItem.
  179.      *
  180.      * @param float $quantity
  181.      *
  182.      * @throws \Exception
  183.      * @throws \Propel\Runtime\Exception\PropelException
  184.      *
  185.      * @return CartItem
  186.      */
  187.     protected function updateQuantity(EventDispatcherInterface $dispatcherCartItem $cartItem$quantity)
  188.     {
  189.         $cartItem->setDisptacher($dispatcher);
  190.         $cartItem->updateQuantity($quantity)
  191.             ->save();
  192.         return $cartItem;
  193.     }
  194.     /**
  195.      * try to attach a new item to an existing cart.
  196.      *
  197.      * @param int   $productId
  198.      * @param float $quantity
  199.      *
  200.      * @return CartItem
  201.      */
  202.     protected function doAddItem(
  203.         EventDispatcherInterface $dispatcher,
  204.         CartModel $cart,
  205.         $productId,
  206.         ProductSaleElements $productSaleElements,
  207.         $quantity,
  208.         ProductPriceTools $productPrices
  209.     ) {
  210.         $cartItem = new CartItem();
  211.         $cartItem->setDisptacher($dispatcher);
  212.         $cartItem
  213.             ->setCart($cart)
  214.             ->setProductId($productId)
  215.             ->setProductSaleElementsId($productSaleElements->getId())
  216.             ->setQuantity($quantity)
  217.             ->setPrice($productPrices->getPrice())
  218.             ->setPromoPrice($productPrices->getPromoPrice())
  219.             ->setPromo($productSaleElements->getPromo())
  220.             ->setPriceEndOfLife(time() + ConfigQuery::read('cart.priceEOF'60 60 24 30))
  221.             ->save();
  222.         return $cartItem;
  223.     }
  224.     /**
  225.      * find a specific record in CartItem table using the Cart id, the product id
  226.      * and the product_sale_elements id.
  227.      *
  228.      * @param int $cartId
  229.      * @param int $productId
  230.      * @param int $productSaleElementsId
  231.      *
  232.      * @return CartItem
  233.      *
  234.      * @deprecated this method is deprecated. Dispatch a TheliaEvents::CART_FINDITEM instead
  235.      */
  236.     protected function findItem($cartId$productId$productSaleElementsId)
  237.     {
  238.         return CartItemQuery::create()
  239.             ->filterByCartId($cartId)
  240.             ->filterByProductId($productId)
  241.             ->filterByProductSaleElementsId($productSaleElementsId)
  242.             ->findOne();
  243.     }
  244.     /**
  245.      * Find a specific record in CartItem table using the current CartEvent.
  246.      *
  247.      * @param CartEvent $event the cart event
  248.      */
  249.     public function findCartItem(CartEvent $event): void
  250.     {
  251.         // Do not try to find a cartItem if one exists in the event, as previous event handlers
  252.         // mays have put it in th event.
  253.         if (null === $event->getCartItem() && null !== $foundItem CartItemQuery::create()
  254.             ->filterByCartId($event->getCart()->getId())
  255.             ->filterByProductId($event->getProduct())
  256.             ->filterByProductSaleElementsId($event->getProductSaleElementsId())
  257.             ->findOne()) {
  258.             $event->setCartItem($foundItem);
  259.         }
  260.     }
  261.     /**
  262.      * Search if cart already exists in session. If not try to restore it from the cart cookie,
  263.      * or duplicate an old one.
  264.      *
  265.      * @param $eventName
  266.      */
  267.     public function restoreCurrentCart(CartRestoreEvent $cartRestoreEvent$eventNameEventDispatcherInterface $dispatcher): void
  268.     {
  269.         $cookieName ConfigQuery::read('cart.cookie_name''thelia_cart');
  270.         $persistentCookie ConfigQuery::read('cart.use_persistent_cookie'1);
  271.         $cart null;
  272.         if ($this->requestStack->getCurrentRequest()->cookies->has($cookieName) && $persistentCookie) {
  273.             $cart $this->managePersistentCart($cartRestoreEvent$cookieName$dispatcher);
  274.         } elseif (!$persistentCookie) {
  275.             $cart $this->manageNonPersistentCookie($cartRestoreEvent$dispatcher);
  276.         }
  277.         // Still no cart ? Create a new one.
  278.         if (null === $cart) {
  279.             $cart $this->dispatchNewCart($dispatcher);
  280.         }
  281.         $cartRestoreEvent->setCart($cart);
  282.     }
  283.     /**
  284.      * The cart token is not saved in a cookie, if the cart is present in session, we just change the customer id
  285.      * if needed or create duplicate the current cart if the customer is not the same as customer already present in
  286.      * the cart.
  287.      *
  288.      * @return CartModel
  289.      *
  290.      * @throws \Exception
  291.      * @throws \Propel\Runtime\Exception\PropelException
  292.      */
  293.     protected function manageNonPersistentCookie(CartRestoreEvent $cartRestoreEventEventDispatcherInterface $dispatcher)
  294.     {
  295.         $cart $cartRestoreEvent->getCart();
  296.         if (null === $cart) {
  297.             $cart $this->dispatchNewCart($dispatcher);
  298.         } else {
  299.             $cart $this->manageCartDuplicationAtCustomerLogin($cart$dispatcher);
  300.         }
  301.         return $cart;
  302.     }
  303.     /**
  304.      * The cart token is saved in a cookie so we try to retrieve it. Then the customer is checked.
  305.      *
  306.      * @param $cookieName
  307.      *
  308.      * @return CartModel
  309.      *
  310.      * @throws \Exception
  311.      * @throws \Propel\Runtime\Exception\PropelException
  312.      */
  313.     protected function managePersistentCart(CartRestoreEvent $cartRestoreEvent$cookieNameEventDispatcherInterface $dispatcher)
  314.     {
  315.         // The cart cookie exists -> get the cart token
  316.         $token $this->requestStack->getCurrentRequest()->cookies->get($cookieName);
  317.         // Check if a cart exists for this token
  318.         if (null !== $cart CartQuery::create()->findOneByToken($token)) {
  319.             $cart $this->manageCartDuplicationAtCustomerLogin($cart$dispatcher);
  320.         }
  321.         return $cart;
  322.     }
  323.     protected function manageCartDuplicationAtCustomerLogin(CartModel $cartEventDispatcherInterface $dispatcher)
  324.     {
  325.         /** @var CustomerModel $customer */
  326.         if (null !== $customer $this->getSession()->getCustomerUser()) {
  327.             // Check if we have to duplicate the existing cart.
  328.             $duplicateCart true;
  329.             // A customer is logged in.
  330.             if (null === $cart->getCustomerId()) {
  331.                 // If the customer has a discount, whe have to duplicate the cart,
  332.                 // so that the discount will be applied to the products in cart.
  333.                 if (=== $customer->getDiscount() || === $cart->countCartItems()) {
  334.                     // If no discount, or an empty cart, there's no need to duplicate.
  335.                     $duplicateCart false;
  336.                 }
  337.             }
  338.             if ($duplicateCart) {
  339.                 // Duplicate the cart
  340.                 $cart $this->duplicateCart($dispatcher$cart$customer);
  341.             } else {
  342.                 // No duplication required, just assign the cart to the customer
  343.                 $cart->setCustomerId($customer->getId())->save();
  344.             }
  345.         } elseif ($cart->getCustomerId() != null) {
  346.             // The cart belongs to another user
  347.             if (=== $cart->countCartItems()) {
  348.                 // No items in cart, assign it to nobody.
  349.                 $cart->setCustomerId(null)->save();
  350.             } else {
  351.                 // Some itemls in cart, duplicate it without assigning a customer ID.
  352.                 $cart $this->duplicateCart($dispatcher$cart);
  353.             }
  354.         }
  355.         return $cart;
  356.     }
  357.     /**
  358.      * @return CartModel
  359.      */
  360.     protected function dispatchNewCart(EventDispatcherInterface $dispatcher)
  361.     {
  362.         $cartCreateEvent = new CartCreateEvent();
  363.         $dispatcher->dispatch($cartCreateEventTheliaEvents::CART_CREATE_NEW);
  364.         return $cartCreateEvent->getCart();
  365.     }
  366.     /**
  367.      * Create a new, empty cart object, and assign it to the current customer, if any.
  368.      */
  369.     public function createEmptyCart(CartCreateEvent $cartCreateEvent): void
  370.     {
  371.         $cart = new CartModel();
  372.         $cart->setCurrency($this->getSession()->getCurrency(true));
  373.         /** @var CustomerModel $customer */
  374.         if (null !== $customer $this->getSession()->getCustomerUser()) {
  375.             $cart->setCustomer(CustomerQuery::create()->findPk($customer->getId()));
  376.         }
  377.         $this->getSession()->setSessionCart($cart);
  378.         if (ConfigQuery::read('cart.use_persistent_cookie'1) == 1) {
  379.             // set cart_use_cookie to "" to remove the cart cookie
  380.             // see Thelia\Core\EventListener\ResponseListener
  381.             $this->getSession()->set('cart_use_cookie''');
  382.         }
  383.         $cartCreateEvent->setCart($cart);
  384.     }
  385.     /**
  386.      * Duplicate an existing Cart. If a customer ID is provided the created cart will be attached to this customer.
  387.      *
  388.      * @param CustomerModel $customer
  389.      *
  390.      * @return CartModel
  391.      */
  392.     protected function duplicateCart(EventDispatcherInterface $dispatcherCartModel $cartCustomerModel $customer null)
  393.     {
  394.         $newCart $cart->duplicate(
  395.             $this->generateCartCookieIdentifier(),
  396.             $customer,
  397.             $this->getSession()->getCurrency(),
  398.             $dispatcher
  399.         );
  400.         $cartEvent = new CartDuplicationEvent($newCart$cart);
  401.         $dispatcher->dispatch($cartEventTheliaEvents::CART_DUPLICATE);
  402.         return $cartEvent->getDuplicatedCart();
  403.     }
  404.     /**
  405.      * Generate the cart cookie identifier, or return null if the cart is only managed in the session object,
  406.      * not in a client cookie.
  407.      *
  408.      * @return string
  409.      */
  410.     protected function generateCartCookieIdentifier()
  411.     {
  412.         $id null;
  413.         if (ConfigQuery::read('cart.use_persistent_cookie'1) == 1) {
  414.             $id $this->tokenProvider->getToken();
  415.             $this->getSession()->set('cart_use_cookie'$id);
  416.         }
  417.         return $id;
  418.     }
  419.     /**
  420.      * {@inheritdoc}
  421.      */
  422.     public static function getSubscribedEvents()
  423.     {
  424.         return [
  425.             TheliaEvents::CART_PERSIST => ['persistCart'128],
  426.             TheliaEvents::CART_RESTORE_CURRENT => ['restoreCurrentCart'128],
  427.             TheliaEvents::CART_CREATE_NEW => ['createEmptyCart'128],
  428.             TheliaEvents::CART_ADDITEM => ['addItem'128],
  429.             TheliaEvents::CART_FINDITEM => ['findCartItem'128],
  430.             TheliaEvents::CART_DELETEITEM => ['deleteItem'128],
  431.             TheliaEvents::CART_UPDATEITEM => ['changeItem'128],
  432.             TheliaEvents::CART_CLEAR => ['clear'128],
  433.             TheliaEvents::CHANGE_DEFAULT_CURRENCY => ['updateCart'128],
  434.         ];
  435.     }
  436.     /**
  437.      * Returns the session from the current request.
  438.      *
  439.      * @return \Thelia\Core\HttpFoundation\Session\Session
  440.      */
  441.     protected function getSession()
  442.     {
  443.         return $this->requestStack->getCurrentRequest()->getSession();
  444.     }
  445. }