classes/XLite/Model/Cart.php line 36

Open in your IDE?
  1. <?php
  2. /**
  3.  * Copyright (c) 2011-present Qualiteam software Ltd. All rights reserved.
  4.  * See https://www.x-cart.com/license-agreement.html for license details.
  5.  */
  6. namespace XLite\Model;
  7. use Doctrine\ORM\Mapping as ORM;
  8. use ApiPlatform\Core\Annotation as ApiPlatform;
  9. use XLite\API\Endpoint\Cart\DTO\CartOutput as Output;
  10. /**
  11.  * Cart
  12.  *
  13.  * @ORM\Entity
  14.  * @ApiPlatform\ApiResource(
  15.  *     output=Output::class,
  16.  *     itemOperations={
  17.  *          "get"={
  18.  *              "method"="GET",
  19.  *              "path"="/carts/{id}.{_format}",
  20.  *              "identifiers"={"id"},
  21.  *              "requirements"={"id"="\d+"},
  22.  *              "openapi_context"={
  23.  *                  "summary"="Retrieve a cart by id",
  24.  *                  "parameters"={
  25.  *                      {"name"="id", "in"="path", "required"=true, "schema"={"type"="integer"}}
  26.  *                  }
  27.  *             },
  28.  *          }
  29.  *     },
  30.  *     collectionOperations={
  31.  *          "get"={
  32.  *              "method"="GET",
  33.  *              "path"="/carts.{_format}",
  34.  *              "identifiers"={"id"}
  35.  *          }
  36.  *     }
  37.  * )
  38.  */
  39. class Cart extends \XLite\Model\Order
  40. {
  41.     /**
  42.      * Cart renew period
  43.      */
  44.     public const RENEW_PERIOD 3600;
  45.     /**
  46.      * Array of instances for all derived classes
  47.      *
  48.      * @var array
  49.      */
  50.     protected static $instances = [];
  51.     /**
  52.      * Flag to ignore long calculation for speed up purposes
  53.      *
  54.      * @var boolean
  55.      */
  56.     protected static $ignoreLongCalculations false;
  57.     /**
  58.      * Method to access a singleton
  59.      *
  60.      * @param boolean $doCalculate Flag for cart recalculation OPTIONAL
  61.      *
  62.      * @return \XLite\Model\Cart
  63.      */
  64.     public static function getInstance($doCalculate true)
  65.     {
  66.         $className get_called_class();
  67.         // Create new instance of the object (if it is not already created)
  68.         if (!isset(static::$instances[$className])) {
  69.             $auth \XLite\Core\Auth::getInstance();
  70.             $cart = static::tryRetrieveCart();
  71.             if (!isset($cart)) {
  72.                 // Cart not found - create a new instance
  73.                 $cart = new $className();
  74.                 $cart->initializeCart();
  75.             }
  76.             static::$instances[$className] = $cart;
  77.             if ($doCalculate) {
  78.                 if (
  79.                     $auth->isLogged()
  80.                     && (!$cart->getProfile()
  81.                         || $auth->getProfile()->getProfileId() != $cart->getProfile()->getProfileId()
  82.                     )
  83.                 ) {
  84.                     $cart->setProfile($auth->getProfile());
  85.                     $cart->setOrigProfile($auth->getProfile());
  86.                 }
  87.                 // Check login state
  88.                 if (
  89.                     \XLite\Core\Session::getInstance()->lastLoginUnique === null
  90.                     && $cart->getProfile()
  91.                     && $cart->getProfile()->getAnonymous()
  92.                     && $cart->getProfile()->getLogin()
  93.                 ) {
  94.                     $tmpProfile = new \XLite\Model\Profile();
  95.                     $tmpProfile->setProfileId(0);
  96.                     $tmpProfile->setLogin($cart->getProfile()->getLogin());
  97.                     $exists \XLite\Core\Database::getRepo('XLite\Model\Profile')
  98.                         ->checkRegisteredUserWithSameLogin($tmpProfile);
  99.                     \XLite\Core\Session::getInstance()->lastLoginUnique = !$exists;
  100.                 }
  101.                 if ($cart->isPersistent()) {
  102.                     if (
  103.                         $cart instanceof \XLite\Model\Cart
  104.                         || (\XLite\Core\Converter::time() - static::RENEW_PERIOD) > $cart->getLastRenewDate()
  105.                     ) {
  106.                         $cart->renew();
  107.                     } else {
  108.                         $cart->calculate();
  109.                     }
  110.                     if ($cart->getPaymentMethod() && !$cart->getPaymentMethod()->isEnabled()) {
  111.                         $cart->renewPaymentMethod();
  112.                     }
  113.                     $cart->renewSoft();
  114.                     \XLite\Core\Session::getInstance()->order_id $cart->getOrderId();
  115.                 }
  116.             }
  117.         }
  118.         return static::$instances[$className];
  119.     }
  120.     /**
  121.      * Add item to order
  122.      *
  123.      * @param \XLite\Model\OrderItem $newItem Item to add
  124.      *
  125.      * @return boolean
  126.      */
  127.     public function addItem(\XLite\Model\OrderItem $newItem)
  128.     {
  129.         if (\XLite\Core\Request::getInstance()->addLastUpdatedItem === 'true') {
  130.             $suitableItems $this->getItemsByProductId($newItem->getProductId());
  131.             if ($suitableItems) {
  132.                 /** @var \XLite\Model\OrderItem $lastUpdatedItem */
  133.                 $lastUpdatedItem $this->getLastUpdatedOrderItem($suitableItems);
  134.                 $newItem $lastUpdatedItem->cloneEntity();
  135.                 $newItem->setAmount(1);
  136.             }
  137.         }
  138.         $this->renewUpdatedTime();
  139.         return parent::addItem($newItem);
  140.     }
  141.     /**
  142.      * Remove item from cart
  143.      *
  144.      * @param \XLite\Model\OrderItem $item
  145.      *
  146.      * @return boolean
  147.      */
  148.     public function removeItem(\XLite\Model\OrderItem $item)
  149.     {
  150.         $suitableItems $this->getItemsByProductId($item->getProductId());
  151.         if (
  152.             $suitableItems
  153.             && $lastUpdatedItem $this->getLastUpdatedOrderItem($suitableItems)
  154.         ) {
  155.             $this->renewUpdatedTime();
  156.             return parent::removeItem($lastUpdatedItem);
  157.         }
  158.     }
  159.     /**
  160.      * @param \XLite\Model\OrderItem[] $orderItems
  161.      *
  162.      * @return boolean
  163.      */
  164.     protected function getLastUpdatedOrderItem($orderItems)
  165.     {
  166.         $maxUpdateDate        0;
  167.         $lastUpdatedOrderItem null;
  168.         foreach ($orderItems as $item) {
  169.             if ($item->getUpdateDate() >= $maxUpdateDate) {
  170.                 $maxUpdateDate        $item->getUpdateDate();
  171.                 $lastUpdatedOrderItem $item;
  172.             }
  173.         }
  174.         return $lastUpdatedOrderItem;
  175.     }
  176.     /**
  177.      * Check if recently updated
  178.      *
  179.      * @return boolean
  180.      */
  181.     public function isRecentlyUpdated()
  182.     {
  183.         return (bool)$this->getUpdatedTime();
  184.     }
  185.     /**
  186.      * Check if given payment method can be applied to the order
  187.      *
  188.      * @param  \XLite\Model\Payment\Method  $method
  189.      * @return boolean
  190.      */
  191.     protected function isPaymentMethodIsApplicable($method)
  192.     {
  193.         return parent::isPaymentMethodIsApplicable($method)
  194.             && $method->isEnabled();
  195.     }
  196.     /**
  197.      * Return updated time
  198.      *
  199.      * @return integer
  200.      */
  201.     public function getUpdatedTime()
  202.     {
  203.         return \XLite\Core\Session::getInstance()->cartUpdatedTime;
  204.     }
  205.     /**
  206.      * Renew updated time
  207.      */
  208.     public function renewUpdatedTime()
  209.     {
  210.         \XLite\Core\Session::getInstance()->cartUpdatedTime \XLite\Core\Converter::getInstance()->time();
  211.     }
  212.     /**
  213.      * Set updated time to 0
  214.      */
  215.     public function unsetUpdatedTime()
  216.     {
  217.         \XLite\Core\Session::getInstance()->cartUpdatedTime 0;
  218.     }
  219.     /**
  220.      * Set object instance
  221.      *
  222.      * @param \XLite\Model\Order $object Cart
  223.      *
  224.      * @return void
  225.      */
  226.     public static function setObject(\XLite\Model\Order $object)
  227.     {
  228.         $className get_called_class();
  229.         static::$instances[$className] = $object;
  230.         \XLite\Core\Session::getInstance()->order_id $object->getOrderId();
  231.     }
  232.     /**
  233.      * Method to retrieve cart from either profile or session
  234.      *
  235.      * @return \XLite\Model\Cart
  236.      */
  237.     public static function tryRetrieveCart()
  238.     {
  239.         $auth \XLite\Core\Auth::getInstance();
  240.         $cart null;
  241.         if ($auth->isLogged()) {
  242.             // Try to find cart of logged in user
  243.             $cart \XLite\Core\Database::getRepo('XLite\Model\Cart')->findOneByProfile($auth->getProfile());
  244.         }
  245.         if (empty($cart)) {
  246.             // Try to get cart from session
  247.             $orderId \XLite\Core\Session::getInstance()->order_id;
  248.             if ($orderId) {
  249.                 $cart \XLite\Core\Database::getRepo('XLite\Model\Cart')->findOneForCustomer($orderId);
  250.                 // Forget cart if cart is order
  251.                 if ($cart && !$cart->hasCartStatus()) {
  252.                     unset(\XLite\Core\Session::getInstance()->order_id$cart);
  253.                 }
  254.             }
  255.         }
  256.         return $cart;
  257.     }
  258.     /**
  259.      * Try close
  260.      *
  261.      * @return boolean
  262.      */
  263.     public function tryClose()
  264.     {
  265.         $result false;
  266.         if (!$this->isOpen()) {
  267.             \XLite\Model\Cart::setObject($this);
  268.             if ($this instanceof \XLite\Model\Cart) {
  269.                 $this->assignOrderNumber();
  270.             }
  271.             $paymentStatus $this->getCalculatedPaymentStatus(true);
  272.             $this->setPaymentStatus($paymentStatus);
  273.             $controller = new \XLite\Controller\Customer\Checkout();
  274.             $controller->processSucceed();
  275.             $result true;
  276.         }
  277.         return $result;
  278.     }
  279.     /**
  280.      * Get ignoreLongCalculations flag value
  281.      *
  282.      * @return boolean
  283.      */
  284.     public function isIgnoreLongCalculations()
  285.     {
  286.         return static::$ignoreLongCalculations;
  287.     }
  288.     /**
  289.      * Set ignoreLongCalculations flag value
  290.      *
  291.      * @return boolean
  292.      */
  293.     public function setIgnoreLongCalculations()
  294.     {
  295.         return static::$ignoreLongCalculations true;
  296.     }
  297.     /**
  298.      * Calculate order
  299.      *
  300.      * @return void
  301.      */
  302.     public function calculate()
  303.     {
  304.         if ($this->isPersistent()) {
  305.             parent::calculate();
  306.         }
  307.     }
  308.     /**
  309.      * Order number is assigned during the pay process
  310.      * It must be kept during the checkout session
  311.      *
  312.      * @return void
  313.      */
  314.     public function assignOrderNumber()
  315.     {
  316.         if (!$this->getOrderNumber()) {
  317.             $this->setOrderNumber(
  318.                 \XLite\Core\Database::getRepo('XLite\Model\Order')->findNextOrderNumber()
  319.             );
  320.             if ($this->isFlushOnOrderNumberAssign()) {
  321.                 \XLite\Core\Database::getEM()->flush();
  322.             }
  323.         }
  324.     }
  325.     /**
  326.      * Should we flush in order assign
  327.      *
  328.      * @return boolean
  329.      */
  330.     public function isFlushOnOrderNumberAssign()
  331.     {
  332.         return true;
  333.     }
  334.     /**
  335.      * Calculate order with the subtotal calculation only
  336.      *
  337.      * @return void
  338.      */
  339.     public function calculateInitial()
  340.     {
  341.         $this->reinitializeCurrency();
  342.     }
  343.     /**
  344.      * Clear cart (remove cart items)
  345.      *
  346.      * @return void
  347.      */
  348.     public function clear()
  349.     {
  350.         foreach ($this->getItems() as $item) {
  351.             \XLite\Core\Database::getEM()->remove($item);
  352.         }
  353.         $this->getItems()->clear();
  354.     }
  355.     /**
  356.      * Checks whether a product is in the cart
  357.      *
  358.      * @param integer $productId ID of the product to look for
  359.      *
  360.      * @return boolean
  361.      */
  362.     public function isProductAdded($productId)
  363.     {
  364.         $result false;
  365.         foreach ($this->getItems() as $item) {
  366.             $product $item->getProduct();
  367.             if ($product && $product->getProductId() == $productId) {
  368.                 $result true;
  369.                 break;
  370.             }
  371.         }
  372.         return $result;
  373.     }
  374.     /**
  375.      * Get cart items
  376.      *
  377.      * @return list<OrderItem>
  378.      */
  379.     public function getItems()
  380.     {
  381.         $items parent::getItems();
  382.         if (!\XLite::isAdminZone()) {
  383.             foreach ($items as $item) {
  384.                 if ($item->isDeleted()) {
  385.                     $items->removeElement($item);
  386.                     \XLite\Core\Database::getRepo('XLite\Model\OrderItem')->delete($item);
  387.                 }
  388.             }
  389.         }
  390.         return $items;
  391.     }
  392.     /**
  393.      * Prepare order before remove operation
  394.      *
  395.      * @return void
  396.      */
  397.     public function prepareBeforeRemove()
  398.     {
  399.         parent::prepareBeforeRemove();
  400.     }
  401.     /**
  402.      * Mark cart as order
  403.      *
  404.      * @return void
  405.      */
  406.     public function markAsOrder()
  407.     {
  408.         parent::markAsOrder();
  409.         if ($this instanceof \XLite\Model\Cart) {
  410.             $this->assignOrderNumber();
  411.         }
  412.         $this->getRepository()->markAsOrder($this->getOrderId());
  413.         $this->setJustClosed(true);
  414.     }
  415.     /**
  416.      * Check if the cart has a "Cart" status. ("in progress", "temporary")
  417.      *
  418.      * @return boolean
  419.      */
  420.     public function hasCartStatus()
  421.     {
  422.         return $this instanceof \XLite\Model\Cart;
  423.     }
  424.     /**
  425.      * If we can proceed with checkout with current cart
  426.      *
  427.      * @return boolean
  428.      */
  429.     public function checkCart()
  430.     {
  431.         return !$this->isEmpty()
  432.             && !((bool) $this->getItemsWithWrongAmounts())
  433.             && !$this->isMinOrderAmountError()
  434.             && !$this->isMaxOrderAmountError()
  435.             && $this->isConfigured()
  436.             && $this->isAllItemsValid();
  437.     }
  438.     /**
  439.      * Login operation
  440.      *
  441.      * @param \XLite\Model\Profile $profile Profile
  442.      *
  443.      * @return void
  444.      */
  445.     public function login(\XLite\Model\Profile $profile)
  446.     {
  447.         if ($this->isEmpty()) {
  448.             if ($this->getProfile() && !$this->getProfile()->getAnonymous()) {
  449.                 $this->setProfile(null);
  450.             }
  451.             \XLite\Core\Database::getEM()->remove($this);
  452.         } else {
  453.             $this->mergeWithProfile($profile);
  454.             $this->setProfile($profile);
  455.             $this->setOrigProfile($profile);
  456.             \XLite\Core\Session::getInstance()->unsetBatch(
  457.                 'same_address',
  458.                 'order_create_profile',
  459.                 'createProfilePassword',
  460.                 'lastLoginUnique'
  461.             );
  462.         }
  463.     }
  464.     /**
  465.      * Returns the list of session vars that must be cleared on logoff
  466.      *
  467.      * @return array
  468.      */
  469.     public function getSessionVarsToClearOnLogoff()
  470.     {
  471.         return [
  472.             'same_address',
  473.             'order_id'
  474.         ];
  475.     }
  476.     /**
  477.      * Clear some session variables on logout
  478.      *
  479.      * @return void
  480.      */
  481.     protected function clearSessionVarsOnLogoff()
  482.     {
  483.         foreach ($this->getSessionVarsToClearOnLogoff() as $name) {
  484.             unset(\XLite\Core\Session::getInstance()->$name);
  485.         }
  486.     }
  487.     /**
  488.      * Logoff operation
  489.      *
  490.      * @return void
  491.      */
  492.     public function logoff()
  493.     {
  494.         $this->clearSessionVarsOnLogoff();
  495.     }
  496.     /**
  497.      * Initialize new cart
  498.      *
  499.      * @return void
  500.      */
  501.     protected function initializeCart()
  502.     {
  503.         $this->reinitializeCurrency();
  504.         \XLite\Core\Session::getInstance()->unsetBatch(
  505.             'same_address',
  506.             'order_create_profile',
  507.             'createProfilePassword'
  508.         );
  509.     }
  510.     /**
  511.      * Merge cart with with other carts specified profile
  512.      *
  513.      * @param \XLite\Model\Profile $profile Profile
  514.      *
  515.      * @return void
  516.      */
  517.     public function mergeWithProfile(\XLite\Model\Profile $profile)
  518.     {
  519.         // Merge carts
  520.         $carts $this->getRepository()->findByProfile($profile);
  521.         if ($carts) {
  522.             foreach ($carts as $cart) {
  523.                 $this->merge($cart);
  524.                 $cart->setProfile(null);
  525.                 $cart->setOrigProfile(null);
  526.                 \XLite\Core\Database::getEM()->remove($cart);
  527.             }
  528.         }
  529.         // Merge old addresses
  530.         if ($this->getProfile()) {
  531.             // Merge shipping address
  532.             if ($this->getProfile()->getShippingAddress() && $this->getProfile()->getShippingAddress()->getIsWork()) {
  533.                 $address $this->getProfile()->getShippingAddress();
  534.                 if ($profile->getShippingAddress()) {
  535.                     if ($profile->getShippingAddress()->getIsWork()) {
  536.                         \XLite\Core\Database::getEM()->remove($profile->getShippingAddress());
  537.                         $profile->getAddresses()->removeElement($profile->getShippingAddress());
  538.                     } else {
  539.                         $profile->getShippingAddress()->setisShipping(false);
  540.                     }
  541.                 }
  542.                 $address->setProfile($profile);
  543.                 $this->getProfile()->getAddresses()->removeElement($address);
  544.                 $profile->addAddresses($address);
  545.             }
  546.             // Merge billing address
  547.             if (
  548.                 $this->getProfile()->getBillingAddress()
  549.                 && $this->getProfile()->getBillingAddress()->getIsWork()
  550.             ) {
  551.                 $address $this->getProfile()->getBillingAddress();
  552.                 if ($profile->getBillingAddress()) {
  553.                     if (
  554.                         $profile->getBillingAddress()->getIsWork()
  555.                         && !$profile->getBillingAddress()->getIsShipping()
  556.                     ) {
  557.                         \XLite\Core\Database::getEM()->remove($profile->getBillingAddress());
  558.                         $profile->getAddresses()->removeElement($profile->getBillingAddress());
  559.                     } else {
  560.                         $profile->getBillingAddress()->setIsBilling(false);
  561.                     }
  562.                 }
  563.                 $address->setProfile($profile);
  564.                 $this->getProfile()->getAddresses()->removeElement($address);
  565.                 $profile->addAddresses($address);
  566.             }
  567.         }
  568.     }
  569.     /**
  570.      * Merge
  571.      *
  572.      * @param \XLite\Model\Cart $cart Cart
  573.      *
  574.      * @return \XLite\Model\Cart
  575.      */
  576.     public function merge(\XLite\Model\Cart $cart)
  577.     {
  578.         if (!$cart->isEmpty()) {
  579.             foreach ($cart->getItems() as $item) {
  580.                 $cart->getItems()->removeElement($item);
  581.                 $item->setOrder($this);
  582.                 $this->addItems($item);
  583.             }
  584.         }
  585.         $this->updateOrder();
  586.     }
  587. }