/home/sharedstore/public_html/prestashop.sharedstore.ma/classes
NameSizeModeActions
assets/-0755rm
cache/-0755rm
checkout/-0755rm
container/-0755rm
controller/-0755rm
db/-0755rm
exception/-0755rm
form/-0755rm
helper/-0755rm
lang/-0755rm
log/-0755rm
module/-0755rm
order/-0755rm
pdf/-0755rm
product/-0755rm
proxy/-0755rm
range/-0755rm
shop/-0755rm
Smarty/-0755rm
stock/-0755rm
tax/-0755rm
tree/-0755rm
webservice/-0755rm
.htaccess1700644editdlrm
Access.php135730644editdlrm
Address.php230160644editdlrm
AddressChecksumCore.php18510644editdlrm
AddressFormat.php251250644editdlrm
Alias.php51060644editdlrm
Attachment.php115570644editdlrm
AttributeGroup.php134880644editdlrm
Carrier.php656660644editdlrm
Cart.php2211920644editdlrm
CartRule.php927650644editdlrm
Category.php891710644editdlrm
Chart.php41770644editdlrm
ChecksumInterface.php12460644editdlrm
CMS.php117250644editdlrm
CMSCategory.php241480644editdlrm
CMSRole.php18220644editdlrm
Combination.php180120644editdlrm
Configuration.php271560644editdlrm
ConfigurationKPI.php91260644editdlrm
ConfigurationTest.php123090644editdlrm
Connection.php102620644editdlrm
ConnectionsSource.php49260644editdlrm
Contact.php39540644editdlrm
Context.php183720644editdlrm
Cookie.php178060644editdlrm
Country.php167230644editdlrm
CSV.php31100644editdlrm
Currency.php371670644editdlrm
Curve.php29930644editdlrm
Customer.php535760644editdlrm
CustomerAddress.php12950644editdlrm
CustomerMessage.php58900644editdlrm
CustomerSession.php27180644editdlrm
CustomerThread.php98480644editdlrm
Customization.php137540644editdlrm
CustomizationField.php29150644editdlrm
DateRange.php25320644editdlrm
Delivery.php33170644editdlrm
Dispatcher.php488100644editdlrm
Employee.php240620644editdlrm
EmployeeSession.php27180644editdlrm
Feature.php116560644editdlrm
FeatureFlag.php32260644editdlrm
FeatureValue.php82490644editdlrm
FileUploader.php38200644editdlrm
Gender.php28760644editdlrm
Group.php153230644editdlrm
GroupReduction.php90250644editdlrm
Guest.php85290644editdlrm
Hook.php509090644editdlrm
Image.php324180644editdlrm
ImageManager.php325490644editdlrm
ImageType.php71550644editdlrm
index.php13720644editdlrm
Language.php635470644editdlrm
Link.php638140644editdlrm
LocalizationPack.php236040644editdlrm
Mail.php343120644editdlrm
Manufacturer.php217450644editdlrm
ManufacturerAddress.php13110644editdlrm
Media.php330210644editdlrm
Message.php66260644editdlrm
Meta.php185770644editdlrm
Notification.php84700644editdlrm
ObjectModel.php837730644editdlrm
Pack.php263320644editdlrm
Page.php45980644editdlrm
PaymentFree.php13620644editdlrm
PaymentModule.php611970644editdlrm
PhpEncryption.php32910644editdlrm
PhpEncryptionEngine.php39490644editdlrm
PrestaShopAutoload.php97990644editdlrm
PrestaShopBackup.php117020644editdlrm
PrestaShopCollection.php217900644editdlrm
PrestaShopLogger.php85230644editdlrm
Product.php3180560644editdlrm
ProductAssembler.php68950644editdlrm
ProductAttribute.php138080644editdlrm
ProductDownload.php102940644editdlrm
ProductPresenterFactory.php39830644editdlrm
ProductSale.php130000644editdlrm
ProductSupplier.php85370644editdlrm
Profile.php85230644editdlrm
QqUploadedFileForm.php53170644editdlrm
QqUploadedFileXhr.php58250644editdlrm
QuickAccess.php64020644editdlrm
RequestSql.php214530644editdlrm
Risk.php24190644editdlrm
Search.php592290644editdlrm
SearchEngine.php28790644editdlrm
SpecificPrice.php332120644editdlrm
SpecificPriceRule.php143740644editdlrm
State.php79560644editdlrm
Store.php63940644editdlrm
Supplier.php183390644editdlrm
SupplierAddress.php12990644editdlrm
Tab.php222230644editdlrm
Tag.php135580644editdlrm
Tools.php1409030644editdlrm
Translate.php120590644editdlrm
TranslatedConfiguration.php47920644editdlrm
Upgrader.php118590644editdlrm
Uploader.php111040644editdlrm
Validate.php398010644editdlrm
ValidateConstraintTranslator.php26110644editdlrm
WarehouseAddress.php13020644editdlrm
Zone.php35000644editdlrm
Edit: /home/sharedstore/public_html/prestashop.sharedstore.ma/classes/CartRule.php (92765B)
* @copyright Since 2007 PrestaShop SA and Contributors * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) */ /** * Class CartRuleCore. */ class CartRuleCore extends ObjectModel { /* Filters used when retrieving the cart rules applied to a cart of when calculating the value of a reduction */ public const FILTER_ACTION_ALL = 1; public const FILTER_ACTION_SHIPPING = 2; public const FILTER_ACTION_REDUCTION = 3; public const FILTER_ACTION_GIFT = 4; public const FILTER_ACTION_ALL_NOCAP = 5; public 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. * * @var array */ protected static $only_one_gift = []; public $id; public $name; public $id_customer; /** * @var string|null */ public $date_from; /** * @var string|null */ public $date_to; public $description; public $quantity = 1; public $quantity_per_user = 1; public $priority = 1; /** @var bool */ public $partial_use = true; public $code; public $minimum_amount; /** @var bool */ public $minimum_amount_tax; public $minimum_amount_currency; /** @var bool */ public $minimum_amount_shipping; /** @var bool */ public $country_restriction; /** @var bool */ public $carrier_restriction; /** @var bool */ public $group_restriction; /** @var bool */ public $cart_rule_restriction; /** @var bool */ public $product_restriction; /** @var bool */ public $shop_restriction; /** @var bool */ public $free_shipping; public $reduction_percent; public $reduction_amount; /** * @var bool is this voucher value tax included (false = tax excluded value) */ public $reduction_tax; /** @var int */ public $reduction_currency; public $reduction_product; /** @var bool */ public $reduction_exclude_special; public $gift_product; public $gift_product_attribute; /** @var bool */ public $highlight; /** @var bool */ public $active = true; public $date_add; public $date_upd; protected static $cartAmountCache = []; /** * @see ObjectModel::$definition */ public static $definition = [ 'table' => 'cart_rule', 'primary' => 'id_cart_rule', 'multilang' => true, 'fields' => [ 'id_customer' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId'], 'date_from' => ['type' => self::TYPE_DATE, 'validate' => 'isDate', 'required' => true], 'date_to' => ['type' => self::TYPE_DATE, 'validate' => 'isDate', 'required' => true], 'description' => ['type' => self::TYPE_STRING, 'validate' => 'isCleanHtml', 'size' => 4194303], 'quantity' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedInt'], 'quantity_per_user' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedInt'], 'priority' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedInt'], 'partial_use' => ['type' => self::TYPE_BOOL, 'validate' => 'isBool'], 'code' => ['type' => self::TYPE_STRING, 'validate' => 'isCleanHtml', 'size' => 254], 'minimum_amount' => ['type' => self::TYPE_FLOAT, 'validate' => 'isFloat'], 'minimum_amount_tax' => ['type' => self::TYPE_BOOL, 'validate' => 'isBool'], 'minimum_amount_currency' => ['type' => self::TYPE_INT, 'validate' => 'isInt'], 'minimum_amount_shipping' => ['type' => self::TYPE_BOOL, 'validate' => 'isBool'], 'country_restriction' => ['type' => self::TYPE_BOOL, 'validate' => 'isBool'], 'carrier_restriction' => ['type' => self::TYPE_BOOL, 'validate' => 'isBool'], 'group_restriction' => ['type' => self::TYPE_BOOL, 'validate' => 'isBool'], 'cart_rule_restriction' => ['type' => self::TYPE_BOOL, 'validate' => 'isBool'], 'product_restriction' => ['type' => self::TYPE_BOOL, 'validate' => 'isBool'], 'shop_restriction' => ['type' => self::TYPE_BOOL, 'validate' => 'isBool'], 'free_shipping' => ['type' => self::TYPE_BOOL, 'validate' => 'isBool'], 'reduction_percent' => ['type' => self::TYPE_FLOAT, 'validate' => 'isPercentage'], 'reduction_amount' => ['type' => self::TYPE_FLOAT, 'validate' => 'isFloat'], 'reduction_tax' => ['type' => self::TYPE_BOOL, 'validate' => 'isBool'], 'reduction_currency' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId'], 'reduction_product' => ['type' => self::TYPE_INT, 'validate' => 'isInt'], 'reduction_exclude_special' => ['type' => self::TYPE_BOOL, 'validate' => 'isBool'], 'gift_product' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId'], 'gift_product_attribute' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId'], 'highlight' => ['type' => self::TYPE_BOOL, 'validate' => 'isBool'], 'active' => ['type' => self::TYPE_BOOL, 'validate' => 'isBool'], 'date_add' => ['type' => self::TYPE_DATE, 'validate' => 'isDate'], 'date_upd' => ['type' => self::TYPE_DATE, 'validate' => 'isDate'], /* Lang fields */ 'name' => [ 'type' => self::TYPE_HTML, 'lang' => true, 'required' => true, 'size' => 254, ], ], ]; public static function resetStaticCache() { static::$cartAmountCache = []; } /** * Adds current CartRule as a new Object to the database. * * @param bool $autodate Automatically set `date_upd` and `date_add` columns * @param bool $null_values Whether we want to use NULL values instead of empty quotes values * * @return bool Indicates whether the CartRule has been successfully added * * @throws PrestaShopDatabaseException * @throws PrestaShopException */ public function add($autodate = true, $null_values = false) { if (!$this->reduction_currency) { $this->reduction_currency = Currency::getDefaultCurrencyId(); } if (!parent::add($autodate, $null_values)) { return false; } Configuration::updateGlobalValue('PS_CART_RULE_FEATURE_ACTIVE', '1'); return true; } /** * Updates the current object in the database. * * @param bool $null_values Whether we want to use NULL values instead of empty quotes values * * @return bool Indicates whether the CartRule has been successfully updated * * @throws PrestaShopDatabaseException * @throws PrestaShopException */ public function update($null_values = false) { Cache::clean('getContextualValue_' . $this->id . '_*'); if (!$this->reduction_currency) { $this->reduction_currency = Currency::getDefaultCurrencyId(); } if (!parent::update($null_values)) { return false; } Configuration::updateGlobalValue( 'PS_CART_RULE_FEATURE_ACTIVE', CartRule::isCurrentlyUsed($this->def['table'], true) ); return true; } /** * Deletes current CartRule from the database. * * @return bool True if delete was successful * * @throws PrestaShopException */ public function delete() { if (!parent::delete()) { return false; } Configuration::updateGlobalValue( 'PS_CART_RULE_FEATURE_ACTIVE', CartRule::isCurrentlyUsed($this->def['table'], true) ); $r = Db::getInstance()->delete('cart_cart_rule', '`id_cart_rule` = ' . (int) $this->id); $r &= Db::getInstance()->delete('cart_rule_carrier', '`id_cart_rule` = ' . (int) $this->id); $r &= Db::getInstance()->delete('cart_rule_shop', '`id_cart_rule` = ' . (int) $this->id); $r &= Db::getInstance()->delete('cart_rule_group', '`id_cart_rule` = ' . (int) $this->id); $r &= Db::getInstance()->delete('cart_rule_country', '`id_cart_rule` = ' . (int) $this->id); $r &= Db::getInstance()->delete('cart_rule_combination', '`id_cart_rule_1` = ' . (int) $this->id . ' OR `id_cart_rule_2` = ' . (int) $this->id); $r &= Db::getInstance()->delete('cart_rule_product_rule_group', '`id_cart_rule` = ' . (int) $this->id); $r &= Db::getInstance()->delete('cart_rule_product_rule', 'NOT EXISTS (SELECT 1 FROM `' . _DB_PREFIX_ . 'cart_rule_product_rule_group` WHERE `' . _DB_PREFIX_ . 'cart_rule_product_rule`.`id_product_rule_group` = `' . _DB_PREFIX_ . 'cart_rule_product_rule_group`.`id_product_rule_group`)'); $r &= Db::getInstance()->delete('cart_rule_product_rule_value', 'NOT EXISTS (SELECT 1 FROM `' . _DB_PREFIX_ . 'cart_rule_product_rule` WHERE `' . _DB_PREFIX_ . 'cart_rule_product_rule_value`.`id_product_rule` = `' . _DB_PREFIX_ . 'cart_rule_product_rule`.`id_product_rule`)'); return (bool) $r; } /** * Copy conditions from one CartRule to another. * * @param int $id_cart_rule_source Source CartRule ID * @param int $id_cart_rule_destination Destination CartRule ID */ 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 CartRule ID associated with the given voucher code. * * @param string $code Voucher code * * @return int|bool CartRule ID * false if not found */ 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) . '\'' ); } /** * Check if some cart rules exists today for the given customer. * * @param int $idCustomer * * @return bool */ public static function haveCartRuleToday($idCustomer) { static $haveCartRuleToday = []; if (!isset($haveCartRuleToday[$idCustomer])) { $start_date = date('Y-m-d 00:00:00'); $end_date = date('Y-m-d 23:59:59'); $sql = 'SELECT 1 FROM `' . _DB_PREFIX_ . 'cart_rule` ' . 'WHERE ((date_to >= "' . $start_date . '" AND date_to <= "' . $end_date . '") OR (date_from >= "' . $start_date . '" AND date_from <= "' . $end_date . '") OR (date_from < "' . $start_date . '" AND date_to > "' . $end_date . '")) AND `id_customer` IN (0,' . (int) $idCustomer . ')'; $haveCartRuleToday[$idCustomer] = Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue($sql); } return !empty($haveCartRuleToday[$idCustomer]); } /** * 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 vouchers that don't have specific customer * @param bool $inStock Vouchers that have "total quantity" remaining * @param CartCore|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, CartCore $cart = null, $free_shipping_only = false, $highlight_only = false ) { if (!CartRule::isFeatureActive() || !CartRule::haveCartRuleToday($id_customer)) { return []; } // Basic part of the query, we are selecting all cart rules $sql = ' SELECT SQL_NO_CACHE * 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 . ')'; // We will definitely include vouchers for this specific customer $sql .= ' WHERE (cr.`id_customer` = ' . (int) $id_customer; // And if required, all the generic ones, that don't have any specific customer set if ($includeGeneric && (int) $id_customer !== 0) { $sql .= ' OR cr.`id_customer` = 0'; } $sql .= ')'; // Then, conditions for date, voucher active property and total amount of vouchers in stock $sql .= ' AND NOW() BETWEEN cr.date_from AND cr.date_to ' . ($active ? 'AND cr.`active` = 1' : '') . ' ' . ($inStock ? 'AND cr.`quantity` > 0' : ''); // If we want to select only vouchers that have free shipping as the action if ($free_shipping_only) { $sql .= ' AND free_shipping = 1 AND carrier_restriction = 1'; } // If we want to select only vouchers with "Highlight" option activated if ($highlight_only) { $sql .= ' AND highlight = 1 AND code NOT LIKE "' . pSQL(CartRule::BO_ORDER_CODE_PREFIX) . '%"'; } $result = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS($sql, true, false); if (empty($result)) { return []; } /* * Remove cart rule that does not match the customer groups. * Even if empty $id_customer was provided, we will still get * a visitor group. */ $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, $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, $cart->id)) { foreach ($result as $key => $cart_rule) { if ($cart_rule['product_restriction']) { $cr = new CartRule((int) $cart_rule['id_cart_rule']); $r = $cr->checkProductRestrictionsFromCart(Context::getContext()->cart, false, false); if ($r !== false) { continue; } unset($result[$key]); } } } /* * Now, we check the country restrictions on this cart rule. * The rule is will be displayed, if the customer has at least one * address with country in the allowed list. * * If the customer has no addresses, we won't display anything. */ foreach ($result as $key => $cart_rule) { if ($cart_rule['country_restriction']) { /* * If the rule has country restriction and there is no customer ID * provided, it doesn't make sense to check anything else. * * This customer can't have any addresses, thus no cart rule will be valid. */ if (empty($id_customer)) { unset($result[$key]); continue; } /* * Now, when we are sure that we have some sensible customer ID to validate upon, * we can check if he has any valid addresses that intersect with the allowed countries * in the cart rule. So he will be able to use it. */ $validAddressExists = Db::getInstance()->getValue(' SELECT crc.id_cart_rule FROM ' . _DB_PREFIX_ . 'cart_rule_country crc INNER JOIN ' . _DB_PREFIX_ . 'address a ON a.id_customer = ' . (int) $id_customer . ' AND a.deleted = 0 AND a.id_country = crc.id_country WHERE crc.id_cart_rule = ' . (int) $cart_rule['id_cart_rule'] ); if (empty($validAddressExists)) { unset($result[$key]); } } } return $result; } /** * Get all (inactive too) CartRules for a given customer * * @param int $customerId * * @return array */ public static function getAllCustomerCartRules( int $customerId ): array { $query = new DbQuery(); $query->select('cr.*, crl.name'); $query->from('cart_rule', 'cr'); $query->where('cr.id_customer = ' . $customerId); $query->leftJoin('cart_rule_lang', 'crl', 'cr.id_cart_rule = crl.id_cart_rule AND crl.id_lang = ' . (int) Configuration::get('PS_LANG_DEFAULT')); $query->orderBy('cr.active DESC, cr.id_customer DESC'); $result = Db::getInstance()->executeS($query); if (!$result) { return []; } foreach ($result as &$cart_rule) { if ($cart_rule['quantity_per_user']) { $quantity_used = Order::getDiscountsCustomer($customerId, (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; } } return $result; } public static function getCustomerHighlightedDiscounts( $languageId, $customerId, CartCore $cart ) { return static::getCustomerCartRules( $languageId, $customerId, $active = true, $includeGeneric = true, $inStock = true, $cart, $freeShippingOnly = false, $highlightOnly = true ); } /** * Check if the CartRule has been used by the given Customer. * * @param int $id_customer Customer ID * * @return bool Indicates if the CartRule has been used by a Customer * The Cart must have been converted into an Order, otherwise it doesn't count */ 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.`deleted` = 0 AND ocr.`id_cart_rule` = ' . (int) $this->id . ' AND o.`id_customer` = ' . (int) $id_customer); } /** * Check if the CartRule exists. * * @param string $code CartRule code * * @return bool Indicates whether the CartRule can be found */ public static function cartRuleExists($code) { if (!CartRule::isFeatureActive()) { return false; } return (bool) Db::getInstance()->getValue(' SELECT `id_cart_rule` FROM `' . _DB_PREFIX_ . 'cart_rule` WHERE `code` = \'' . pSQL($code) . '\'', false); } /** * Delete CartRules by Customer ID. * * @param int $id_customer Customer ID * * @return bool Indicates if the CartRules were successfully deleted */ public static function deleteByIdCustomer($id_customer) { // Remove cart rules only if we got some sensible ID of a customer. // If we would pass zero further below, it would delete all non-customer-restricted cart rules. if (empty($id_customer)) { return false; } $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 []; } $productRuleGroups = []; $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']] = ['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 int $id_product_rule_group * * @return array ('type' => ? , 'values' => ?) */ public function getProductRules($id_product_rule_group) { if (!Validate::isLoadedObject($this) || $this->product_restriction == 0) { return []; } $productRules = []; $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']] = ['type' => $row['type'], 'values' => []]; } $productRules[$row['id_product_rule']]['values'][] = $row['id_item']; } return $productRules; } /** * 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 * @param bool $check_carrier * @param bool $useOrderPrices * * @return bool|mixed|string */ public function checkValidity(Context $context, $alreadyInCart = false, $display_error = true, $check_carrier = true, $useOrderPrices = false) { if (!CartRule::isFeatureActive()) { return false; } $cart = $context->cart; // All these checks are necessary when you add the cart rule the first time, so when it's not in cart yet // However when it's in the cart and you are checking if the cart rule is still valid (when performing auto remove) // these rules are outdated For example: // - the cart rule can now be disabled but it was at the time it was applied, so it doesn't need to be removed // - the current date is not in the range any more but it was at the time // - the quantity is now zero but it was not when it was added if (!$alreadyInCart) { if (!$this->active) { return (!$display_error) ? false : $this->trans('This voucher is disabled', [], 'Shop.Notifications.Error'); } if (!$this->quantity) { return (!$display_error) ? false : $this->trans('This voucher has already been used', [], 'Shop.Notifications.Error'); } if (strtotime($this->date_from) > time()) { return (!$display_error) ? false : $this->trans('This voucher is not valid yet', [], 'Shop.Notifications.Error'); } if (strtotime($this->date_to) < time()) { return (!$display_error) ? false : $this->trans('This voucher has expired', [], 'Shop.Notifications.Error'); } } if ($cart->id_customer) { $quantityUsed = Db::getInstance()->getValue(' SELECT count(*) FROM `' . _DB_PREFIX_ . 'orders` o LEFT JOIN `' . _DB_PREFIX_ . 'order_cart_rule` ocr ON o.`id_order` = ocr.`id_order` WHERE o.`id_customer` = ' . $cart->id_customer . ' AND ocr.`deleted` = 0 AND ocr.`id_cart_rule` = ' . (int) $this->id . ' AND ' . (int) Configuration::get('PS_OS_ERROR') . ' != o.`current_state` '); if ($alreadyInCart) { // Sometimes a cart rule is already in a cart, but the cart is not yet attached to an order (when logging // in for example), these cart rules are not taken into account by the query above: // so we count cart rules that are already linked to the current cart but not attached to an order yet. $quantityUsed += (int) Db::getInstance()->getValue(' SELECT count(*) FROM `' . _DB_PREFIX_ . 'cart_cart_rule` ccr INNER JOIN `' . _DB_PREFIX_ . 'cart` c ON c.id_cart = ccr.id_cart LEFT JOIN `' . _DB_PREFIX_ . 'orders` o ON o.id_cart = c.id_cart WHERE c.id_customer = ' . $cart->id_customer . ' AND c.id_cart = ' . $cart->id . ' AND ccr.id_cart_rule = ' . (int) $this->id . ' AND o.id_order IS NULL '); } else { // When checking the cart rules present in that cart the request result is accurate // When we check if using the cart rule one more time is valid then we increment this value ++$quantityUsed; } if ($quantityUsed > $this->quantity_per_user) { return (!$display_error) ? false : $this->trans('You cannot use this voucher anymore (usage limit reached)', [], '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 ' . ($cart->id_customer ? 'IN (SELECT cg.id_group FROM ' . _DB_PREFIX_ . 'customer_group cg WHERE cg.id_customer = ' . (int) $cart->id_customer . ')' : '= ' . (int) Configuration::get('PS_UNIDENTIFIED_GROUP'))); if (!$id_cart_rule) { return (!$display_error) ? false : $this->trans('You cannot use this voucher', [], 'Shop.Notifications.Error'); } } // Check if the customer delivery address is usable with the cart rule if ($this->country_restriction) { if (!$cart->id_address_delivery) { return (!$display_error) ? false : $this->trans('You must choose a delivery address before applying this voucher to your order', [], '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) $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', [], 'Shop.Notifications.Error'); } } // Check if the carrier chosen by the customer is usable with the cart rule if ($this->carrier_restriction && $check_carrier) { if (!$cart->id_carrier) { return (!$display_error) ? false : $this->trans('You must choose a carrier before applying this voucher to your order', [], '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) $cart->id_carrier); if (!$id_cart_rule) { return (!$display_error) ? false : $this->trans('You cannot use this voucher with this carrier', [], 'Shop.Notifications.Error'); } } if ($this->reduction_exclude_special) { $products = $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', [], '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', [], 'Shop.Notifications.Error'); } } // Check if the products chosen by the customer are usable with the cart rule if ($this->product_restriction) { $r = $this->checkProductRestrictionsFromCart($context->cart, 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 && $cart->id_customer != $this->id_customer) { if (!Context::getContext()->customer->isLogged()) { return (!$display_error) ? false : ($this->trans('You cannot use this voucher', [], 'Shop.Notifications.Error') . ' - ' . $this->trans('Please log in first', [], 'Shop.Notifications.Error')); } return (!$display_error) ? false : $this->trans('You cannot use this voucher', [], '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 = $cart->getOrderTotal( $this->minimum_amount_tax, Cart::ONLY_PRODUCTS, null, null, false, $useOrderPrices ); if ($this->minimum_amount_shipping) { $cartTotal += $cart->getOrderTotal( $this->minimum_amount_tax, Cart::ONLY_SHIPPING, null, null, false, $useOrderPrices ); } $products = $cart->getProducts(); $cart_rules = $cart->getCartRules(CartRule::FILTER_ACTION_ALL, false); foreach ($cart_rules as $cart_rule) { if ($cart_rule['gift_product']) { foreach ($products as $key => &$product) { if (empty($product['is_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 * Context::getContext()->getComputingPrecision()); } } } } if ($cartTotal < $minimum_amount) { return (!$display_error) ? false : $this->trans('The minimum amount to benefit from this promo code is %s.', [Tools::getContextLocale($context)->formatPrice($minimum_amount, $context->currency->iso_code)], '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($cart->id); $otherCartRules = []; if ($check_carrier) { $otherCartRules = $cart->getCartRules(CartRule::FILTER_ACTION_ALL, false); } 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', [], 'Shop.Notifications.Error'); } $giftProductQuantity = $cart->getProductQuantity($otherCartRule['gift_product'], $otherCartRule['gift_product_attribute']); if ($otherCartRule['gift_product'] && !empty($giftProductQuantity['quantity'])) { --$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'], $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', [$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 $cart->removeCartRule($cart_rule->id); } } } } } if (!$nb_products) { return (!$display_error) ? false : $this->trans('Cart is empty', [], 'Shop.Notifications.Error'); } // Check if order cart rule was removed from back office $removed_order_cartRule_id = (int) Db::getInstance()->getValue(' SELECT ocr.`id_order_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 ocr.`deleted` = 1 AND o.`id_cart` = ' . $cart->id); if ($removed_order_cartRule_id) { return (!$display_error) ? false : $this->trans('You cannot use this voucher because it has manually been removed.', [], 'Shop.Notifications.Error'); } if (!$display_error) { return true; } } /** * Checks if the products chosen by the customer are usable with the cart rule. * * @param CartCore $cart * @param bool $returnProducts [default=false] * If true, this method will return an array of eligible products. * Otherwise, it returns TRUE on success and string|false on errors (depending on the value of $displayError) * @param bool $displayError [default=false] * If true, this method will return an error message instead of FALSE on errors. * Otherwise, it returns FALSE on errors * @param bool $alreadyInCart * * @return array|bool|string * * @throws PrestaShopDatabaseException */ public function checkProductRestrictionsFromCart(CartCore $cart, $returnProducts = false, $displayError = true, $alreadyInCart = false) { // Prepare a list of products to return, if the caller wishes so and provided returnProducts = true $selected_products = []; // Do all of this only if the cart rule actually has some restrictions if ($this->product_restriction) { // Load products in cart and return if it's empty, there is no point in checking anything else $products = $cart->getProducts(); if (empty($products)) { return (!$displayError) ? false : $this->trans('You cannot use this voucher in an empty cart', [], 'Shop.Notifications.Error'); } // Now we load all RULE GROUP. $product_rule_groups = $this->getProductRuleGroups(); foreach ($product_rule_groups as $id_product_rule_group => $product_rule_group) { /* * Rule group is a set of rules that the cart must meet for this cart rule to be applied. * These groups have an AND relationship. If you create two groups for given cart rule, * the cart must meet the conditions of both of them to be applied. * * Also, at least $product_rule_group['quantity'] must meet these rules. */ $eligible_products_list = []; foreach ($products as $product) { $eligible_products_list[] = (int) $product['id_product'] . '-' . (int) $product['id_product_attribute']; } // Now, we load the RULES inside the RULE GROUP $product_rules = $this->getProductRules($id_product_rule_group); $countRulesProduct = count($product_rules); $condition = 0; foreach ($product_rules as $product_rule) { /* * For the cart RULE GROUP to be validated, at least on of the RULES inside the RULE GROUP * must meet the conditions. */ switch ($product_rule['type']) { case 'attributes': $cart_attributes = 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) $cart->id . ' AND cp.`id_product` IN (' . implode(',', array_map('intval', $eligible_products_list)) . ') AND cp.id_product_attribute > 0'); $count_matching_products = 0; $matching_products_list = []; foreach ($cart_attributes as $cart_attribute) { if (in_array($cart_attribute['id_attribute'], $product_rule['values'])) { $count_matching_products += $cart_attribute['quantity']; if ( $alreadyInCart && $this->gift_product == $cart_attribute['id_product'] && $this->gift_product_attribute == $cart_attribute['id_product_attribute']) { --$count_matching_products; } $matching_products_list[] = $cart_attribute['id_product'] . '-' . $cart_attribute['id_product_attribute']; } } if ($count_matching_products < $product_rule_group['quantity']) { if ($countRulesProduct === 1) { return (!$displayError) ? false : $this->trans('You cannot use this voucher with these products', [], 'Shop.Notifications.Error'); } else { ++$condition; break; } } $eligible_products_list = $this->filterProducts($eligible_products_list, $matching_products_list, $product_rule['type']); break; case 'products': $cart_products = Db::getInstance()->executeS(' SELECT cp.quantity, cp.`id_product` FROM `' . _DB_PREFIX_ . 'cart_product` cp WHERE cp.`id_cart` = ' . (int) $cart->id . ' AND cp.`id_product` IN (' . implode(',', array_map('intval', $eligible_products_list)) . ')'); $count_matching_products = 0; $matching_products_list = []; foreach ($cart_products as $cart_product) { if (in_array($cart_product['id_product'], $product_rule['values'])) { $count_matching_products += $cart_product['quantity']; if ($alreadyInCart && $this->gift_product == $cart_product['id_product']) { --$count_matching_products; } $matching_products_list[] = $cart_product['id_product'] . '-0'; } } if ($count_matching_products < $product_rule_group['quantity']) { if ($countRulesProduct === 1) { return (!$displayError) ? false : $this->trans('You cannot use this voucher with these products', [], 'Shop.Notifications.Error'); } else { ++$condition; break; } } $eligible_products_list = $this->filterProducts($eligible_products_list, $matching_products_list, $product_rule['type']); break; case 'categories': $cart_categories = 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) $cart->id . ' AND cp.`id_product` IN (' . implode(',', array_map('intval', $eligible_products_list)) . ') AND cp.`id_product` <> ' . (int) $this->gift_product); $count_matching_products = 0; $matching_products_list = []; foreach ($cart_categories as $cart_category) { if (in_array($cart_category['id_category'], $product_rule['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($cart_category['id_product'] . '-' . $cart_category['id_product_attribute'], $matching_products_list)) { $count_matching_products += $cart_category['quantity']; $matching_products_list[] = $cart_category['id_product'] . '-' . $cart_category['id_product_attribute']; } } if ($count_matching_products < $product_rule_group['quantity']) { if ($countRulesProduct === 1) { return (!$displayError) ? false : $this->trans('You cannot use this voucher with these products', [], 'Shop.Notifications.Error'); } else { ++$condition; break; } } // Attribute id is not important for this filter in the global list, so the ids are replaced by 0 foreach ($matching_products_list as &$matching_product) { $matching_product = preg_replace('/^([0-9]+)-[0-9]+$/', '$1-0', $matching_product); } $eligible_products_list = $this->filterProducts($eligible_products_list, $matching_products_list, $product_rule['type']); break; case 'manufacturers': $cart_manufacturers = 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) $cart->id . ' AND cp.`id_product` IN (' . implode(',', array_map('intval', $eligible_products_list)) . ')'); $count_matching_products = 0; $matching_products_list = []; foreach ($cart_manufacturers as $cart_manufacturer) { if (in_array($cart_manufacturer['id_manufacturer'], $product_rule['values'])) { $count_matching_products += $cart_manufacturer['quantity']; $matching_products_list[] = $cart_manufacturer['id_product'] . '-0'; } } if ($count_matching_products < $product_rule_group['quantity']) { if ($countRulesProduct === 1) { return (!$displayError) ? false : $this->trans('You cannot use this voucher with these products', [], 'Shop.Notifications.Error'); } else { ++$condition; break; } } $eligible_products_list = $this->filterProducts($eligible_products_list, $matching_products_list, $product_rule['type']); break; case 'suppliers': $cart_suppliers = 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) $cart->id . ' AND cp.`id_product` IN (' . implode(',', array_map('intval', $eligible_products_list)) . ')'); $count_matching_products = 0; $matching_products_list = []; foreach ($cart_suppliers as $cart_supplier) { if (in_array($cart_supplier['id_supplier'], $product_rule['values'])) { $count_matching_products += $cart_supplier['quantity']; $matching_products_list[] = $cart_supplier['id_product'] . '-0'; } } if ($count_matching_products < $product_rule_group['quantity']) { if ($countRulesProduct === 1) { return (!$displayError) ? false : $this->trans('You cannot use this voucher with these products', [], 'Shop.Notifications.Error'); } else { ++$condition; break; } } $eligible_products_list = $this->filterProducts($eligible_products_list, $matching_products_list, $product_rule['type']); break; } if (!count($eligible_products_list)) { if ($countRulesProduct === 1) { return (!$displayError) ? false : $this->trans('You cannot use this voucher with these products', [], 'Shop.Notifications.Error'); } } } if ($countRulesProduct !== 1 && $condition == $countRulesProduct) { return (!$displayError) ? false : $this->trans('You cannot use this voucher with these products', [], 'Shop.Notifications.Error'); } $selected_products = array_merge($selected_products, $eligible_products_list); } } if ($returnProducts) { return $selected_products; } return (!$displayError) ? true : false; } /** * The reduction value is POSITIVE. * * @param bool $use_tax Apply taxes * @param Context $context Context instance * @param bool $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; } // set base price that will be used for percent reductions if (!empty($context->virtualTotalTaxIncluded) && !empty($context->virtualTotalTaxExcluded)) { $basePriceForPercentReduction = $use_tax ? $context->virtualTotalTaxIncluded : $context->virtualTotalTaxExcluded; } if (!$context) { $context = Context::getContext(); } if (!$filter) { $filter = CartRule::FILTER_ACTION_ALL; } $all_products = $context->cart->getProducts(); $package_products = (null === $package ? $all_products : $package['products']); $all_cart_rules_ids = $context->cart->getOrderedCartRulesIds(); if (!array_key_exists($context->cart->id, static::$cartAmountCache)) { if (Tax::excludeTaxeOption()) { static::$cartAmountCache[$context->cart->id]['te'] = $context->cart->getOrderTotal(false, Cart::ONLY_PRODUCTS); static::$cartAmountCache[$context->cart->id]['ti'] = static::$cartAmountCache[$context->cart->id]['te']; } else { static::$cartAmountCache[$context->cart->id]['ti'] = $context->cart->getOrderTotal(true, Cart::ONLY_PRODUCTS); static::$cartAmountCache[$context->cart->id]['te'] = $context->cart->getOrderTotal(false, Cart::ONLY_PRODUCTS); } } $cart_amount_te = static::$cartAmountCache[$context->cart->id]['te']; $cart_amount_ti = static::$cartAmountCache[$context->cart->id]['ti']; $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'] . (isset($product['in_stock']) ? '_' . (int) $product['in_stock'] : ''); } if (Cache::isStored($cache_id)) { return Cache::retrieve($cache_id); } // Free shipping on selected carriers $reduction_carrier = 0; if ($this->free_shipping && in_array($filter, [CartRule::FILTER_ACTION_ALL, CartRule::FILTER_ACTION_ALL_NOCAP, CartRule::FILTER_ACTION_SHIPPING])) { if (!$this->carrier_restriction) { $reduction_carrier += $context->cart->getOrderTotal($use_tax, Cart::ONLY_SHIPPING, null === $package ? null : $package['products'], 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_carrier += $context->cart->getCarrierCost((int) $cart_rule['id_carrier'], $use_tax, $context->country); } } } $reduction_value += $reduction_carrier; } if (in_array($filter, [CartRule::FILTER_ACTION_ALL, CartRule::FILTER_ACTION_ALL_NOCAP, CartRule::FILTER_ACTION_REDUCTION])) { $order_package_products_total = 0; if ((float) $this->reduction_amount > 0 || (float) $this->reduction_percent && $this->reduction_product == 0) { $order_package_products_total = $context->cart->getOrderTotal($use_tax, Cart::ONLY_PRODUCTS, $package_products); } // Discount (%) on the whole order if ((float) $this->reduction_percent && $this->reduction_product == 0) { // Do not give a reduction on free products! $order_total = $order_package_products_total; $basePriceContainsDiscount = isset($basePriceForPercentReduction) && $order_total === $basePriceForPercentReduction; foreach ($context->cart->getCartRules(CartRule::FILTER_ACTION_GIFT, false) as $cart_rule) { $freeProductsPrice = Tools::ps_round($cart_rule['obj']->getContextualValue($use_tax, $context, CartRule::FILTER_ACTION_GIFT, $package), Context::getContext()->getComputingPrecision()); if ($basePriceContainsDiscount && isset($basePriceForPercentReduction)) { // Gifts haven't been excluded yet, we need to do it $basePriceForPercentReduction -= $freeProductsPrice; } $order_total -= $freeProductsPrice; } // Remove products that are on special if ($this->reduction_exclude_special) { foreach ($package_products as $product) { if ($product['reduction_applies']) { $roundTotal = $use_tax ? $product['total_wt'] : $product['total']; $excludedReduction = Tools::ps_round($roundTotal, Context::getContext()->getComputingPrecision()); $order_total -= $excludedReduction; if ($basePriceContainsDiscount && isset($basePriceForPercentReduction)) { $basePriceForPercentReduction -= $excludedReduction; } } } } // set base price on which percentage reduction will be applied $basePriceForPercentReduction = $basePriceForPercentReduction ?? $order_total; $reduction_value += $basePriceForPercentReduction * $this->reduction_percent / 100; } // Discount (%) on a specific product if ((float) $this->reduction_percent && $this->reduction_product > 0) { foreach ($package_products as $product) { if ($product['id_product'] == $this->reduction_product && (($this->reduction_exclude_special && !$product['reduction_applies']) || !$this->reduction_exclude_special)) { $reduction_value += ($use_tax ? $product['total_wt'] : $product['total']) * $this->reduction_percent / 100; } } } // Discount (%) on the cheapest product if ((float) $this->reduction_percent && $this->reduction_product == -1) { $minPrice = false; $cheapest_product = null; foreach ($all_products as $product) { $price = $product['price']; if ($use_tax) { // since later on we won't be able to know the product the cart rule was applied to, // use average cart VAT for price_wt $price *= (1 + $context->cart->getAverageProductsTaxRate()); } if ($price > 0 && ($minPrice === false || $minPrice > $price) && (($this->reduction_exclude_special && !$product['reduction_applies']) || !$this->reduction_exclude_special)) { $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 ((float) $this->reduction_percent && $this->reduction_product == -2) { $selected_products_reduction = 0; // Let's get products this cart rule applies to. We should get an array, but we can also // get a false in some cases. It doesn't matter much though, as long as we check what we got. $selected_products = $this->checkProductRestrictionsFromCart($context->cart, 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)) && (($this->reduction_exclude_special && !$product['reduction_applies']) || !$this->reduction_exclude_special)) { $price = $product['price_with_reduction_without_tax']; if ($use_tax) { $price = $product['price_with_reduction']; } $selected_products_reduction += $price * $product['cart_quantity']; } } } $reduction_value += $selected_products_reduction * $this->reduction_percent / 100; } // Discount (¤) if ((float) $this->reduction_amount > 0) { $prorata = 1; if (null !== $package && count($all_products)) { $total_products = $use_tax ? $cart_amount_ti : $cart_amount_te; if ($total_products) { $prorata = $order_package_products_total / $total_products; } } $reduction_amount = (float) $this->reduction_amount; // If the cart rule is restricted to one product it can't exceed this product price if ($this->reduction_product > 0) { foreach ($all_products as $product) { if ($product['id_product'] == $this->reduction_product) { $productPrice = $this->reduction_tax ? $product['price_wt'] : $product['price']; $max_reduction_amount = (int) $product['cart_quantity'] * (float) $productPrice; $reduction_amount = min($reduction_amount, $max_reduction_amount); break; } } } // If we need to convert the voucher value to the cart currency if (isset($context->currency) && $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, Context::getContext()->getComputingPrecision()); } // 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 = $use_tax ? $cart_amount_ti : $cart_amount_te; $reduction_amount = min($reduction_amount, $cart_amount); } $reduction_value += $prorata * $reduction_amount; } else { if ($this->reduction_product > 0) { foreach ($all_products 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); } } } } elseif ($this->reduction_product == 0) { // Discount (¤) on the whole order $cart_amount_te = null; $cart_amount_ti = null; $cart_average_vat_rate = $context->cart->getAverageProductsTaxRate($cart_amount_te, $cart_amount_ti); // 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); } 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, it only applies with percent * Please keep this code, so it won't be considered as a bug * elseif ($this->reduction_product == -1) * elseif ($this->reduction_product == -2) */ } // Take care of the other cart rules values if the filter allow it if ($filter != CartRule::FILTER_ACTION_ALL_NOCAP) { // Cart values $cart = Context::getContext()->cart; if (!Validate::isLoadedObject($cart)) { $cart = new Cart(); } $cart_average_vat_rate = $cart->getAverageProductsTaxRate(); $current_cart_amount = $use_tax ? $cart_amount_ti : $cart_amount_te; foreach ($all_cart_rules_ids as $current_cart_rule_id) { if ((int) $current_cart_rule_id['id_cart_rule'] == (int) $this->id) { break; } $previous_cart_rule = new CartRule((int) $current_cart_rule_id['id_cart_rule']); $previous_reduction_amount = $previous_cart_rule->reduction_amount; if ($previous_cart_rule->reduction_tax && !$use_tax) { $previous_reduction_amount = $prorata * $previous_reduction_amount / (1 + $cart_average_vat_rate); } elseif (!$previous_cart_rule->reduction_tax && $use_tax) { $previous_reduction_amount = $prorata * $previous_reduction_amount * (1 + $cart_average_vat_rate); } $current_cart_amount = max($current_cart_amount - (float) $previous_reduction_amount, 0); } $reduction_value = min($reduction_value, $current_cart_amount); } } } // Free gift if ((int) $this->gift_product && in_array($filter, [CartRule::FILTER_ACTION_ALL, CartRule::FILTER_ACTION_ALL_NOCAP, CartRule::FILTER_ACTION_GIFT])) { $id_address = (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 += Tools::ps_round($use_tax ? $product['price_wt'] : $product['price'], Context::getContext()->getComputingPrecision()); 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); // update virtual total values, for percentage reductions that might be applied later // but remove the carrier as free shipping is not a real reduction if ($use_tax && !empty($context->virtualTotalTaxIncluded)) { $context->virtualTotalTaxIncluded -= $reduction_value; if ($this->free_shipping) { $context->virtualTotalTaxIncluded += $reduction_carrier; } } elseif (!$use_tax && !empty($context->virtualTotalTaxExcluded)) { $context->virtualTotalTaxExcluded -= $reduction_value; if ($this->free_shipping) { $context->virtualTotalTaxExcluded += $reduction_carrier; } } return $reduction_value; } /** * Make sure caches are empty * Must be called before calling multiple time getContextualValue(). */ public static function cleanCache() { self::$only_one_gift = []; } /** * Get CartRule combinations. * * @param int $offset Offset * @param int $limit Limit * @param string $search Search query * * @return array CartRule search results */ protected function getCartRuleCombinations($offset = null, $limit = null, $search = '') { $array = []; if ($offset !== null && $limit !== null) { $sql_limit = ' LIMIT ' . (int) $offset . ', ' . (int) ($limit + 1); } else { $sql_limit = ''; } $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 . ($search ? ' AND crl.name LIKE "%' . pSQL($search) . '%"' : '') . ' AND ( cr.cart_rule_restriction = 0 OR EXISTS ( SELECT 1 FROM ' . _DB_PREFIX_ . 'cart_rule_combination WHERE cr.id_cart_rule = ' . _DB_PREFIX_ . 'cart_rule_combination.id_cart_rule_1 AND ' . (int) $this->id . ' = id_cart_rule_2 ) OR EXISTS ( SELECT 1 FROM ' . _DB_PREFIX_ . 'cart_rule_combination WHERE cr.id_cart_rule = ' . _DB_PREFIX_ . 'cart_rule_combination.id_cart_rule_2 AND ' . (int) $this->id . ' = id_cart_rule_1 ) ) ORDER BY cr.id_cart_rule' . $sql_limit); $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 . ($search ? ' AND crl.name LIKE "%' . pSQL($search) . '%"' : '') . ' AND crc1.id_cart_rule_1 IS NULL AND crc2.id_cart_rule_1 IS NULL ORDER BY cr.id_cart_rule' . $sql_limit); return $array; } /** * Get associated restrictions. * * @param string $type Restriction type * Can be one of the following: * - country * - carrier * - group * - cart_rule * - shop * @param bool $active_only Only return active restrictions * @param bool $i18n Join with associated language table * @param int $offset Search offset * @param int $limit Search results limit * @param string $search_cart_rule_name CartRule name to search for * * @return array|bool Array with DB rows of requested type * * @throws PrestaShopDatabaseException */ public function getAssociatedRestrictions( $type, $active_only, $i18n, $offset = null, $limit = null, $search_cart_rule_name = '' ) { $array = ['selected' => [], 'unselected' => []]; if (!in_array($type, ['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 ($offset !== null && $limit !== null) { $sql_limit = ' LIMIT ' . (int) $offset . ', ' . (int) ($limit + 1); } else { $sql_limit = ''; } if (!Validate::isLoadedObject($this) || $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, ['carrier', 'shop']) ? ' AND t.deleted = 0' : '') . ' ' . ($type == 'cart_rule' ? 'AND t.id_cart_rule != ' . (int) $this->id : '') . $shop_list . (in_array($type, ['carrier', 'shop']) ? ' ORDER BY t.name ASC ' : '') . (in_array($type, ['country', 'group', 'cart_rule']) && $i18n ? ' ORDER BY tl.name ASC ' : '') . $sql_limit); } else { if ($type == 'cart_rule') { $array = $this->getCartRuleCombinations($offset, $limit, $search_cart_rule_name); } else { $resource = Db::getInstance()->executeS( ' 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, ['carrier', 'shop']) ? ' AND t.deleted = 0' : '') . (in_array($type, ['carrier', 'shop']) ? ' ORDER BY t.name ASC ' : '') . (in_array($type, ['country', 'group']) && $i18n ? ' ORDER BY tl.name ASC ' : '') . $sql_limit, false ); while ($row = Db::getInstance()->nextRow($resource)) { $array[($row['selected'] || $this->{$type . '_restriction'} == 0) ? 'selected' : 'unselected'][] = $row; } } } return $array; } /** * Automatically add this CartRule to the Cart. * * @param Context|null $context Context instance * @param bool $useOrderPrices */ public static function autoAddToCart(Context $context = null, bool $useOrderPrices = false) { if ($context === null) { $context = Context::getContext(); } if (!CartRule::isFeatureActive() || !Validate::isLoadedObject($context->cart)) { return; } $sql = ' SELECT SQL_NO_CACHE cr.* FROM ' . _DB_PREFIX_ . 'cart_rule cr LEFT JOIN ' . _DB_PREFIX_ . 'cart_rule_shop crs ON cr.id_cart_rule = crs.id_cart_rule ' . (!Validate::isLoadedObject($context->customer) && Group::isFeatureActive() ? ' LEFT JOIN ' . _DB_PREFIX_ . 'cart_rule_group crg ON cr.id_cart_rule = crg.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 NOW() BETWEEN cr.date_from AND cr.date_to AND ( cr.id_customer = 0 ' . (Validate::isLoadedObject($context->customer) ? '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 ' . (Validate::isLoadedObject($context->customer) ? 'OR EXISTS ( SELECT 1 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 )' : (Group::isFeatureActive() ? 'OR crg.`id_group` = ' . (int) Configuration::get('PS_UNIDENTIFIED_GROUP') : '')) . ' ) AND ( cr.`reduction_product` <= 0 OR EXISTS ( SELECT 1 FROM `' . _DB_PREFIX_ . 'cart_product` WHERE `' . _DB_PREFIX_ . 'cart_product`.`id_product` = cr.`reduction_product` AND `id_cart` = ' . (int) $context->cart->id . ' ) ) AND NOT EXISTS (SELECT 1 FROM ' . _DB_PREFIX_ . 'cart_cart_rule WHERE cr.id_cart_rule = ' . _DB_PREFIX_ . 'cart_cart_rule.id_cart_rule AND id_cart = ' . (int) $context->cart->id . ') ORDER BY priority'; $result = Db::getInstance()->executeS($sql, true, false); if ($result) { $cart_rules = ObjectModel::hydrateCollection('CartRule', $result); if ($cart_rules) { foreach ($cart_rules as $cart_rule) { /** @var CartRule $cart_rule */ if ($cart_rule->checkValidity($context, false, false, true, $useOrderPrices)) { $context->cart->addCartRule($cart_rule->id, $useOrderPrices); } } } } } /** * Automatically remove this CartRule from the Cart. * * @param Context|null $context Context instance * @param bool $useOrderPrice * * @return array Error messages */ public static function autoRemoveFromCart(Context $context = null, bool $useOrderPrice = false) { if (!$context) { $context = Context::getContext(); } if (!CartRule::isFeatureActive() || !Validate::isLoadedObject($context->cart)) { return []; } static $errors = []; foreach ($context->cart->getCartRules(CartRule::FILTER_ACTION_ALL, true, $useOrderPrice) as $cart_rule) { if ($error = $cart_rule['obj']->checkValidity($context, true, true, true, $useOrderPrice)) { $context->cart->removeCartRule($cart_rule['obj']->id, $useOrderPrice); $context->cart->update(); $errors[] = $error; } } return $errors; } /** * Check if the CartRule feature is active * It becomes active after adding the first CartRule to the store. * * @return bool Indicates whether the CartRule feature is active */ public static function isFeatureActive() { $is_feature_active = (bool) Configuration::get('PS_CART_RULE_FEATURE_ACTIVE'); return $is_feature_active; } /** * CartRule cleanup * When an entity associated to a product rule * (product, category, attribute, supplier, manufacturer...) * is deleted, the product rules must be updated. * * @param string $type Entity type * Can be one of the following: * - products * - categories * - attributes * - manufacturers * - suppliers * @param array|int $list Entities * * @return bool Indicates whether the cleanup was successful */ 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, ['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()->delete('cart_rule_product_rule', 'NOT EXISTS (SELECT 1 FROM `' . _DB_PREFIX_ . 'cart_rule_product_rule_value` WHERE `' . _DB_PREFIX_ . 'cart_rule_product_rule`.`id_product_rule` = `' . _DB_PREFIX_ . 'cart_rule_product_rule_value`.`id_product_rule`)'); } // 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()->delete('cart_rule_product_rule_group', 'NOT EXISTS (SELECT 1 FROM `' . _DB_PREFIX_ . 'cart_rule_product_rule` WHERE `' . _DB_PREFIX_ . 'cart_rule_product_rule`.`id_product_rule_group` = `' . _DB_PREFIX_ . 'cart_rule_product_rule_group`.`id_product_rule_group`)'); } // 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; } /** * Get CartRules by voucher code. * * @param string $name Name of voucher code * @param int $id_lang Language ID * @param bool $extended Also search by voucher name * * @return array Result from database */ public static function getCartsRuleByCode($name, $id_lang, $extended = false) { $sql_base = '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 . ')'; if ($extended) { return Db::getInstance()->executeS('(' . $sql_base . ' WHERE code LIKE \'%' . pSQL($name) . '%\') UNION (' . $sql_base . ' WHERE name LIKE \'%' . pSQL($name) . '%\')'); } else { return Db::getInstance()->executeS($sql_base . ' WHERE code LIKE \'%' . pSQL($name) . '%\''); } } /** * CartRules compare function to use the Product and the rules. * * @param array $products List of Products from the cart, * @param array $eligibleProducts List of Product eligible for rules, * @param string $ruleType name of the rule, * * @return array Product selected who are eligible */ protected function filterProducts($products, $eligibleProducts, $ruleType) { //If the two same array, no verification todo. if ($products === $eligibleProducts) { return $products; } $return = []; // Attribute id is not important for this filter in the global list // so the ids are replaced by 0 if (in_array($ruleType, ['products', 'categories', 'manufacturers', 'suppliers'])) { $productsList = explode(':', preg_replace("#\-[0-9]+#", '-0', implode(':', $products))); } else { $productsList = $products; } foreach ($productsList as $k => $product) { if (in_array($product, $eligibleProducts)) { $return[] = $products[$k]; } } return $return; } }