* @copyright 2007-2017 PrestaShop SA * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) * International Registered Trademark & Property of PrestaShop SA */ /** * Class CartRuleCore */ class CartRule extends CartRuleCore { /** * Get CartRules for the given Customer * * @param int $id_lang Language ID * @param int $id_customer Customer ID * @param bool $active Active vouchers only * @param bool $includeGeneric Include generic AND highlighted vouchers, regardless of highlight_only setting * @param bool $inStock Vouchers in stock only * @param Cart|null $cart Cart * @param bool $free_shipping_only Free shipping only * @param bool $highlight_only Highlighted vouchers only * @return array * @throws PrestaShopDatabaseException */ public static function getCustomerCartRules( $id_lang, $id_customer, $active = false, $includeGeneric = true, $inStock = false, Cart $cart = null, $free_shipping_only = false, $highlight_only = false ) { if (!CartRule::isFeatureActive() // || !CartRule::haveCartRuleToday($id_customer) Empeche les reductions d'apparaitre... ) { return array(); } $sql_part1 = '* FROM `' . _DB_PREFIX_ . 'cart_rule` cr LEFT JOIN `' . _DB_PREFIX_ . 'cart_rule_lang` crl ON (cr.`id_cart_rule` = crl.`id_cart_rule` AND crl.`id_lang` = ' . (int) $id_lang . ')'; $sql_part2 = ' AND cr.date_from < "' . date('Y-m-d H:i:59') . '" AND cr.date_to > "' . date('Y-m-d H:i:59') . '" ' . ($active ? 'AND cr.`active` = 1' : '') . ' ' . ($inStock ? 'AND cr.`quantity` > 0' : ''); if ($free_shipping_only) { $sql_part2 .= ' AND free_shipping = 1 AND carrier_restriction = 1'; } if ($highlight_only) { $sql_part2 .= ' AND highlight = 1 AND code NOT LIKE "' . pSQL(CartRule::BO_ORDER_CODE_PREFIX) . '%"'; } $sql = '(SELECT SQL_NO_CACHE ' . $sql_part1 . ' WHERE cr.`id_customer` = ' . (int) $id_customer . ' ' . $sql_part2 . ')'; $sql .= ' UNION (SELECT ' . $sql_part1 . ' WHERE cr.`group_restriction` = 1 ' . $sql_part2 . ')'; if ($includeGeneric && (int) $id_customer != 0) { $sql .= ' UNION (SELECT ' . $sql_part1 . ' WHERE cr.`id_customer` = 0 ' . $sql_part2 . ')'; } $result = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS($sql, true, false); if (empty($result)) { return array(); } // Remove cart rule that does not match the customer groups $customerGroups = Customer::getGroupsStatic($id_customer); foreach ($result as $key => $cart_rule) { if ($cart_rule['group_restriction']) { $cartRuleGroups = Db::getInstance()->executeS('SELECT id_group FROM ' . _DB_PREFIX_ . 'cart_rule_group WHERE id_cart_rule = ' . (int) $cart_rule['id_cart_rule']); foreach ($cartRuleGroups as $cartRuleGroup) { if (in_array($cartRuleGroup['id_group'], $customerGroups)) { continue 2; } } unset($result[$key]); } } foreach ($result as &$cart_rule) { if ($cart_rule['quantity_per_user']) { $quantity_used = Order::getDiscountsCustomer((int) $id_customer, (int) $cart_rule['id_cart_rule']); if (isset($cart) && isset($cart->id)) { $quantity_used += $cart->getDiscountsCustomer((int) $cart_rule['id_cart_rule']); } $cart_rule['quantity_for_user'] = $cart_rule['quantity_per_user'] - $quantity_used; } else { $cart_rule['quantity_for_user'] = 0; } } unset($cart_rule); foreach ($result as $key => $cart_rule) { if ($cart_rule['shop_restriction']) { $cartRuleShops = Db::getInstance()->executeS('SELECT id_shop FROM ' . _DB_PREFIX_ . 'cart_rule_shop WHERE id_cart_rule = ' . (int) $cart_rule['id_cart_rule']); foreach ($cartRuleShops as $cartRuleShop) { if (Shop::isFeatureActive() && ($cartRuleShop['id_shop'] == Context::getContext()->shop->id)) { continue 2; } } unset($result[$key]); } } if (isset($cart) && isset($cart->id)) { foreach ($result as $key => $cart_rule) { if ($cart_rule['product_restriction']) { $cr = new CartRule((int) $cart_rule['id_cart_rule']); $r = $cr->checkProductRestrictions(Context::getContext(), false, false); if ($r !== false) { continue; } unset($result[$key]); } } } $result_bak = $result; $result = array(); $country_restriction = false; foreach ($result_bak as $key => $cart_rule) { if ($cart_rule['country_restriction']) { $country_restriction = true; $countries = Db::getInstance()->ExecuteS(' SELECT `id_country` FROM `' . _DB_PREFIX_ . 'address` WHERE `id_customer` = ' . (int) $id_customer . ' AND `deleted` = 0' ); if (is_array($countries) && count($countries)) { foreach ($countries as $country) { $id_cart_rule = (bool) Db::getInstance()->getValue(' SELECT crc.id_cart_rule FROM ' . _DB_PREFIX_ . 'cart_rule_country crc WHERE crc.id_cart_rule = ' . (int) $cart_rule['id_cart_rule'] . ' AND crc.id_country = ' . (int) $country['id_country']); if ($id_cart_rule) { $result[] = $result_bak[$key]; break; } } } } else { $result[] = $result_bak[$key]; } } if (!$country_restriction) { $result = $result_bak; } return $result; } /** * Check if this CartRule can be applied * * @param Context $context Context instance * @param bool $alreadyInCart Check if the voucher is already on the cart * @param bool $display_error Display error * * @return bool|mixed|string */ public function checkValidity(Context $context, $alreadyInCart = false, $display_error = true, $check_carrier = true) { if (!CartRule::isFeatureActive()) { return false; } if (!$this->active) { return (!$display_error) ? false : $this->trans('This voucher is disabled', array(), 'Shop.Notifications.Error'); } if (!$this->quantity) { return (!$display_error) ? false : $this->trans('This voucher has already been used', array(), 'Shop.Notifications.Error'); } if (strtotime($this->date_from) > time()) { return (!$display_error) ? false : $this->trans('This voucher is not valid yet', array(), 'Shop.Notifications.Error'); } if (strtotime($this->date_to) < time()) { return (!$display_error) ? false : $this->trans('This voucher has expired', array(), 'Shop.Notifications.Error'); } if ($context->cart->id_customer) { $quantityUsed = Db::getInstance()->getValue(' SELECT count(*) FROM ' . _DB_PREFIX_ . 'orders o LEFT JOIN ' . _DB_PREFIX_ . 'order_cart_rule od ON o.id_order = od.id_order WHERE o.id_customer = ' . $context->cart->id_customer . ' AND od.id_cart_rule = ' . (int) $this->id . ' AND ' . (int) Configuration::get('PS_OS_ERROR') . ' != o.current_state '); if ($quantityUsed + 1 > $this->quantity_per_user) { return (!$display_error) ? false : $this->trans('You cannot use this voucher anymore (usage limit reached)', array(), 'Shop.Notifications.Error'); } } // Get an intersection of the customer groups and the cart rule groups (if the customer is not logged in, the default group is Visitors) if ($this->group_restriction) { $id_cart_rule = (int) Db::getInstance()->getValue(' SELECT crg.id_cart_rule FROM ' . _DB_PREFIX_ . 'cart_rule_group crg WHERE crg.id_cart_rule = ' . (int) $this->id . ' AND crg.id_group ' . ($context->cart->id_customer ? 'IN (SELECT cg.id_group FROM ' . _DB_PREFIX_ . 'customer_group cg WHERE cg.id_customer = ' . (int) $context->cart->id_customer . ')' : '= ' . (int) Configuration::get('PS_UNIDENTIFIED_GROUP'))); if (!$id_cart_rule) { return (!$display_error) ? false : $this->trans('You cannot use this voucher', array(), 'Shop.Notifications.Error'); } } // Check if the customer delivery address is usable with the cart rule if ($this->country_restriction) { if (!$context->cart->id_address_delivery) { return (!$display_error) ? false : $this->trans('You must choose a delivery address before applying this voucher to your order', array(), 'Shop.Notifications.Error'); } $id_cart_rule = (int) Db::getInstance()->getValue(' SELECT crc.id_cart_rule FROM ' . _DB_PREFIX_ . 'cart_rule_country crc WHERE crc.id_cart_rule = ' . (int) $this->id . ' AND crc.id_country = (SELECT a.id_country FROM ' . _DB_PREFIX_ . 'address a WHERE a.id_address = ' . (int) $context->cart->id_address_delivery . ' LIMIT 1)'); if (!$id_cart_rule) { return (!$display_error) ? false : $this->trans('You cannot use this voucher in your country of delivery', array(), 'Shop.Notifications.Error'); } } // Check if the carrier chosen by the customer is usable with the cart rule if ($this->carrier_restriction && $check_carrier) { if (!$context->cart->id_carrier) { return (!$display_error) ? false : $this->trans('You must choose a carrier before applying this voucher to your order', array(), 'Shop.Notifications.Error'); } $id_cart_rule = (int) Db::getInstance()->getValue(' SELECT crc.id_cart_rule FROM ' . _DB_PREFIX_ . 'cart_rule_carrier crc INNER JOIN ' . _DB_PREFIX_ . 'carrier c ON (c.id_reference = crc.id_carrier AND c.deleted = 0) WHERE crc.id_cart_rule = ' . (int) $this->id . ' AND c.id_carrier = ' . (int) $context->cart->id_carrier); if (!$id_cart_rule) { return (!$display_error) ? false : $this->trans('You cannot use this voucher with this carrier', array(), 'Shop.Notifications.Error'); } } if ($this->reduction_exclude_special) { $products = $context->cart->getProducts(); $is_ok = false; foreach ($products as $product) { if (!$product['reduction_applies']) { $is_ok = true; break; } } if (!$is_ok) { return (!$display_error) ? false : $this->trans('You cannot use this voucher on products on sale', array(), 'Shop.Notifications.Error'); } } // Check if the cart rules appliy to the shop browsed by the customer if ($this->shop_restriction && $context->shop->id && Shop::isFeatureActive()) { $id_cart_rule = (int) Db::getInstance()->getValue(' SELECT crs.id_cart_rule FROM ' . _DB_PREFIX_ . 'cart_rule_shop crs WHERE crs.id_cart_rule = ' . (int) $this->id . ' AND crs.id_shop = ' . (int) $context->shop->id); if (!$id_cart_rule) { return (!$display_error) ? false : $this->trans('You cannot use this voucher', array(), 'Shop.Notifications.Error'); } } // Check if the products chosen by the customer are usable with the cart rule if ($this->product_restriction) { $r = $this->checkProductRestrictions($context, false, $display_error, $alreadyInCart); if ($r !== false && $display_error) { return $r; } elseif (!$r && !$display_error) { return false; } } // Check if the cart rule is only usable by a specific customer, and if the current customer is the right one if ($this->id_customer && $context->cart->id_customer != $this->id_customer) { if (!Context::getContext()->customer->isLogged()) { return (!$display_error) ? false : ($this->trans('You cannot use this voucher', array(), 'Shop.Notifications.Error') . ' - ' . $this->trans('Please log in first', array(), 'Shop.Notifications.Error')); } return (!$display_error) ? false : $this->trans('You cannot use this voucher', array(), 'Shop.Notifications.Error'); } if ($this->minimum_amount && $check_carrier) { // Minimum amount is converted to the contextual currency $minimum_amount = $this->minimum_amount; if ($this->minimum_amount_currency != Context::getContext()->currency->id) { $minimum_amount = Tools::convertPriceFull($minimum_amount, new Currency($this->minimum_amount_currency), Context::getContext()->currency); } $cartTotal = $context->cart->getOrderTotal($this->minimum_amount_tax, Cart::ONLY_PRODUCTS); if ($this->minimum_amount_shipping) { $cartTotal += $context->cart->getOrderTotal($this->minimum_amount_tax, Cart::ONLY_SHIPPING); } $products = $context->cart->getProducts(); $cart_rules = $context->cart->getCartRules(); foreach ($cart_rules as &$cart_rule) { if ($cart_rule['gift_product']) { foreach ($products as $key => &$product) { if (empty($product['gift']) && $product['id_product'] == $cart_rule['gift_product'] && $product['id_product_attribute'] == $cart_rule['gift_product_attribute']) { $cartTotal = Tools::ps_round($cartTotal - $product[$this->minimum_amount_tax ? 'price_wt' : 'price'], (int) $context->currency->decimals * _PS_PRICE_COMPUTE_PRECISION_); } } } } if ($cartTotal < $minimum_amount) { return (!$display_error) ? false : $this->trans('You have not reached the minimum amount required to use this voucher', array(), 'Shop.Notifications.Error'); } } /* This loop checks: - if the voucher is already in the cart - if a non compatible voucher is in the cart - if there are products in the cart (gifts excluded) Important note: this MUST be the last check, because if the tested cart rule has priority over a non combinable one in the cart, we will switch them */ $nb_products = Cart::getNbProducts($context->cart->id); $otherCartRules = array(); if ($check_carrier) { $otherCartRules = $context->cart->getCartRules(); } if (count($otherCartRules)) { foreach ($otherCartRules as $otherCartRule) { if ($otherCartRule['id_cart_rule'] == $this->id && !$alreadyInCart) { return (!$display_error) ? false : $this->trans('This voucher is already in your cart', array(), 'Shop.Notifications.Error'); } if ($otherCartRule['gift_product'] && $context->cart->containsProduct($otherCartRule['gift_product'], $otherCartRule['gift_product_attribute']) > 0) { --$nb_products; } if ( ( (!$this->cart_rule_restriction && !$otherCartRule['cart_rule_restriction']) || ($this->cart_rule_restriction && $otherCartRule['cart_rule_restriction']) ) && $otherCartRule['id_cart_rule'] != $this->id) { $combinable = Db::getInstance()->getValue(' SELECT id_cart_rule_1 FROM ' . _DB_PREFIX_ . 'cart_rule_combination WHERE (id_cart_rule_1 = ' . (int) $this->id . ' AND id_cart_rule_2 = ' . (int) $otherCartRule['id_cart_rule'] . ') OR (id_cart_rule_2 = ' . (int) $this->id . ' AND id_cart_rule_1 = ' . (int) $otherCartRule['id_cart_rule'] . ')'); if (!$combinable) { $cart_rule = new CartRule((int) $otherCartRule['id_cart_rule'], $context->cart->id_lang); // The cart rules are not combinable and the cart rule currently in the cart has priority over the one tested if ($cart_rule->priority <= $this->priority) { return (!$display_error) ? false : $this->trans('This voucher is not combinable with an other voucher already in your cart: %s', array($cart_rule->name), 'Shop.Notifications.Error'); } else { // But if the cart rule that is tested has priority over the one in the cart, we remove the one in the cart and keep this new one $context->cart->removeCartRule($cart_rule->id); } } } } } if (!$nb_products) { return (!$display_error) ? false : $this->trans('Cart is empty', array(), 'Shop.Notifications.Error'); } if (!$display_error) { return true; } } }