* @copyright 2007-2014 PrestaShop SA * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) * International Registered Trademark & Property of PrestaShop SA */ class CartRuleCore extends ObjectModel { /* Filters used when retrieving the cart rules applied to a cart of when calculating the value of a reduction */ const FILTER_ACTION_ALL = 1; const FILTER_ACTION_SHIPPING = 2; const FILTER_ACTION_REDUCTION = 3; const FILTER_ACTION_GIFT = 4; const FILTER_ACTION_ALL_NOCAP = 5; const BO_ORDER_CODE_PREFIX = 'BO_ORDER_'; /* This variable controls that a free gift is offered only once, even when multi-shippping is activated and the same product is delivered in both addresses */ protected static $only_one_gift = array(); public $id; public $name; public $id_customer; public $date_from; public $date_to; public $description; public $quantity = 1; public $quantity_per_user = 1; public $priority = 1; public $partial_use = 1; public $code; public $minimum_amount; public $minimum_amount_tax; public $minimum_amount_currency; public $minimum_amount_shipping; public $country_restriction; public $carrier_restriction; public $group_restriction; public $cart_rule_restriction; public $product_restriction; public $shop_restriction; public $free_shipping; public $reduction_percent; public $reduction_amount; public $reduction_tax; public $reduction_currency; public $reduction_product; public $gift_product; public $gift_product_attribute; public $highlight; public $active = 1; public $date_add; public $date_upd; /** * @see ObjectModel::$definition */ public static $definition = array( 'table' => 'cart_rule', 'primary' => 'id_cart_rule', 'multilang' => true, 'fields' => array( 'id_customer' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId'), 'date_from' => array('type' => self::TYPE_DATE, 'validate' => 'isDate', 'required' => true), 'date_to' => array('type' => self::TYPE_DATE, 'validate' => 'isDate', 'required' => true), 'description' => array('type' => self::TYPE_STRING, 'validate' => 'isCleanHtml', 'size' => 65534), 'quantity' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedInt'), 'quantity_per_user' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedInt'), 'priority' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedInt'), 'partial_use' => array('type' => self::TYPE_BOOL, 'validate' => 'isBool'), 'code' => array('type' => self::TYPE_STRING, 'validate' => 'isCleanHtml', 'size' => 254), 'minimum_amount' => array('type' => self::TYPE_FLOAT, 'validate' => 'isFloat'), 'minimum_amount_tax' => array('type' => self::TYPE_BOOL, 'validate' => 'isBool'), 'minimum_amount_currency' =>array('type' => self::TYPE_INT, 'validate' => 'isInt'), 'minimum_amount_shipping' =>array('type' => self::TYPE_BOOL, 'validate' => 'isBool'), 'country_restriction' => array('type' => self::TYPE_BOOL, 'validate' => 'isBool'), 'carrier_restriction' => array('type' => self::TYPE_BOOL, 'validate' => 'isBool'), 'group_restriction' => array('type' => self::TYPE_BOOL, 'validate' => 'isBool'), 'cart_rule_restriction' => array('type' => self::TYPE_BOOL, 'validate' => 'isBool'), 'product_restriction' => array('type' => self::TYPE_BOOL, 'validate' => 'isBool'), 'shop_restriction' => array('type' => self::TYPE_BOOL, 'validate' => 'isBool'), 'free_shipping' => array('type' => self::TYPE_BOOL, 'validate' => 'isBool'), 'reduction_percent' => array('type' => self::TYPE_FLOAT, 'validate' => 'isPercentage'), 'reduction_amount' => array('type' => self::TYPE_FLOAT, 'validate' => 'isFloat'), 'reduction_tax' => array('type' => self::TYPE_BOOL, 'validate' => 'isBool'), 'reduction_currency' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId'), 'reduction_product' => array('type' => self::TYPE_INT, 'validate' => 'isInt'), 'gift_product' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId'), 'gift_product_attribute' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId'), 'highlight' => array('type' => self::TYPE_BOOL, 'validate' => 'isBool'), 'active' => array('type' => self::TYPE_BOOL, 'validate' => 'isBool'), 'date_add' => array('type' => self::TYPE_DATE, 'validate' => 'isDate'), 'date_upd' => array('type' => self::TYPE_DATE, 'validate' => 'isDate'), // Lang fields 'name' => array('type' => self::TYPE_STRING, 'lang' => true, 'validate' => 'isCleanHtml', 'required' => true, 'size' => 254), ), ); /** * @see ObjectModel::add() */ public function add($autodate = true, $null_values = false) { if (!parent::add($autodate, $null_values)) return false; Configuration::updateGlobalValue('PS_CART_RULE_FEATURE_ACTIVE', '1'); return true; } public function update($null_values = false) { Cache::clean('getContextualValue_'.$this->id.'_*'); return parent::update($null_values); } /** * @see ObjectModel::delete() */ public function delete() { if (!parent::delete()) return false; Configuration::updateGlobalValue('PS_CART_RULE_FEATURE_ACTIVE', CartRule::isCurrentlyUsed($this->def['table'], true)); $r = Db::getInstance()->execute('DELETE FROM `'._DB_PREFIX_.'cart_cart_rule` WHERE `id_cart_rule` = '.(int)$this->id); $r &= Db::getInstance()->execute('DELETE FROM `'._DB_PREFIX_.'cart_rule_carrier` WHERE `id_cart_rule` = '.(int)$this->id); $r &= Db::getInstance()->execute('DELETE FROM `'._DB_PREFIX_.'cart_rule_shop` WHERE `id_cart_rule` = '.(int)$this->id); $r &= Db::getInstance()->execute('DELETE FROM `'._DB_PREFIX_.'cart_rule_group` WHERE `id_cart_rule` = '.(int)$this->id); $r &= Db::getInstance()->execute('DELETE FROM `'._DB_PREFIX_.'cart_rule_country` WHERE `id_cart_rule` = '.(int)$this->id); $r &= Db::getInstance()->execute('DELETE FROM `'._DB_PREFIX_.'cart_rule_combination` WHERE `id_cart_rule_1` = '.(int)$this->id.' OR `id_cart_rule_2` = '.(int)$this->id); $r &= Db::getInstance()->execute('DELETE FROM `'._DB_PREFIX_.'cart_rule_product_rule_group` WHERE `id_cart_rule` = '.(int)$this->id); $r &= Db::getInstance()->execute('DELETE FROM `'._DB_PREFIX_.'cart_rule_product_rule` WHERE `id_product_rule_group` NOT IN (SELECT `id_product_rule_group` FROM `'._DB_PREFIX_.'cart_rule_product_rule_group`)'); $r &= Db::getInstance()->execute('DELETE FROM `'._DB_PREFIX_.'cart_rule_product_rule_value` WHERE `id_product_rule` NOT IN (SELECT `id_product_rule` FROM `'._DB_PREFIX_.'cart_rule_product_rule`)'); return $r; } /** * Copy conditions from one cart rule to an other * * @static * @param int $id_cart_rule_source * @param int $id_cart_rule_destination */ public static function copyConditions($id_cart_rule_source, $id_cart_rule_destination) { Db::getInstance()->execute(' INSERT INTO `'._DB_PREFIX_.'cart_rule_shop` (`id_cart_rule`, `id_shop`) (SELECT '.(int)$id_cart_rule_destination.', id_shop FROM `'._DB_PREFIX_.'cart_rule_shop` WHERE `id_cart_rule` = '.(int)$id_cart_rule_source.')'); Db::getInstance()->execute(' INSERT INTO `'._DB_PREFIX_.'cart_rule_carrier` (`id_cart_rule`, `id_carrier`) (SELECT '.(int)$id_cart_rule_destination.', id_carrier FROM `'._DB_PREFIX_.'cart_rule_carrier` WHERE `id_cart_rule` = '.(int)$id_cart_rule_source.')'); Db::getInstance()->execute(' INSERT INTO `'._DB_PREFIX_.'cart_rule_group` (`id_cart_rule`, `id_group`) (SELECT '.(int)$id_cart_rule_destination.', id_group FROM `'._DB_PREFIX_.'cart_rule_group` WHERE `id_cart_rule` = '.(int)$id_cart_rule_source.')'); Db::getInstance()->execute(' INSERT INTO `'._DB_PREFIX_.'cart_rule_country` (`id_cart_rule`, `id_country`) (SELECT '.(int)$id_cart_rule_destination.', id_country FROM `'._DB_PREFIX_.'cart_rule_country` WHERE `id_cart_rule` = '.(int)$id_cart_rule_source.')'); Db::getInstance()->execute(' INSERT INTO `'._DB_PREFIX_.'cart_rule_combination` (`id_cart_rule_1`, `id_cart_rule_2`) (SELECT '.(int)$id_cart_rule_destination.', IF(id_cart_rule_1 != '.(int)$id_cart_rule_source.', id_cart_rule_1, id_cart_rule_2) FROM `'._DB_PREFIX_.'cart_rule_combination` WHERE `id_cart_rule_1` = '.(int)$id_cart_rule_source.' OR `id_cart_rule_2` = '.(int)$id_cart_rule_source.')'); // Todo : should be changed soon, be must be copied too // Db::getInstance()->execute('DELETE FROM `'._DB_PREFIX_.'cart_rule_product_rule` WHERE `id_cart_rule` = '.(int)$this->id); // Db::getInstance()->execute('DELETE FROM `'._DB_PREFIX_.'cart_rule_product_rule_value` WHERE `id_product_rule` NOT IN (SELECT `id_product_rule` FROM `'._DB_PREFIX_.'cart_rule_product_rule`)'); // Copy products/category filters $products_rules_group_source = Db::getInstance()->ExecuteS(' SELECT id_product_rule_group,quantity FROM `'._DB_PREFIX_.'cart_rule_product_rule_group` WHERE `id_cart_rule` = '.(int)$id_cart_rule_source.' '); foreach ($products_rules_group_source as $product_rule_group_source) { Db::getInstance()->execute(' INSERT INTO `'._DB_PREFIX_.'cart_rule_product_rule_group` (`id_cart_rule`, `quantity`) VALUES ('.(int)$id_cart_rule_destination.','.(int)$product_rule_group_source['quantity'].')'); $id_product_rule_group_destination = Db::getInstance()->Insert_ID(); $products_rules_source = Db::getInstance()->ExecuteS(' SELECT id_product_rule,type FROM `'._DB_PREFIX_.'cart_rule_product_rule` WHERE `id_product_rule_group` = '.(int)$product_rule_group_source['id_product_rule_group'].' '); foreach ($products_rules_source as $product_rule_source) { Db::getInstance()->execute(' INSERT INTO `'._DB_PREFIX_.'cart_rule_product_rule` (`id_product_rule_group`, `type`) VALUES ('.(int)$id_product_rule_group_destination.',"'.pSQL($product_rule_source['type']).'")'); $id_product_rule_destination = Db::getInstance()->Insert_ID(); $products_rules_values_source = Db::getInstance()->ExecuteS(' SELECT id_item FROM `'._DB_PREFIX_.'cart_rule_product_rule_value` WHERE `id_product_rule` = '.(int)$product_rule_source['id_product_rule'].' '); foreach ($products_rules_values_source as $product_rule_value_source) Db::getInstance()->execute(' INSERT INTO `'._DB_PREFIX_.'cart_rule_product_rule_value` (`id_product_rule`, `id_item`) VALUES ('.(int)$id_product_rule_destination.','.(int)$product_rule_value_source['id_item'].')'); } } } /** * Retrieves the id associated to the given code * * @static * @param string $code * @return int|bool */ public static function getIdByCode($code) { if (!Validate::isCleanHtml($code)) return false; return Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue('SELECT `id_cart_rule` FROM `'._DB_PREFIX_.'cart_rule` WHERE `code` = \''.pSQL($code).'\''); } /** * @static * @param $id_lang * @param $id_customer * @param bool $active * @param bool $includeGeneric * @param bool $inStock * @param Cart|null $cart * @return array */ public static function getCustomerCartRules($id_lang, $id_customer, $active = false, $includeGeneric = true, $inStock = false, Cart $cart = null) { if (!CartRule::isFeatureActive()) return array(); $result = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS(' SELECT * 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.') WHERE ( cr.`id_customer` = '.(int)$id_customer.' OR cr.group_restriction = 1 '.($includeGeneric ? 'OR cr.`id_customer` = 0' : '').' ) AND cr.date_from < "'.date('Y-m-d H:i:s').'" AND cr.date_to > "'.date('Y-m-d H:i:s').'" '.($active ? 'AND cr.`active` = 1' : '').' '.($inStock ? 'AND cr.`quantity` > 0' : '')); // 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 $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]); } // Retrocompatibility with 1.4 discounts foreach ($result as &$cart_rule) { $cart_rule['value'] = 0; $cart_rule['minimal'] = Tools::convertPriceFull($cart_rule['minimum_amount'], new Currency($cart_rule['minimum_amount_currency']), Context::getContext()->currency); $cart_rule['cumulable'] = !$cart_rule['cart_rule_restriction']; $cart_rule['id_discount_type'] = false; if ($cart_rule['free_shipping']) $cart_rule['id_discount_type'] = Discount::FREE_SHIPPING; elseif ($cart_rule['reduction_percent'] > 0) { $cart_rule['id_discount_type'] = Discount::PERCENT; $cart_rule['value'] = $cart_rule['reduction_percent']; } elseif ($cart_rule['reduction_amount'] > 0) { $cart_rule['id_discount_type'] = Discount::AMOUNT; $cart_rule['value'] = $cart_rule['reduction_amount']; } } return $result; } /** * @param $id_customer * @return bool */ public function usedByCustomer($id_customer) { return (bool)Db::getInstance()->getValue(' SELECT id_cart_rule FROM `'._DB_PREFIX_.'order_cart_rule` ocr LEFT JOIN `'._DB_PREFIX_.'orders` o ON ocr.`id_order` = o.`id_order` WHERE ocr.`id_cart_rule` = '.(int)$this->id.' AND o.`id_customer` = '.(int)$id_customer); } /** * @static * @param $name * @return bool */ public static function cartRuleExists($name) { if (!CartRule::isFeatureActive()) return false; return (bool)Db::getInstance()->getValue(' SELECT `id_cart_rule` FROM `'._DB_PREFIX_.'cart_rule` WHERE `code` = \''.pSQL($name).'\''); } /** * @static * @param $id_customer * @return bool */ public static function deleteByIdCustomer($id_customer) { $return = true; $cart_rules = new PrestaShopCollection('CartRule'); $cart_rules->where('id_customer', '=', $id_customer); foreach ($cart_rules as $cart_rule) $return &= $cart_rule->delete(); return $return; } /** * @return array */ public function getProductRuleGroups() { if (!Validate::isLoadedObject($this) || $this->product_restriction == 0) return array(); $productRuleGroups = array(); $result = Db::getInstance()->executeS('SELECT * FROM '._DB_PREFIX_.'cart_rule_product_rule_group WHERE id_cart_rule = '.(int)$this->id); foreach ($result as $row) { if (!isset($productRuleGroups[$row['id_product_rule_group']])) $productRuleGroups[$row['id_product_rule_group']] = array('id_product_rule_group' => $row['id_product_rule_group'], 'quantity' => $row['quantity']); $productRuleGroups[$row['id_product_rule_group']]['product_rules'] = $this->getProductRules($row['id_product_rule_group']); } return $productRuleGroups; } /** * @param $id_product_rule_group * @return array ('type' => ? , 'values' => ?) */ public function getProductRules($id_product_rule_group) { if (!Validate::isLoadedObject($this) || $this->product_restriction == 0) return array(); $productRules = array(); $results = Db::getInstance()->executeS(' SELECT * FROM '._DB_PREFIX_.'cart_rule_product_rule pr LEFT JOIN '._DB_PREFIX_.'cart_rule_product_rule_value prv ON pr.id_product_rule = prv.id_product_rule WHERE pr.id_product_rule_group = '.(int)$id_product_rule_group); foreach ($results as $row) { if (!isset($productRules[$row['id_product_rule']])) $productRules[$row['id_product_rule']] = array('type' => $row['type'], 'values' => array()); $productRules[$row['id_product_rule']]['values'][] = $row['id_item']; } return $productRules; } /** * Check if this cart rule can be applied * * @param Context $context * @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) { if (!CartRule::isFeatureActive()) return false; if (!$this->active) return (!$display_error) ? false : Tools::displayError('This voucher is disabled'); if (!$this->quantity) return (!$display_error) ? false : Tools::displayError('This voucher has already been used'); if (strtotime($this->date_from) > time()) return (!$display_error) ? false : Tools::displayError('This voucher is not valid yet'); if (strtotime($this->date_to) < time()) return (!$display_error) ? false : Tools::displayError('This voucher has expired'); 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 : Tools::displayError('You cannot use this voucher anymore (usage limit reached)'); } // Get an intersection of the customer groups and the cart rule groups (if the customer is not logged in, the default group is 1) 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.')' : '= 1')); if (!$id_cart_rule) return (!$display_error) ? false : Tools::displayError('You cannot use this voucher'); } // 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 : Tools::displayError('You must choose a delivery address before applying this voucher to your order'); $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 : Tools::displayError('You cannot use this voucher in your country of delivery'); } // Check if the carrier chosen by the customer is usable with the cart rule if ($this->carrier_restriction) { if (!$context->cart->id_carrier) return (!$display_error) ? false : Tools::displayError('You must choose a carrier before applying this voucher to your order'); $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 : Tools::displayError('You cannot use this voucher with this carrier'); } // 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 : Tools::displayError('You cannot use this voucher'); } // 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 : (Tools::displayError('You cannot use this voucher').' - '.Tools::displayError('Please log in')); return (!$display_error) ? false : Tools::displayError('You cannot use this voucher'); } if ($this->minimum_amount) { // 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_DISPLAY_PRECISION_); if ($cartTotal < $minimum_amount) return (!$display_error) ? false : Tools::displayError('You have not reached the minimum amount required to use this voucher'); } /* 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 = $context->cart->getCartRules(); if (count($otherCartRules)) foreach ($otherCartRules as $otherCartRule) { if ($otherCartRule['id_cart_rule'] == $this->id && !$alreadyInCart) return (!$display_error) ? false : Tools::displayError('This voucher is already in your cart'); if ($otherCartRule['gift_product']) --$nb_products; if ($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 : Tools::displayError('This voucher is not combinable with an other voucher already in your cart:').' '.$cart_rule->name; // 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 else $context->cart->removeCartRule($cart_rule->id); } } } if (!$nb_products) return (!$display_error) ? false : Tools::displayError('Cart is empty'); if (!$display_error) return true; } protected function checkProductRestrictions(Context $context, $return_products = false, $display_error = true, $alreadyInCart = false) { $selectedProducts = array(); // Check if the products chosen by the customer are usable with the cart rule if ($this->product_restriction) { $productRuleGroups = $this->getProductRuleGroups(); foreach ($productRuleGroups as $id_product_rule_group => $productRuleGroup) { $eligibleProductsList = array(); foreach ($context->cart->getProducts() as $product) $eligibleProductsList[] = (int)$product['id_product'].'-'.(int)$product['id_product_attribute']; if (!count($eligibleProductsList)) return (!$display_error) ? false : Tools::displayError('You cannot use this voucher in an empty cart'); $productRules = $this->getProductRules($id_product_rule_group); foreach ($productRules as $productRule) { switch ($productRule['type']) { case 'attributes': $cartAttributes = Db::getInstance()->executeS(' SELECT cp.quantity, cp.`id_product`, pac.`id_attribute`, cp.`id_product_attribute` FROM `'._DB_PREFIX_.'cart_product` cp LEFT JOIN `'._DB_PREFIX_.'product_attribute_combination` pac ON cp.id_product_attribute = pac.id_product_attribute WHERE cp.`id_cart` = '.(int)$context->cart->id.' AND cp.`id_product` IN ('.implode(',', array_map('intval', $eligibleProductsList)).') AND cp.id_product_attribute > 0'); $countMatchingProducts = 0; $matchingProductsList = array(); foreach ($cartAttributes as $cartAttribute) if (in_array($cartAttribute['id_attribute'], $productRule['values'])) { $countMatchingProducts += $cartAttribute['quantity']; if ($alreadyInCart && $this->gift_product == $cartAttribute['id_product'] && $this->gift_product_attribute == $cartAttribute['id_product_attribute']) --$countMatchingProducts; $matchingProductsList[] = $cartAttribute['id_product'].'-'.$cartAttribute['id_product_attribute']; } if ($countMatchingProducts < $productRuleGroup['quantity']) return (!$display_error) ? false : Tools::displayError('You cannot use this voucher with these products'); $eligibleProductsList = CartRule::array_uintersect($eligibleProductsList, $matchingProductsList); break; case 'products': $cartProducts = Db::getInstance()->executeS(' SELECT cp.quantity, cp.`id_product` FROM `'._DB_PREFIX_.'cart_product` cp WHERE cp.`id_cart` = '.(int)$context->cart->id.' AND cp.`id_product` IN ('.implode(',', array_map('intval', $eligibleProductsList)).')'); $countMatchingProducts = 0; $matchingProductsList = array(); foreach ($cartProducts as $cartProduct) if (in_array($cartProduct['id_product'], $productRule['values'])) { $countMatchingProducts += $cartProduct['quantity']; if ($alreadyInCart && $this->gift_product == $cartProduct['id_product']) --$countMatchingProducts; $matchingProductsList[] = $cartProduct['id_product'].'-0'; } if ($countMatchingProducts < $productRuleGroup['quantity']) return (!$display_error) ? false : Tools::displayError('You cannot use this voucher with these products'); $eligibleProductsList = CartRule::array_uintersect($eligibleProductsList, $matchingProductsList); break; case 'categories': $cartCategories = Db::getInstance()->executeS(' SELECT cp.quantity, cp.`id_product`, cp.`id_product_attribute`, catp.`id_category` FROM `'._DB_PREFIX_.'cart_product` cp LEFT JOIN `'._DB_PREFIX_.'category_product` catp ON cp.id_product = catp.id_product WHERE cp.`id_cart` = '.(int)$context->cart->id.' AND cp.`id_product` IN ('.implode(',', array_map('intval', $eligibleProductsList)).') AND cp.`id_product` <> '.(int)$this->gift_product); $countMatchingProducts = 0; $matchingProductsList = array(); foreach ($cartCategories as $cartCategory) if (in_array($cartCategory['id_category'], $productRule['values']) // We also check that the product is not already in the matching product list, because there are doubles in the query results (when the product is in multiple categories) && !in_array($cartCategory['id_product'].'-'.$cartCategory['id_product_attribute'], $matchingProductsList)) { $countMatchingProducts += $cartCategory['quantity']; $matchingProductsList[] = $cartCategory['id_product'].'-'.$cartCategory['id_product_attribute']; } if ($countMatchingProducts < $productRuleGroup['quantity']) return (!$display_error) ? false : Tools::displayError('You cannot use this voucher with these products'); // Attribute id is not important for this filter in the global list, so the ids are replaced by 0 foreach ($matchingProductsList as &$matchingProduct) $matchingProduct = preg_replace('/^([0-9]+)-[0-9]+$/', '$1-0', $matchingProduct); $eligibleProductsList = CartRule::array_uintersect($eligibleProductsList, $matchingProductsList); break; case 'manufacturers': $cartManufacturers = Db::getInstance()->executeS(' SELECT cp.quantity, cp.`id_product`, p.`id_manufacturer` FROM `'._DB_PREFIX_.'cart_product` cp LEFT JOIN `'._DB_PREFIX_.'product` p ON cp.id_product = p.id_product WHERE cp.`id_cart` = '.(int)$context->cart->id.' AND cp.`id_product` IN ('.implode(',', array_map('intval', $eligibleProductsList)).')'); $countMatchingProducts = 0; $matchingProductsList = array(); foreach ($cartManufacturers as $cartManufacturer) if (in_array($cartManufacturer['id_manufacturer'], $productRule['values'])) { $countMatchingProducts += $cartManufacturer['quantity']; $matchingProductsList[] = $cartManufacturer['id_product'].'-0'; } if ($countMatchingProducts < $productRuleGroup['quantity']) return (!$display_error) ? false : Tools::displayError('You cannot use this voucher with these products'); $eligibleProductsList = CartRule::array_uintersect($eligibleProductsList, $matchingProductsList); break; case 'suppliers': $cartSuppliers = Db::getInstance()->executeS(' SELECT cp.quantity, cp.`id_product`, p.`id_supplier` FROM `'._DB_PREFIX_.'cart_product` cp LEFT JOIN `'._DB_PREFIX_.'product` p ON cp.id_product = p.id_product WHERE cp.`id_cart` = '.(int)$context->cart->id.' AND cp.`id_product` IN ('.implode(',', array_map('intval', $eligibleProductsList)).')'); $countMatchingProducts = 0; $matchingProductsList = array(); foreach ($cartSuppliers as $cartSupplier) if (in_array($cartSupplier['id_supplier'], $productRule['values'])) { $countMatchingProducts += $cartSupplier['quantity']; $matchingProductsList[] = $cartSupplier['id_product'].'-0'; } if ($countMatchingProducts < $productRuleGroup['quantity']) return (!$display_error) ? false : Tools::displayError('You cannot use this voucher with these products'); $eligibleProductsList = CartRule::array_uintersect($eligibleProductsList, $matchingProductsList); break; } if (!count($eligibleProductsList)) return (!$display_error) ? false : Tools::displayError('You cannot use this voucher with these products'); } $selectedProducts = array_merge($selectedProducts, $eligibleProductsList); } } if ($return_products) return $selectedProducts; return (!$display_error) ? true : false; } protected static function array_uintersect($array1, $array2) { $intersection = array(); foreach ($array1 as $value1) foreach ($array2 as $value2) if (CartRule::array_uintersect_compare($value1, $value2) == 0) { $intersection[] = $value1; break 1; } return $intersection; } protected static function array_uintersect_compare($a, $b) { if ($a == $b) return 0; $asplit = explode('-', $a); $bsplit = explode('-', $b); if ($asplit[0] == $bsplit[0] && (!(int)$asplit[1] || !(int)$bsplit[1])) return 0; return 1; } /** * The reduction value is POSITIVE * * @param bool $use_tax * @param Context $context * @param boolean $use_cache Allow using cache to avoid multiple free gift using multishipping * @return float|int|string */ public function getContextualValue($use_tax, Context $context = null, $filter = null, $package = null, $use_cache = true) { if (!CartRule::isFeatureActive()) return 0; if (!$context) $context = Context::getContext(); if (!$filter) $filter = CartRule::FILTER_ACTION_ALL; $all_products = $context->cart->getProducts(); $package_products = (is_null($package) ? $all_products : $package['products']); $reduction_value = 0; $cache_id = 'getContextualValue_'.(int)$this->id.'_'.(int)$use_tax.'_'.(int)$context->cart->id.'_'.(int)$filter; foreach ($package_products as $product) $cache_id .= '_'.(int)$product['id_product'].'_'.(int)$product['id_product_attribute']; if (Cache::isStored($cache_id)) return Cache::retrieve($cache_id); // Free shipping on selected carriers if ($this->free_shipping && in_array($filter, array(CartRule::FILTER_ACTION_ALL, CartRule::FILTER_ACTION_ALL_NOCAP, CartRule::FILTER_ACTION_SHIPPING))) { if (!$this->carrier_restriction) $reduction_value += $context->cart->getOrderTotal($use_tax, Cart::ONLY_SHIPPING, is_null($package) ? null : $package['products'], is_null($package) ? null : $package['id_carrier']); else { $data = Db::getInstance()->executeS(' SELECT crc.id_cart_rule, c.id_carrier 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 ($data) foreach ($data as $cart_rule) $reduction_value += $context->cart->getCarrierCost((int)$cart_rule['id_carrier'], $use_tax, $context->country); } } if (in_array($filter, array(CartRule::FILTER_ACTION_ALL, CartRule::FILTER_ACTION_ALL_NOCAP, CartRule::FILTER_ACTION_REDUCTION))) { // Discount (%) on the whole order if ($this->reduction_percent && $this->reduction_product == 0) { // Do not give a reduction on free products! $order_total = $context->cart->getOrderTotal($use_tax, Cart::ONLY_PRODUCTS, $package_products); foreach ($context->cart->getCartRules(CartRule::FILTER_ACTION_GIFT) as $cart_rule) $order_total -= Tools::ps_round($cart_rule['obj']->getContextualValue($use_tax, $context, CartRule::FILTER_ACTION_GIFT, $package), 2); $reduction_value += $order_total * $this->reduction_percent / 100; } // Discount (%) on a specific product if ($this->reduction_percent && $this->reduction_product > 0) { foreach ($package_products as $product) if ($product['id_product'] == $this->reduction_product) $reduction_value += ($use_tax ? $product['total_wt'] : $product['total']) * $this->reduction_percent / 100; } // Discount (%) on the cheapest product if ($this->reduction_percent && $this->reduction_product == -1) { $minPrice = false; $cheapest_product = null; foreach ($all_products as $product) { $price = ($use_tax ? $product['price_wt'] : $product['price']); if ($price > 0 && ($minPrice === false || $minPrice > $price)) { $minPrice = $price; $cheapest_product = $product['id_product'].'-'.$product['id_product_attribute']; } } // Check if the cheapest product is in the package $in_package = false; foreach ($package_products as $product) if ($product['id_product'].'-'.$product['id_product_attribute'] == $cheapest_product || $product['id_product'].'-0' == $cheapest_product) $in_package = true; if ($in_package) $reduction_value += $minPrice * $this->reduction_percent / 100; } // Discount (%) on the selection of products if ($this->reduction_percent && $this->reduction_product == -2) { $selected_products_reduction = 0; $selected_products = $this->checkProductRestrictions($context, true); if (is_array($selected_products)) foreach ($package_products as $product) if (in_array($product['id_product'].'-'.$product['id_product_attribute'], $selected_products) || in_array($product['id_product'].'-0', $selected_products)) { $price = ($use_tax ? $product['price_wt'] : $product['price']); $selected_products_reduction += $price * $product['cart_quantity']; } $reduction_value += $selected_products_reduction * $this->reduction_percent / 100; } // Discount (¤) if ($this->reduction_amount) { $prorata = 1; if (!is_null($package) && count($all_products)) { $total_products = $context->cart->getOrderTotal($use_tax, Cart::ONLY_PRODUCTS); if ($total_products) $prorata = $context->cart->getOrderTotal($use_tax, Cart::ONLY_PRODUCTS, $package['products']) / $total_products; } $reduction_amount = $this->reduction_amount; // If we need to convert the voucher value to the cart currency if ($this->reduction_currency != $context->currency->id) { $voucherCurrency = new Currency($this->reduction_currency); // First we convert the voucher value to the default currency if ($reduction_amount == 0 || $voucherCurrency->conversion_rate == 0) $reduction_amount = 0; else $reduction_amount /= $voucherCurrency->conversion_rate; // Then we convert the voucher value in the default currency into the cart currency $reduction_amount *= $context->currency->conversion_rate; $reduction_amount = Tools::ps_round($reduction_amount); } // If it has the same tax application that you need, then it's the right value, whatever the product! if ($this->reduction_tax == $use_tax) { // The reduction cannot exceed the products total, except when we do not want it to be limited (for the partial use calculation) if ($filter != CartRule::FILTER_ACTION_ALL_NOCAP) { $cart_amount = $context->cart->getOrderTotal($use_tax, Cart::ONLY_PRODUCTS); $reduction_amount = min($reduction_amount, $cart_amount); } $reduction_value += $prorata * $reduction_amount; } else { if ($this->reduction_product > 0) { foreach ($context->cart->getProducts() as $product) if ($product['id_product'] == $this->reduction_product) { $product_price_ti = $product['price_wt']; $product_price_te = $product['price']; $product_vat_amount = $product_price_ti - $product_price_te; if ($product_vat_amount == 0 || $product_price_te == 0) $product_vat_rate = 0; else $product_vat_rate = $product_vat_amount / $product_price_te; if ($this->reduction_tax && !$use_tax) $reduction_value += $prorata * $reduction_amount / (1 + $product_vat_rate); elseif (!$this->reduction_tax && $use_tax) $reduction_value += $prorata * $reduction_amount * (1 + $product_vat_rate); } } // Discount (¤) on the whole order elseif ($this->reduction_product == 0) { $cart_amount_ti = $context->cart->getOrderTotal(true, Cart::ONLY_PRODUCTS); $cart_amount_te = $context->cart->getOrderTotal(false, Cart::ONLY_PRODUCTS); // The reduction cannot exceed the products total, except when we do not want it to be limited (for the partial use calculation) if ($filter != CartRule::FILTER_ACTION_ALL_NOCAP) $reduction_amount = min($reduction_amount, $this->reduction_tax ? $cart_amount_ti : $cart_amount_te); $cart_vat_amount = $cart_amount_ti - $cart_amount_te; if ($cart_vat_amount == 0 || $cart_amount_te == 0) $cart_average_vat_rate = 0; else $cart_average_vat_rate = Tools::ps_round($cart_vat_amount / $cart_amount_te, 3); if ($this->reduction_tax && !$use_tax) $reduction_value += $prorata * $reduction_amount / (1 + $cart_average_vat_rate); elseif (!$this->reduction_tax && $use_tax) $reduction_value += $prorata * $reduction_amount * (1 + $cart_average_vat_rate); } /* * Reduction on the cheapest or on the selection is not really meaningful and has been disabled in the backend * Please keep this code, so it won't be considered as a bug * elseif ($this->reduction_product == -1) * elseif ($this->reduction_product == -2) */ } } } // Free gift if ((int)$this->gift_product && in_array($filter, array(CartRule::FILTER_ACTION_ALL, CartRule::FILTER_ACTION_ALL_NOCAP, CartRule::FILTER_ACTION_GIFT))) { $id_address = (is_null($package) ? 0 : $package['id_address']); foreach ($package_products as $product) if ($product['id_product'] == $this->gift_product && ($product['id_product_attribute'] == $this->gift_product_attribute || !(int)$this->gift_product_attribute)) { // The free gift coupon must be applied to one product only (needed for multi-shipping which manage multiple product lists) if (!isset(CartRule::$only_one_gift[$this->id.'-'.$this->gift_product]) || CartRule::$only_one_gift[$this->id.'-'.$this->gift_product] == $id_address || CartRule::$only_one_gift[$this->id.'-'.$this->gift_product] == 0 || $id_address == 0 || !$use_cache) { $reduction_value += ($use_tax ? $product['price_wt'] : $product['price']); if ($use_cache && (!isset(CartRule::$only_one_gift[$this->id.'-'.$this->gift_product]) || CartRule::$only_one_gift[$this->id.'-'.$this->gift_product] == 0)) CartRule::$only_one_gift[$this->id.'-'.$this->gift_product] = $id_address; break; } } } Cache::store($cache_id, $reduction_value); return $reduction_value; } /** * Make sure caches are empty * Must be called before calling multiple time getContextualValue() */ public static function cleanCache() { self::$only_one_gift = array(); } protected function getCartRuleCombinations() { $array = array(); $array['selected'] = Db::getInstance()->executeS(' SELECT cr.*, crl.*, 1 as selected 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)Context::getContext()->language->id.') WHERE cr.id_cart_rule != '.(int)$this->id.' AND ( cr.cart_rule_restriction = 0 OR cr.id_cart_rule IN ( SELECT IF(id_cart_rule_1 = '.(int)$this->id.', id_cart_rule_2, id_cart_rule_1) FROM '._DB_PREFIX_.'cart_rule_combination WHERE '.(int)$this->id.' = id_cart_rule_1 OR '.(int)$this->id.' = id_cart_rule_2 ) )'); $array['unselected'] = Db::getInstance()->executeS(' SELECT cr.*, crl.*, 1 as selected FROM '._DB_PREFIX_.'cart_rule cr INNER JOIN '._DB_PREFIX_.'cart_rule_lang crl ON (cr.id_cart_rule = crl.id_cart_rule AND crl.id_lang = '.(int)Context::getContext()->language->id.') LEFT JOIN '._DB_PREFIX_.'cart_rule_combination crc1 ON (cr.id_cart_rule = crc1.id_cart_rule_1 AND crc1.id_cart_rule_2 = '.(int)$this->id.') LEFT JOIN '._DB_PREFIX_.'cart_rule_combination crc2 ON (cr.id_cart_rule = crc2.id_cart_rule_2 AND crc2.id_cart_rule_1 = '.(int)$this->id.') WHERE cr.cart_rule_restriction = 1 AND cr.id_cart_rule != '.(int)$this->id.' AND crc1.id_cart_rule_1 IS NULL AND crc2.id_cart_rule_1 IS NULL'); return $array; } public function getAssociatedRestrictions($type, $active_only, $i18n) { $array = array('selected' => array(), 'unselected' => array()); if (!in_array($type, array('country', 'carrier', 'group', 'cart_rule', 'shop'))) return false; $shop_list = ''; if ($type == 'shop') { $shops = Context::getContext()->employee->getAssociatedShops(); if (count($shops)) $shop_list = ' AND t.id_shop IN ('.implode(array_map('intval', $shops), ',').') '; } if (!Validate::isLoadedObject($this) OR $this->{$type.'_restriction'} == 0) { $array['selected'] = Db::getInstance()->executeS(' SELECT t.*'.($i18n ? ', tl.*' : '').', 1 as selected FROM `'._DB_PREFIX_.$type.'` t '.($i18n ? 'LEFT JOIN `'._DB_PREFIX_.$type.'_lang` tl ON (t.id_'.$type.' = tl.id_'.$type.' AND tl.id_lang = '.(int)Context::getContext()->language->id.')' : '').' WHERE 1 '.($active_only ? 'AND t.active = 1' : '').' '.(in_array($type, array('carrier', 'shop')) ? ' AND t.deleted = 0' : '').' '.($type == 'cart_rule' ? 'AND t.id_cart_rule != '.(int)$this->id : ''). $shop_list. ' ORDER BY name ASC'); } else { if ($type == 'cart_rule') $array = $this->getCartRuleCombinations(); else { $resource = Db::getInstance()->query(' SELECT t.*'.($i18n ? ', tl.*' : '').', IF(crt.id_'.$type.' IS NULL, 0, 1) as selected FROM `'._DB_PREFIX_.$type.'` t '.($i18n ? 'LEFT JOIN `'._DB_PREFIX_.$type.'_lang` tl ON (t.id_'.$type.' = tl.id_'.$type.' AND tl.id_lang = '.(int)Context::getContext()->language->id.')' : '').' LEFT JOIN (SELECT id_'.$type.' FROM `'._DB_PREFIX_.'cart_rule_'.$type.'` WHERE id_cart_rule = '.(int)$this->id.') crt ON t.id_'.($type == 'carrier' ? 'reference' : $type).' = crt.id_'.$type.' WHERE 1 '.($active_only ? ' AND t.active = 1' : ''). $shop_list .(in_array($type, array('carrier', 'shop')) ? ' AND t.deleted = 0' : ''). ' ORDER BY name ASC', false); while ($row = Db::getInstance()->nextRow($resource)) $array[($row['selected'] || $this->{$type.'_restriction'} == 0) ? 'selected' : 'unselected'][] = $row; } } return $array; } public static function autoRemoveFromCart($context = null) { if (!$context) $context = Context::getContext(); if (!CartRule::isFeatureActive() || !Validate::isLoadedObject($context->cart)) return array(); static $errors = array(); foreach ($context->cart->getCartRules() as $cart_rule) { if ($error = $cart_rule['obj']->checkValidity($context, true)) { $context->cart->removeCartRule($cart_rule['obj']->id); $context->cart->update(); $errors[] = $error; } } return $errors; } /** * @static * @param Context|null $context * @return mixed */ public static function autoAddToCart(Context $context = null) { if ($context === null) $context = Context::getContext(); if (!CartRule::isFeatureActive() || !Validate::isLoadedObject($context->cart)) return; $sql = ' SELECT cr.* FROM '._DB_PREFIX_.'cart_rule cr LEFT JOIN '._DB_PREFIX_.'cart_rule_shop crs ON cr.id_cart_rule = crs.id_cart_rule LEFT JOIN '._DB_PREFIX_.'cart_rule_carrier crca ON cr.id_cart_rule = crca.id_cart_rule '.($context->cart->id_carrier ? 'LEFT JOIN '._DB_PREFIX_.'carrier c ON (c.id_reference = crca.id_carrier AND c.deleted = 0)' : '').' LEFT JOIN '._DB_PREFIX_.'cart_rule_country crco ON cr.id_cart_rule = crco.id_cart_rule WHERE cr.active = 1 AND cr.code = "" AND cr.quantity > 0 AND cr.date_from < "'.date('Y-m-d H:i:s').'" AND cr.date_to > "'.date('Y-m-d H:i:s').'" AND ( cr.id_customer = 0 '.($context->customer->id ? 'OR cr.id_customer = '.(int)$context->cart->id_customer : '').' ) AND ( cr.`carrier_restriction` = 0 '.($context->cart->id_carrier ? 'OR c.id_carrier = '.(int)$context->cart->id_carrier : '').' ) AND ( cr.`shop_restriction` = 0 '.((Shop::isFeatureActive() && $context->shop->id) ? 'OR crs.id_shop = '.(int)$context->shop->id : '').' ) AND ( cr.`group_restriction` = 0 '.($context->customer->id ? 'OR 0 < ( SELECT cg.`id_group` FROM `'._DB_PREFIX_.'customer_group` cg INNER JOIN `'._DB_PREFIX_.'cart_rule_group` crg ON cg.id_group = crg.id_group WHERE cr.`id_cart_rule` = crg.`id_cart_rule` AND cg.`id_customer` = '.(int)$context->customer->id.' LIMIT 1 )' : '').' ) AND ( cr.`reduction_product` <= 0 OR cr.`reduction_product` IN ( SELECT `id_product` FROM `'._DB_PREFIX_.'cart_product` WHERE `id_cart` = '.(int)$context->cart->id.' ) ) AND cr.id_cart_rule NOT IN (SELECT id_cart_rule FROM '._DB_PREFIX_.'cart_cart_rule WHERE id_cart = '.(int)$context->cart->id.') ORDER BY priority'; $result = Db::getInstance()->executeS($sql); if ($result) { $cart_rules = ObjectModel::hydrateCollection('CartRule', $result); if ($cart_rules) foreach ($cart_rules as $cart_rule) if ($cart_rule->checkValidity($context, false, false)) $context->cart->addCartRule($cart_rule->id); } } /** * @static * @return bool */ public static function isFeatureActive() { static $is_feature_active = null; if ($is_feature_active === null) $is_feature_active = (bool)Configuration::get('PS_CART_RULE_FEATURE_ACTIVE'); return $is_feature_active; } /* When an entity associated to a product rule (product, category, attribute, supplier, manufacturer...) is deleted, the product rules must be updated */ public static function cleanProductRuleIntegrity($type, $list) { // Type must be available in the 'type' enum of the table cart_rule_product_rule if (!in_array($type, array('products', 'categories', 'attributes', 'manufacturers', 'suppliers'))) return false; // This check must not be removed because this var is used a few lines below $list = (is_array($list) ? implode(',', array_map('intval', $list)) : (int)$list); if (!preg_match('/^[0-9,]+$/', $list)) return false; // Delete associated restrictions on cart rules Db::getInstance()->execute(' DELETE crprv FROM `'._DB_PREFIX_.'cart_rule_product_rule` crpr LEFT JOIN `'._DB_PREFIX_.'cart_rule_product_rule_value` crprv ON crpr.`id_product_rule` = crprv.`id_product_rule` WHERE crpr.`type` = "'.pSQL($type).'" AND crprv.`id_item` IN ('.$list.')'); // $list is checked a few lines above // Delete the product rules that does not have any values if (Db::getInstance()->Affected_Rows() > 0) Db::getInstance()->execute(' DELETE FROM `'._DB_PREFIX_.'cart_rule_product_rule` WHERE `id_product_rule` NOT IN (SELECT id_product_rule FROM `'._DB_PREFIX_.'cart_rule_product_rule_value`)'); // If the product rules were the only conditions of a product rule group, delete the product rule group if (Db::getInstance()->Affected_Rows() > 0) Db::getInstance()->execute(' DELETE FROM `'._DB_PREFIX_.'cart_rule_product_rule_group` WHERE `id_product_rule_group` NOT IN (SELECT id_product_rule_group FROM `'._DB_PREFIX_.'cart_rule_product_rule`)'); // If the product rule group were the only restrictions of a cart rule, update de cart rule restriction cache if (Db::getInstance()->Affected_Rows() > 0) Db::getInstance()->execute(' UPDATE `'._DB_PREFIX_.'cart_rule` cr LEFT JOIN `'._DB_PREFIX_.'cart_rule_product_rule_group` crprg ON cr.id_cart_rule = crprg.id_cart_rule SET product_restriction = IF(crprg.id_product_rule_group IS NULL, 0, 1)'); return true; } /** * @static * @param $name * @param $id_lang * @return array */ public static function getCartsRuleByCode($name, $id_lang, $extended = false) { return Db::getInstance()->executeS(' SELECT cr.*, crl.* 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.') WHERE code LIKE \'%'.pSQL($name).'%\'' .($extended ? ' OR name LIKE \'%'.pSQL($name).'%\'' : '')); } }