core/lib/Thelia/Action/Cart.php line 286

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