1<?php 2/** 3 * 2007-2016 PrestaShop 4 * 5 * thirty bees is an extension to the PrestaShop e-commerce software developed by PrestaShop SA 6 * Copyright (C) 2017-2018 thirty bees 7 * 8 * NOTICE OF LICENSE 9 * 10 * This source file is subject to the Open Software License (OSL 3.0) 11 * that is bundled with this package in the file LICENSE.txt. 12 * It is also available through the world-wide-web at this URL: 13 * http://opensource.org/licenses/osl-3.0.php 14 * If you did not receive a copy of the license and are unable to 15 * obtain it through the world-wide-web, please send an email 16 * to license@thirtybees.com so we can send you a copy immediately. 17 * 18 * DISCLAIMER 19 * 20 * Do not edit or add to this file if you wish to upgrade PrestaShop to newer 21 * versions in the future. If you wish to customize PrestaShop for your 22 * needs please refer to https://www.thirtybees.com for more information. 23 * 24 * @author thirty bees <contact@thirtybees.com> 25 * @author PrestaShop SA <contact@prestashop.com> 26 * @copyright 2017-2018 thirty bees 27 * @copyright 2007-2016 PrestaShop SA 28 * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) 29 * PrestaShop is an internationally registered trademark & property of PrestaShop SA 30 */ 31 32/** 33 * Class CartRuleCore 34 * 35 * @since 1.0.0 36 */ 37class CartRuleCore extends ObjectModel 38{ 39 /* Filters used when retrieving the cart rules applied to a cart of when calculating the value of a reduction */ 40 const FILTER_ACTION_ALL = 1; 41 const FILTER_ACTION_SHIPPING = 2; 42 const FILTER_ACTION_REDUCTION = 3; 43 const FILTER_ACTION_GIFT = 4; 44 const FILTER_ACTION_ALL_NOCAP = 5; 45 46 const BO_ORDER_CODE_PREFIX = 'BO_ORDER_'; 47 48 // @codingStandardsIgnoreStart 49 /** 50 * This variable controls that a free gift is offered only once, even when multi-shipping is activated and the same product is delivered in both addresses 51 * 52 * @var array 53 */ 54 protected static $onlyOneGift = []; 55 /** @var int $id */ 56 public $id; 57 /** @var string $name */ 58 public $name; 59 /** @var int $id_customer */ 60 public $id_customer; 61 /** @var string $date_from */ 62 public $date_from; 63 /** @var string $date_to */ 64 public $date_to; 65 /** 66 * @FIXME: with 1.0.x the cart rule cannot register the calculated 67 * cheapest product in case it is converted into an order. 68 * The copied cart rule is then injected with this information 69 * in the `description` field and looks like this: 70 * { 71 * "type": "cheapest_product", 72 * "id_product": "7", 73 * "id_product_attribute": "0" 74 * } 75 * 76 * In the AdminCartRulesController, the field is then disabled to prevent the user from changing it 77 * 78 * When making an update script for 1.1.x, don't forget to clean this field up and convert it to 79 * a proper database table. 80 * 81 * @var string $description 82 */ 83 public $description; 84 /** @var int $quantity */ 85 public $quantity = 1; 86 /** @var int $quantity_per_user */ 87 public $quantity_per_user = 1; 88 /** @var int $priority */ 89 public $priority = 1; 90 /** @var int $partial_use */ 91 public $partial_use = 1; 92 /** @var string $code */ 93 public $code; 94 /** @var float $minimum_amount */ 95 public $minimum_amount; 96 /** @var bool $minimum_amount_tax */ 97 public $minimum_amount_tax; 98 /** @var int $minimum_amount_currency */ 99 public $minimum_amount_currency; 100 /** @var bool $minimum_amount_shipping */ 101 public $minimum_amount_shipping; 102 /** @var bool $country_restriction */ 103 public $country_restriction; 104 /** @var bool $carrier_restriction */ 105 public $carrier_restriction; 106 /** @var bool $group_restriction */ 107 public $group_restriction; 108 /** @var bool $cart_rule_restriction */ 109 public $cart_rule_restriction; 110 /** @var bool $product_restriction */ 111 public $product_restriction; 112 /** @var bool $shop_restriction */ 113 public $shop_restriction; 114 /** @var bool $free_shipping */ 115 public $free_shipping; 116 /** @var float $reduction_percent */ 117 public $reduction_percent; 118 /** @var float $reduction_amount */ 119 public $reduction_amount; 120 /** @var bool $reduction_tax */ 121 public $reduction_tax; 122 /** @var bool $reduction_currency */ 123 public $reduction_currency; 124 /** @var int $reduction_product */ 125 public $reduction_product; 126 /** @var int $gift_product */ 127 public $gift_product; 128 /** @var int $gift_product_attribute */ 129 public $gift_product_attribute; 130 /** @var bool $highlight */ 131 public $highlight; 132 /** @var int $active */ 133 public $active = 1; 134 /** @var string $date_add */ 135 public $date_add; 136 /** @var string $date_upd */ 137 public $date_upd; 138 // @codingStandardsIgnoreEnd 139 140 /** 141 * @see ObjectModel::$definition 142 */ 143 public static $definition = [ 144 'table' => 'cart_rule', 145 'primary' => 'id_cart_rule', 146 'multilang' => true, 147 'fields' => [ 148 'id_customer' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId'], 149 'date_from' => ['type' => self::TYPE_DATE, 'validate' => 'isDate', 'required' => true], 150 'date_to' => ['type' => self::TYPE_DATE, 'validate' => 'isDate', 'required' => true], 151 'description' => ['type' => self::TYPE_STRING, 'validate' => 'isCleanHtml', 'size' => 65534], 152 'quantity' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedInt'], 153 'quantity_per_user' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedInt'], 154 'priority' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedInt'], 155 'partial_use' => ['type' => self::TYPE_BOOL, 'validate' => 'isBool'], 156 'code' => ['type' => self::TYPE_STRING, 'validate' => 'isCleanHtml', 'size' => 254], 157 'minimum_amount' => ['type' => self::TYPE_PRICE, 'validate' => 'isPrice'], 158 'minimum_amount_tax' => ['type' => self::TYPE_BOOL, 'validate' => 'isBool'], 159 'minimum_amount_currency' => ['type' => self::TYPE_INT, 'validate' => 'isInt'], 160 'minimum_amount_shipping' => ['type' => self::TYPE_BOOL, 'validate' => 'isBool'], 161 'country_restriction' => ['type' => self::TYPE_BOOL, 'validate' => 'isBool'], 162 'carrier_restriction' => ['type' => self::TYPE_BOOL, 'validate' => 'isBool'], 163 'group_restriction' => ['type' => self::TYPE_BOOL, 'validate' => 'isBool'], 164 'cart_rule_restriction' => ['type' => self::TYPE_BOOL, 'validate' => 'isBool'], 165 'product_restriction' => ['type' => self::TYPE_BOOL, 'validate' => 'isBool'], 166 'shop_restriction' => ['type' => self::TYPE_BOOL, 'validate' => 'isBool'], 167 'free_shipping' => ['type' => self::TYPE_BOOL, 'validate' => 'isBool'], 168 'reduction_percent' => ['type' => self::TYPE_FLOAT, 'validate' => 'isPercentage'], 169 'reduction_amount' => ['type' => self::TYPE_PRICE, 'validate' => 'isPrice'], 170 'reduction_tax' => ['type' => self::TYPE_BOOL, 'validate' => 'isBool'], 171 'reduction_currency' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId'], 172 'reduction_product' => ['type' => self::TYPE_INT, 'validate' => 'isInt'], 173 'gift_product' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId'], 174 'gift_product_attribute' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId'], 175 'highlight' => ['type' => self::TYPE_BOOL, 'validate' => 'isBool'], 176 'active' => ['type' => self::TYPE_BOOL, 'validate' => 'isBool'], 177 'date_add' => ['type' => self::TYPE_DATE, 'validate' => 'isDate'], 178 'date_upd' => ['type' => self::TYPE_DATE, 'validate' => 'isDate'], 179 180 /* Lang fields */ 181 'name' => ['type' => self::TYPE_STRING, 'lang' => true, 'validate' => 'isCleanHtml', 'required' => true, 'size' => 254], 182 ], 183 ]; 184 185 /** 186 * Copy conditions from one cart rule to an other 187 * 188 * @param int $idCartRuleSource 189 * @param int $idCartRuleDestination 190 * 191 * @since 1.0.0 192 * @version 1.0.0 Initial version 193 * @throws PrestaShopException 194 */ 195 public static function copyConditions($idCartRuleSource, $idCartRuleDestination) 196 { 197 Db::getInstance()->execute( 198 ' 199 INSERT INTO `'._DB_PREFIX_.'cart_rule_shop` (`id_cart_rule`, `id_shop`) 200 (SELECT '.(int) $idCartRuleDestination.', id_shop FROM `'._DB_PREFIX_.'cart_rule_shop` WHERE `id_cart_rule` = '.(int) $idCartRuleSource.')' 201 ); 202 Db::getInstance()->execute( 203 ' 204 INSERT INTO `'._DB_PREFIX_.'cart_rule_carrier` (`id_cart_rule`, `id_carrier`) 205 (SELECT '.(int) $idCartRuleDestination.', id_carrier FROM `'._DB_PREFIX_.'cart_rule_carrier` WHERE `id_cart_rule` = '.(int) $idCartRuleSource.')' 206 ); 207 Db::getInstance()->execute( 208 ' 209 INSERT INTO `'._DB_PREFIX_.'cart_rule_group` (`id_cart_rule`, `id_group`) 210 (SELECT '.(int) $idCartRuleDestination.', id_group FROM `'._DB_PREFIX_.'cart_rule_group` WHERE `id_cart_rule` = '.(int) $idCartRuleSource.')' 211 ); 212 Db::getInstance()->execute( 213 ' 214 INSERT INTO `'._DB_PREFIX_.'cart_rule_country` (`id_cart_rule`, `id_country`) 215 (SELECT '.(int) $idCartRuleDestination.', id_country FROM `'._DB_PREFIX_.'cart_rule_country` WHERE `id_cart_rule` = '.(int) $idCartRuleSource.')' 216 ); 217 Db::getInstance()->execute( 218 ' 219 INSERT INTO `'._DB_PREFIX_.'cart_rule_combination` (`id_cart_rule_1`, `id_cart_rule_2`) 220 (SELECT '.(int) $idCartRuleDestination.', IF(id_cart_rule_1 != '.(int) $idCartRuleSource.', id_cart_rule_1, id_cart_rule_2) FROM `'._DB_PREFIX_.'cart_rule_combination` 221 WHERE `id_cart_rule_1` = '.(int) $idCartRuleSource.' OR `id_cart_rule_2` = '.(int) $idCartRuleSource.')' 222 ); 223 224 // Todo : should be changed soon, be must be copied too 225 // Db::getInstance()->execute('DELETE FROM `'._DB_PREFIX_.'cart_rule_product_rule` WHERE `id_cart_rule` = '.(int)$this->id); 226 // 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`)'); 227 228 // Copy products/category filters 229 $sql = new DbQuery(); 230 $sql->select('`id_product_rule_group`, `quantity`'); 231 $sql->from('cart_rule_product_rule_group'); 232 $sql->where('`id_cart_rule` = '.(int) $idCartRuleSource); 233 $productsRulesGroupSource = Db::getInstance()->ExecuteS($sql); 234 235 foreach ($productsRulesGroupSource as $productRuleGroupSource) { 236 Db::getInstance()->insert( 237 'cart_rule_product_rule_group', 238 [ 239 'id_cart_rule' => (int) $idCartRuleDestination, 240 'quantity' => (int) $productRuleGroupSource['quantity'], 241 ] 242 ); 243 $idProductRuleGroupDestination = Db::getInstance()->Insert_ID(); 244 245 $productsRulesSource = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS( 246 (new DbQuery()) 247 ->select('`id_product_rule`, `type`') 248 ->from('cart_rule_product_rule') 249 ->where('`id_product_rule_group` = '.(int) $productsRulesGroupSource['id_product_rule_group']) 250 ); 251 252 foreach ($productsRulesSource as $productRuleSource) { 253 Db::getInstance()->insert( 254 'cart_rule_product_rule', 255 [ 256 'id_product_rule_group' => (int) $idProductRuleGroupDestination, 257 'type' => pSQL($productRuleSource['type']), 258 ] 259 ); 260 $idProductRuleDestination = Db::getInstance()->Insert_ID(); 261 262 $productsRulesValuesSource = Db::getInstance()->executeS( 263 (new DbQuery()) 264 ->select('`id_item`') 265 ->from('cart_rule_product_rule_value') 266 ->where('`id_product_rule` = '.(int) $productsRulesSource['id_product_rule']) 267 ); 268 269 foreach ($productsRulesValuesSource as $productRuleValueSource) { 270 Db::getInstance()->insert( 271 'cart_rule_product_rule_value', 272 [ 273 'id_product_rule' => (int) $idProductRuleDestination, 274 'id_item' => (int) $productRuleValueSource['id_item'], 275 276 ] 277 ); 278 } 279 } 280 } 281 } 282 283 /** 284 * Retrieves the id associated to the given code 285 * 286 * @param string $code 287 * 288 * @return int|bool 289 * 290 * @since 1.0.0 291 * @version 1.0.0 Initial version 292 * @throws PrestaShopException 293 */ 294 public static function getIdByCode($code) 295 { 296 if (!Validate::isCleanHtml($code)) { 297 return false; 298 } 299 300 return Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue( 301 (new DbQuery()) 302 ->select('`id_cart_rule`') 303 ->from('cart_rule') 304 ->where('`code` = \''.pSQL($code).'\'') 305 ); 306 } 307 308 /** 309 * @param int $idLang 310 * @param int $idCustomer 311 * @param bool $active 312 * @param bool $includeGeneric 313 * @param bool $inStock 314 * @param Cart|null $cart 315 * @param bool $freeShippingOnly 316 * @param bool $highlightOnly 317 * 318 * @return array 319 * @throws PrestaShopDatabaseException 320 * 321 * @since 1.0.0 322 * @version 1.0.0 Initial version 323 * @throws PrestaShopException 324 * @throws PrestaShopException 325 * @throws PrestaShopException 326 * @throws PrestaShopException 327 * @throws PrestaShopException 328 */ 329 public static function getCustomerCartRules($idLang, $idCustomer, $active = false, $includeGeneric = true, $inStock = false, Cart $cart = null, $freeShippingOnly = false, $highlightOnly = false) 330 { 331 if (!static::isFeatureActive()) { 332 return []; 333 } 334 335 $sql = (new DbQuery()) 336 ->select('*') 337 ->from('cart_rule', 'cr') 338 ->leftJoin('cart_rule_lang', 'crl', 'cr.`id_cart_rule` = crl.`id_cart_rule` AND crl.`id_lang` = '.(int) $idLang) 339 ->where('cr.`date_from` < \''.date('Y-m-d H:i:s').'\'') 340 ->where('cr.`date_to` > \''.date('Y-m-d H:i:s').'\''); 341 if ($active) { 342 $sql->where('cr.`active` = 1'); 343 } 344 if ($inStock) { 345 $sql->where('cr.`quantity` > 0'); 346 } 347 if ($freeShippingOnly) { 348 $sql->where('`free_shipping` = 1'); 349 $sql->where('`carrier_restriction` = 1'); 350 } 351 if ($highlightOnly) { 352 $sql->where('`highlight` = 1'); 353 $sql->where('`code` NOT LIKE \''.pSQL(static::BO_ORDER_CODE_PREFIX).'%\''); 354 } 355 $sql->where('cr.`id_customer` = '.(int) $idCustomer.' OR cr.`group_restriction` = 1'.(($includeGeneric && (int) $idCustomer !== 0) ? ' OR cr.`id_customer` = 0' : '')); 356 357 $result = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS($sql, true); 358 359 if (empty($result)) { 360 return []; 361 } 362 363 // Remove cart rule that does not match the customer groups 364 $customerGroups = Customer::getGroupsStatic($idCustomer); 365 366 foreach ($result as $key => $cartRule) { 367 if ($cartRule['group_restriction']) { 368 $cartRuleGroups = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS( 369 (new DbQuery()) 370 ->select('`id_group`') 371 ->from('cart_rule_group') 372 ->where('id_cart_rule = '.(int) $cartRule['id_cart_rule']) 373 ); 374 foreach ($cartRuleGroups as $cartRuleGroup) { 375 if (in_array($cartRuleGroup['id_group'], $customerGroups)) { 376 continue 2; 377 } 378 } 379 unset($result[$key]); 380 } 381 } 382 383 foreach ($result as &$cartRule) { 384 if ($cartRule['quantity_per_user']) { 385 $quantityUsed = Order::getDiscountsCustomer((int) $idCustomer, (int) $cartRule['id_cart_rule']); 386 if (isset($cart) && isset($cart->id)) { 387 $quantityUsed += $cart->getDiscountsCustomer((int) $cartRule['id_cart_rule']); 388 } 389 $cartRule['quantity_for_user'] = $cartRule['quantity_per_user'] - $quantityUsed; 390 } else { 391 $cartRule['quantity_for_user'] = 0; 392 } 393 // Backwards compatibility 394 $cartRule['id_group'] = 0; 395 if ($cartRule['free_shipping']) { 396 $cartRule['id_discount_type'] = 3; 397 } elseif ($cartRule['reduction_percent'] > 0) { 398 $cartRule['id_discount_type'] = 1; 399 } elseif ($cartRule['reduction_amount'] > 0) { 400 $cartRule['id_discount_type'] = 2; 401 } 402 if ($cartRule['reduction_percent'] > 0) { 403 $cartRule['value'] = $cartRule['reduction_percent']; 404 } elseif ($cartRule['reduction_amount'] > 0) { 405 $cartRule['value'] = $cartRule['reduction_amount']; 406 } 407 $cartRule['cumulable'] = $cartRule['cart_rule_restriction']; 408 $cartRule['cumulable_reduction'] = false; 409 $cartRule['minimal'] = $cartRule['minimum_amount']; 410 $cartRule['include_tax'] = $cartRule['reduction_tax']; 411 $cartRule['behavior_not_exhausted'] = $cartRule['partial_use']; 412 $cartRule['cart_display'] = true; 413 } 414 unset($cartRule); 415 416 foreach ($result as $key => $cartRule) { 417 if ($cartRule['shop_restriction']) { 418 $cartRuleShops = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS( 419 (new DbQuery()) 420 ->select('`id_shop`') 421 ->from('cart_rule_shop') 422 ->where('`id_cart_rule` = '.(int) $cartRule['id_cart_rule']) 423 ); 424 foreach ($cartRuleShops as $cartRuleShop) { 425 if (Shop::isFeatureActive() && ($cartRuleShop['id_shop'] == Context::getContext()->shop->id)) { 426 continue 2; 427 } 428 } 429 unset($result[$key]); 430 } 431 } 432 433 if (isset($cart) && isset($cart->id)) { 434 foreach ($result as $key => $cartRule) { 435 if ($cartRule['product_restriction']) { 436 $cr = new CartRule((int) $cartRule['id_cart_rule']); 437 $r = $cr->checkProductRestrictions(Context::getContext(), false, false); 438 if ($r !== false) { 439 continue; 440 } 441 unset($result[$key]); 442 } 443 } 444 } 445 446 $resultBak = $result; 447 $result = []; 448 $countryRestriction = false; 449 foreach ($resultBak as $key => $cartRule) { 450 if ($cartRule['country_restriction']) { 451 $countryRestriction = true; 452 $countries = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS( 453 (new DbQuery()) 454 ->select('`id_country`') 455 ->from('address') 456 ->where('`id_customer` = '.(int) $idCustomer) 457 ->where('`deleted` = 0') 458 ); 459 460 if (is_array($countries) && !empty($countries)) { 461 foreach ($countries as $country) { 462 $idCartRule = (bool) Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue( 463 (new DbQuery()) 464 ->select('crc.`id_cart_rule`') 465 ->from('cart_rule_country', 'crc') 466 ->where('crc.`id_cart_rule` = '.(int) $cartRule['id_cart_rule']) 467 ->where('crc.`id_country` = '.(int) $country['id_country']) 468 ); 469 if ($idCartRule) { 470 $result[] = $resultBak[$key]; 471 break; 472 } 473 } 474 } 475 } else { 476 $result[] = $resultBak[$key]; 477 } 478 } 479 480 if (!$countryRestriction) { 481 $result = $resultBak; 482 } 483 484 return $result; 485 } 486 487 /** 488 * @return bool 489 * 490 * @since 1.0.0 491 * @version 1.0.0 Initial version 492 * @throws PrestaShopException 493 */ 494 public static function isFeatureActive() 495 { 496 static $isFeatureActive = null; 497 if ($isFeatureActive === null) { 498 $isFeatureActive = (bool) Configuration::get('PS_CART_RULE_FEATURE_ACTIVE'); 499 } 500 501 return $isFeatureActive; 502 } 503 504 /** 505 * @param Context $context 506 * @param bool $returnProducts 507 * @param bool $displayError 508 * @param bool $alreadyInCart 509 * 510 * @return array|bool|mixed|string 511 * 512 * @throws PrestaShopDatabaseException 513 * @throws PrestaShopException 514 * @since 1.0.0 515 * @version 1.0.0 Initial version 516 */ 517 protected function checkProductRestrictions(Context $context, $returnProducts = false, $displayError = true, $alreadyInCart = false) 518 { 519 $selectedProducts = []; 520 521 // Check if the products chosen by the customer are usable with the cart rule 522 if ($this->product_restriction) { 523 $productRuleGroups = $this->getProductRuleGroups(); 524 foreach ($productRuleGroups as $idProductRuleGroup => $productRuleGroup) { 525 $eligibleProductsList = []; 526 if (isset($context->cart) && is_object($context->cart) && is_array($products = $context->cart->getProducts())) { 527 foreach ($products as $product) { 528 $eligibleProductsList[] = (int) $product['id_product'].'-'.(int) $product['id_product_attribute']; 529 } 530 } 531 if (!count($eligibleProductsList)) { 532 return (!$displayError) ? false : Tools::displayError('You cannot use this voucher in an empty cart'); 533 } 534 535 $productRules = $this->getProductRules($idProductRuleGroup); 536 foreach ($productRules as $productRule) { 537 switch ($productRule['type']) { 538 case 'attributes': 539 $cartAttributes = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS( 540 (new DbQuery()) 541 ->select('cp.`quantity`, cp.`id_product`, pac.`id_attribute`, cp.`id_product_attribute`') 542 ->from('cart_product', 'cp') 543 ->leftJoin('product_attribute_combination', 'pac', 'cp.`id_product_attribute` = pac.`id_product_attribute`') 544 ->where('cp.`id_cart` = '.(int) $context->cart->id) 545 ->where('cp.`id_product` IN ('.implode(',', array_map('intval', $eligibleProductsList)).')') 546 ->where('cp.`id_product_attribute` > 0') 547 ); 548 $countMatchingProducts = 0; 549 $matchingProductsList = []; 550 foreach ($cartAttributes as $cartAttribute) { 551 if (in_array($cartAttribute['id_attribute'], $productRule['values'])) { 552 $countMatchingProducts += $cartAttribute['quantity']; 553 if ($alreadyInCart && $this->gift_product == $cartAttribute['id_product'] 554 && $this->gift_product_attribute == $cartAttribute['id_product_attribute'] 555 ) { 556 --$countMatchingProducts; 557 } 558 $matchingProductsList[] = $cartAttribute['id_product'].'-'.$cartAttribute['id_product_attribute']; 559 } 560 } 561 if ($countMatchingProducts < $productRuleGroup['quantity']) { 562 return (!$displayError) ? false : Tools::displayError('You cannot use this voucher with these products'); 563 } 564 $eligibleProductsList = static::array_uintersect($eligibleProductsList, $matchingProductsList); 565 break; 566 case 'products': 567 $cartProducts = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS( 568 (new DbQuery()) 569 ->select('cp.`quantity`, cp.`id_product`') 570 ->from('cart_product', 'cp') 571 ->where('cp.`id_cart` = '.(int) $context->cart->id) 572 ->where('cp.`id_product` IN ('.implode(',', array_map('intval', $eligibleProductsList)).')') 573 ); 574 $countMatchingProducts = 0; 575 $matchingProductsList = []; 576 foreach ($cartProducts as $cartProduct) { 577 if (in_array($cartProduct['id_product'], $productRule['values'])) { 578 $countMatchingProducts += $cartProduct['quantity']; 579 if ($alreadyInCart && $this->gift_product == $cartProduct['id_product']) { 580 --$countMatchingProducts; 581 } 582 $matchingProductsList[] = $cartProduct['id_product'].'-0'; 583 } 584 } 585 if ($countMatchingProducts < $productRuleGroup['quantity']) { 586 return (!$displayError) ? false : Tools::displayError('You cannot use this voucher with these products'); 587 } 588 $eligibleProductsList = static::array_uintersect($eligibleProductsList, $matchingProductsList); 589 break; 590 case 'categories': 591 $cartCategories = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS( 592 (new DbQuery()) 593 ->select('cp.quantity, cp.`id_product`, cp.`id_product_attribute`, catp.`id_category`') 594 ->from('cart_product', 'cp') 595 ->leftJoin('category_product', 'catp', 'cp.`id_product` = catp.`id_product`') 596 ->where('cp.`id_cart` = '.(int) $context->cart->id) 597 ->where('cp.`id_product` IN ('.implode(',', array_map('intval', $eligibleProductsList)).')') 598 ->where('cp.`id_product` <> '.(int) $this->gift_product) 599 ); 600 $countMatchingProducts = 0; 601 $matchingProductsList = []; 602 foreach ($cartCategories as $cartCategory) { 603 if (in_array($cartCategory['id_category'], $productRule['values']) 604 /** 605 * We also check that the product is not already in the matching product list, 606 * because there are doubles in the query results (when the product is in multiple categories) 607 */ 608 && !in_array($cartCategory['id_product'].'-'.$cartCategory['id_product_attribute'], $matchingProductsList) 609 ) { 610 $countMatchingProducts += $cartCategory['quantity']; 611 $matchingProductsList[] = $cartCategory['id_product'].'-'.$cartCategory['id_product_attribute']; 612 } 613 } 614 if ($countMatchingProducts < $productRuleGroup['quantity']) { 615 return (!$displayError) ? false : Tools::displayError('You cannot use this voucher with these products'); 616 } 617 // Attribute id is not important for this filter in the global list, so the ids are replaced by 0 618 foreach ($matchingProductsList as &$matchingProduct) { 619 $matchingProduct = preg_replace('/^([0-9]+)-[0-9]+$/', '$1-0', $matchingProduct); 620 } 621 $eligibleProductsList = static::array_uintersect($eligibleProductsList, $matchingProductsList); 622 break; 623 case 'manufacturers': 624 $cartManufacturers = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS( 625 (new DbQuery()) 626 ->select('cp.quantity, cp.`id_product`, p.`id_manufacturer`') 627 ->from('cart_product', 'cp') 628 ->leftJoin('product', 'p', 'cp.`id_product` = p.`id_product`') 629 ->where('cp.`id_cart` = '.(int) $context->cart->id) 630 ->where('cp.`id_product` IN ('.implode(',', array_map('intval', $eligibleProductsList)).')') 631 ); 632 $countMatchingProducts = 0; 633 $matchingProductsList = []; 634 foreach ($cartManufacturers as $cartManufacturer) { 635 if (in_array($cartManufacturer['id_manufacturer'], $productRule['values'])) { 636 $countMatchingProducts += $cartManufacturer['quantity']; 637 $matchingProductsList[] = $cartManufacturer['id_product'].'-0'; 638 } 639 } 640 if ($countMatchingProducts < $productRuleGroup['quantity']) { 641 return (!$displayError) ? false : Tools::displayError('You cannot use this voucher with these products'); 642 } 643 $eligibleProductsList = static::array_uintersect($eligibleProductsList, $matchingProductsList); 644 break; 645 case 'suppliers': 646 $cartSuppliers = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS( 647 (new DbQuery()) 648 ->select('cp.`quantity`, cp.`id_product`, p.`id_supplier`') 649 ->from('cart_product', 'cp') 650 ->leftJoin('product', 'p', 'cp.`id_product` = p.`id_product`') 651 ->where('cp.`id_cart` = '.(int) $context->cart->id) 652 ->where('cp.`id_product` IN ('.implode(',', array_map('intval', $eligibleProductsList)).')') 653 ); 654 $countMatchingProducts = 0; 655 $matchingProductsList = []; 656 foreach ($cartSuppliers as $cartSupplier) { 657 if (in_array($cartSupplier['id_supplier'], $productRule['values'])) { 658 $countMatchingProducts += $cartSupplier['quantity']; 659 $matchingProductsList[] = $cartSupplier['id_product'].'-0'; 660 } 661 } 662 if ($countMatchingProducts < $productRuleGroup['quantity']) { 663 return (!$displayError) ? false : Tools::displayError('You cannot use this voucher with these products'); 664 } 665 $eligibleProductsList = static::array_uintersect($eligibleProductsList, $matchingProductsList); 666 break; 667 } 668 669 if (!count($eligibleProductsList)) { 670 return (!$displayError) ? false : Tools::displayError('You cannot use this voucher with these products'); 671 } 672 } 673 $selectedProducts = array_merge($selectedProducts, $eligibleProductsList); 674 } 675 } 676 677 if ($returnProducts) { 678 return $selectedProducts; 679 } 680 681 return (!$displayError) ? true : false; 682 } 683 684 /** 685 * @return array 686 * 687 * @throws PrestaShopDatabaseException 688 * @throws PrestaShopException 689 * @since 1.0.0 690 * @version 1.0.0 Initial version 691 */ 692 public function getProductRuleGroups() 693 { 694 if (!Validate::isLoadedObject($this) || $this->product_restriction == 0) { 695 return []; 696 } 697 698 $productRuleGroups = []; 699 $result = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS( 700 (new DbQuery()) 701 ->select('*') 702 ->from('cart_rule_product_rule_group') 703 ->where('`id_cart_rule` = '.(int) $this->id) 704 ); 705 foreach ($result as $row) { 706 if (!isset($productRuleGroups[$row['id_product_rule_group']])) { 707 $productRuleGroups[$row['id_product_rule_group']] = ['id_product_rule_group' => $row['id_product_rule_group'], 'quantity' => $row['quantity']]; 708 } 709 $productRuleGroups[$row['id_product_rule_group']]['product_rules'] = $this->getProductRules($row['id_product_rule_group']); 710 } 711 712 return $productRuleGroups; 713 } 714 715 /** 716 * @param int $idProductRuleGroup 717 * 718 * @return array ('type' => ? , 'values' => ?) 719 * @throws PrestaShopDatabaseException 720 * @throws PrestaShopException 721 */ 722 public function getProductRules($idProductRuleGroup) 723 { 724 if (!Validate::isLoadedObject($this) || $this->product_restriction == 0) { 725 return []; 726 } 727 728 $productRules = []; 729 $results = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS( 730 (new DbQuery()) 731 ->select('*') 732 ->from('cart_rule_product_rule', 'pr') 733 ->leftJoin('cart_rule_product_rule_value', 'prv', 'pr.`id_product_rule` = prv.`id_product_rule`') 734 ->where('pr.`id_product_rule_group` = '.(int) $idProductRuleGroup) 735 ); 736 foreach ($results as $row) { 737 if (!isset($productRules[$row['id_product_rule']])) { 738 $productRules[$row['id_product_rule']] = ['type' => $row['type'], 'values' => []]; 739 } 740 $productRules[$row['id_product_rule']]['values'][] = $row['id_item']; 741 } 742 743 return $productRules; 744 } 745 746 /** 747 * @param $array1 748 * @param $array2 749 * 750 * @return array 751 * 752 * @since 1.0.0 753 * @version 1.0.0 Initial version 754 */ 755 protected static function array_uintersect($array1, $array2) 756 { 757 $intersection = []; 758 foreach ($array1 as $value1) { 759 foreach ($array2 as $value2) { 760 if (static::array_uintersect_compare($value1, $value2) == 0) { 761 $intersection[] = $value1; 762 break 1; 763 } 764 } 765 } 766 767 return $intersection; 768 } 769 770 /** 771 * @param $a 772 * @param $b 773 * 774 * @return int 775 * 776 * @since 1.0.0 777 * @version 1.0.0 Initial version 778 */ 779 protected static function array_uintersect_compare($a, $b) 780 { 781 if ($a == $b) { 782 return 0; 783 } 784 785 $asplit = explode('-', $a); 786 $bsplit = explode('-', $b); 787 if ($asplit[0] == $bsplit[0] && (!(int) $asplit[1] || !(int) $bsplit[1])) { 788 return 0; 789 } 790 791 return 1; 792 } 793 794 /** 795 * @param string $name 796 * 797 * @return bool 798 * 799 * @since 1.0.0 800 * @version 1.0.0 Initial version 801 * @throws PrestaShopException 802 */ 803 public static function cartRuleExists($name) 804 { 805 if (!static::isFeatureActive()) { 806 return false; 807 } 808 809 return (bool) Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue( 810 (new DbQuery()) 811 ->select('`id_cart_rule`') 812 ->from('cart_rule') 813 ->where('`code` = \''.pSQL($name).'\'') 814 ); 815 } 816 817 /** 818 * @param int $idCustomer 819 * 820 * @return bool 821 * 822 * @since 1.0.0 823 * @version 1.0.0 Initial version 824 * @throws PrestaShopException 825 */ 826 public static function deleteByIdCustomer($idCustomer) 827 { 828 $return = true; 829 $cartRules = new PrestaShopCollection('CartRule'); 830 $cartRules->where('id_customer', '=', $idCustomer); 831 foreach ($cartRules as $cartRule) { 832 $return &= $cartRule->delete(); 833 } 834 835 return $return; 836 } 837 838 /** 839 * Make sure caches are empty 840 * Must be called before calling multiple time getContextualValue() 841 * 842 * @since 1.0.0 843 * @version 1.0.0 Initial version 844 */ 845 public static function cleanCache() 846 { 847 static::$onlyOneGift = []; 848 } 849 850 /** 851 * @param null $context 852 * 853 * @return array 854 * 855 * @throws PrestaShopDatabaseException 856 * @throws PrestaShopException 857 * @since 1.0.0 858 * @version 1.0.0 Initial version 859 */ 860 public static function autoRemoveFromCart($context = null) 861 { 862 if (!$context) { 863 $context = Context::getContext(); 864 } 865 if (!static::isFeatureActive() || !Validate::isLoadedObject($context->cart)) { 866 return []; 867 } 868 869 static $errors = []; 870 foreach ($context->cart->getCartRules() as $cartRule) { 871 /** @var CartRule $cartRuleObject */ 872 $cartRuleObject = $cartRule['obj']; 873 if ($error = $cartRuleObject->checkValidity($context, true)) { 874 $context->cart->removeCartRule($cartRuleObject->id); 875 $context->cart->update(); 876 $errors[] = $error; 877 } 878 } 879 880 return $errors; 881 } 882 883 /** 884 * @param Context|null $context 885 * 886 * @return mixed 887 * 888 * @since 1.0.0 889 * @version 1.0.0 Initial version 890 * @throws PrestaShopException 891 */ 892 public static function autoAddToCart(Context $context = null) 893 { 894 if ($context === null) { 895 $context = Context::getContext(); 896 } 897 if (!static::isFeatureActive() || !Validate::isLoadedObject($context->cart)) { 898 return; 899 } 900 901 $sql = ' 902 SELECT SQL_NO_CACHE cr.* 903 FROM '._DB_PREFIX_.'cart_rule cr 904 LEFT JOIN '._DB_PREFIX_.'cart_rule_shop crs ON cr.id_cart_rule = crs.id_cart_rule 905 '.(!$context->customer->id && Group::isFeatureActive() ? ' LEFT JOIN '._DB_PREFIX_.'cart_rule_group crg ON cr.id_cart_rule = crg.id_cart_rule' : '').' 906 LEFT JOIN '._DB_PREFIX_.'cart_rule_carrier crca ON cr.id_cart_rule = crca.id_cart_rule 907 '.($context->cart->id_carrier ? 'LEFT JOIN '._DB_PREFIX_.'carrier c ON (c.id_reference = crca.id_carrier AND c.deleted = 0)' : '').' 908 LEFT JOIN '._DB_PREFIX_.'cart_rule_country crco ON cr.id_cart_rule = crco.id_cart_rule 909 WHERE cr.active = 1 910 AND cr.code = "" 911 AND cr.quantity > 0 912 AND cr.date_from < "'.date('Y-m-d H:i:s').'" 913 AND cr.date_to > "'.date('Y-m-d H:i:s').'" 914 AND ( 915 cr.id_customer = 0 916 '.($context->customer->id ? 'OR cr.id_customer = '.(int) $context->cart->id_customer : '').' 917 ) 918 AND ( 919 cr.`carrier_restriction` = 0 920 '.($context->cart->id_carrier ? 'OR c.id_carrier = '.(int) $context->cart->id_carrier : '').' 921 ) 922 AND ( 923 cr.`shop_restriction` = 0 924 '.((Shop::isFeatureActive() && $context->shop->id) ? 'OR crs.id_shop = '.(int) $context->shop->id : '').' 925 ) 926 AND ( 927 cr.`group_restriction` = 0 928 '.($context->customer->id ? 'OR EXISTS ( 929 SELECT 1 930 FROM `'._DB_PREFIX_.'customer_group` cg 931 INNER JOIN `'._DB_PREFIX_.'cart_rule_group` crg ON cg.id_group = crg.id_group 932 WHERE cr.`id_cart_rule` = crg.`id_cart_rule` 933 AND cg.`id_customer` = '.(int) $context->customer->id.' 934 LIMIT 1 935 )' : (Group::isFeatureActive() ? 'OR crg.`id_group` = '.(int) Configuration::get('PS_UNIDENTIFIED_GROUP') : '')).' 936 ) 937 AND ( 938 cr.`reduction_product` <= 0 939 OR EXISTS ( 940 SELECT 1 941 FROM `'._DB_PREFIX_.'cart_product` 942 WHERE `'._DB_PREFIX_.'cart_product`.`id_product` = cr.`reduction_product` AND `id_cart` = '.(int) $context->cart->id.' 943 ) 944 ) 945 AND NOT EXISTS (SELECT 1 FROM '._DB_PREFIX_.'cart_cart_rule WHERE cr.id_cart_rule = '._DB_PREFIX_.'cart_cart_rule.id_cart_rule 946 AND id_cart = '.(int) $context->cart->id.') 947 ORDER BY priority'; 948 $result = Db::getInstance()->executeS($sql, true, false); 949 if ($result) { 950 $cartRules = ObjectModel::hydrateCollection('CartRule', $result); 951 if ($cartRules) { 952 foreach ($cartRules as $cartRule) { 953 /** @var CartRule $cartRule */ 954 if ($cartRule->checkValidity($context, false, false)) { 955 $context->cart->addCartRule($cartRule->id); 956 } 957 } 958 } 959 } 960 } 961 962 /** 963 * Check if this cart rule can be applied 964 * 965 * @param Context $context 966 * @param bool $alreadyInCart Check if the voucher is already on the cart 967 * @param bool $displayError Display error 968 * 969 * @return bool|mixed|string 970 * 971 * @since 1.0.0 972 * @version 1.0.0 Initial version 973 * @throws PrestaShopException 974 */ 975 public function checkValidity(Context $context, $alreadyInCart = false, $displayError = true, $checkCarrier = true) 976 { 977 if (!static::isFeatureActive()) { 978 return false; 979 } 980 981 if (!$this->active) { 982 return (!$displayError) ? false : Tools::displayError('This voucher is disabled'); 983 } 984 if (!$this->quantity) { 985 return (!$displayError) ? false : Tools::displayError('This voucher has already been used'); 986 } 987 if (strtotime($this->date_from) > time()) { 988 return (!$displayError) ? false : Tools::displayError('This voucher is not valid yet'); 989 } 990 if (strtotime($this->date_to) < time()) { 991 return (!$displayError) ? false : Tools::displayError('This voucher has expired'); 992 } 993 994 if ($context->cart->id_customer) { 995 $quantityUsed = Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue( 996 (new DbQuery()) 997 ->select('COUNT(*)') 998 ->from('orders', 'o') 999 ->leftJoin('order_cart_rule', 'od', 'od.`id_order` = o.`id_order`') 1000 ->where('o.`id_customer` = '.(int) $context->cart->id_customer) 1001 ->where('od.`id_cart_rule` = '.(int) $this->id) 1002 ->where('o.`current_state` != '.(int) Configuration::get('PS_OS_ERROR')) 1003 ); 1004 if ($quantityUsed + 1 > $this->quantity_per_user) { 1005 return (!$displayError) ? false : Tools::displayError('You cannot use this voucher anymore (usage limit reached)'); 1006 } 1007 } 1008 1009 // Get an intersection of the customer groups and the cart rule groups (if the customer is not logged in, the default group is Visitors) 1010 if ($this->group_restriction) { 1011 $idCartRule = (int) Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue( 1012 (new DbQuery()) 1013 ->select('crg.`id_cart_rule`') 1014 ->from('cart_rule_group', 'crg') 1015 ->where('crg.`id_cart_rule` = '.(int) $this->id) 1016 ->where('crg.`id_group` '.($context->cart->id_customer ? 'IN (SELECT cg.id_group FROM '._DB_PREFIX_.'customer_group cg WHERE cg.id_customer = '.(int) $context->cart->id_customer.')' : '= '.(int) Configuration::get('PS_UNIDENTIFIED_GROUP'))) 1017 ); 1018 if (!$idCartRule) { 1019 return (!$displayError) ? false : Tools::displayError('You cannot use this voucher'); 1020 } 1021 } 1022 1023 // Check if the customer delivery address is usable with the cart rule 1024 if ($this->country_restriction) { 1025 if (!$context->cart->id_address_delivery) { 1026 return (!$displayError) ? false : Tools::displayError('You must choose a delivery address before applying this voucher to your order'); 1027 } 1028 $idCartRule = (int) Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue( 1029 (new DbQuery()) 1030 ->select('crc.`id_cart_rule`') 1031 ->from('cart_rule_country', 'crc') 1032 ->where('crc.`id_cart_rule` = '.(int) $this->id) 1033 ->where('crc.`id_country` = (SELECT a.id_country FROM '._DB_PREFIX_.'address a WHERE a.id_address = '.(int) $context->cart->id_address_delivery.' LIMIT 1)') 1034 ); 1035 if (!$idCartRule) { 1036 return (!$displayError) ? false : Tools::displayError('You cannot use this voucher in your country of delivery'); 1037 } 1038 } 1039 1040 // Check if the carrier chosen by the customer is usable with the cart rule 1041 if ($this->carrier_restriction && $checkCarrier) { 1042 if (!$context->cart->id_carrier) { 1043 return (!$displayError) ? false : Tools::displayError('You must choose a carrier before applying this voucher to your order'); 1044 } 1045 $idCartRule = (int) Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue( 1046 (new DbQuery()) 1047 ->select('crc.`id_cart_rule`') 1048 ->from('cart_rule_carrier', 'crc') 1049 ->innerJoin('carrier', 'c', 'c.`id_reference` = crc.`id_carrier` AND c.`deleted` = 0') 1050 ->where('crc.`id_cart_rule` = '.(int) $this->id) 1051 ->where('c.`id_carrier` = '.(int) $context->cart->id_carrier) 1052 ); 1053 if (!$idCartRule) { 1054 return (!$displayError) ? false : Tools::displayError('You cannot use this voucher with this carrier'); 1055 } 1056 } 1057 1058 // Check if the cart rules appliy to the shop browsed by the customer 1059 if ($this->shop_restriction && $context->shop->id && Shop::isFeatureActive()) { 1060 $idCartRule = (int) Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue( 1061 (new DbQuery()) 1062 ->select('crs.`id_cart_rule`') 1063 ->from('cart_rule_shop', 'crs') 1064 ->where('crs.`id_cart_rule` = '.(int) $this->id) 1065 ->where('crs.`id_shop` = '.(int) $context->shop->id) 1066 ); 1067 if (!$idCartRule) { 1068 return (!$displayError) ? false : Tools::displayError('You cannot use this voucher'); 1069 } 1070 } 1071 1072 // Check if the products chosen by the customer are usable with the cart rule 1073 if ($this->product_restriction) { 1074 $r = $this->checkProductRestrictions($context, false, $displayError, $alreadyInCart); 1075 if ($r !== false && $displayError) { 1076 return $r; 1077 } elseif (!$r && !$displayError) { 1078 return false; 1079 } 1080 } 1081 1082 // Check if the cart rule is only usable by a specific customer, and if the current customer is the right one 1083 if ($this->id_customer && $context->cart->id_customer != $this->id_customer) { 1084 if (!Context::getContext()->customer->isLogged()) { 1085 return (!$displayError) ? false : (Tools::displayError('You cannot use this voucher').' - '.Tools::displayError('Please log in first')); 1086 } 1087 1088 return (!$displayError) ? false : Tools::displayError('You cannot use this voucher'); 1089 } 1090 1091 if ($this->minimum_amount && $checkCarrier) { 1092 // Minimum amount is converted to the contextual currency 1093 $minimumAmount = $this->minimum_amount; 1094 if ($this->minimum_amount_currency != Context::getContext()->currency->id) { 1095 $minimumAmount = Tools::convertPriceFull($minimumAmount, new Currency($this->minimum_amount_currency), Context::getContext()->currency); 1096 } 1097 1098 $cartTotal = $context->cart->getOrderTotal($this->minimum_amount_tax, Cart::ONLY_PRODUCTS); 1099 if ($this->minimum_amount_shipping) { 1100 $cartTotal += $context->cart->getOrderTotal($this->minimum_amount_tax, Cart::ONLY_SHIPPING); 1101 } 1102 $products = $context->cart->getProducts(); 1103 $cartRules = $context->cart->getCartRules(); 1104 1105 foreach ($cartRules as &$cartRule) { 1106 if ($cartRule['gift_product']) { 1107 foreach ($products as $key => &$product) { 1108 if (empty($product['gift']) 1109 && $product['id_product'] 1110 == $cartRule['gift_product'] 1111 && $product['id_product_attribute'] 1112 == $cartRule['gift_product_attribute']) { 1113 if ($this->minimum_amount_tax) { 1114 $cartTotal = $cartTotal - $product['price_wt']; 1115 } else { 1116 $cartTotal = $cartTotal - $product['price']; 1117 } 1118 } 1119 } 1120 } 1121 } 1122 1123 if ($cartTotal < $minimumAmount) { 1124 return (!$displayError) ? false : Tools::displayError('You have not reached the minimum amount required to use this voucher'); 1125 } 1126 } 1127 1128 /* This loop checks: 1129 - if the voucher is already in the cart 1130 - if a non compatible voucher is in the cart 1131 - if there are products in the cart (gifts excluded) 1132 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 1133 */ 1134 $nbProducts = Cart::getNbProducts($context->cart->id); 1135 $otherCartRules = []; 1136 if ($checkCarrier) { 1137 $otherCartRules = $context->cart->getCartRules(); 1138 } 1139 if (count($otherCartRules)) { 1140 foreach ($otherCartRules as $otherCartRule) { 1141 if ($otherCartRule['id_cart_rule'] == $this->id && !$alreadyInCart) { 1142 return (!$displayError) ? false : Tools::displayError('This voucher is already in your cart'); 1143 } 1144 if ($otherCartRule['gift_product']) { 1145 --$nbProducts; 1146 } 1147 1148 if ($this->cart_rule_restriction && $otherCartRule['cart_rule_restriction'] && $otherCartRule['id_cart_rule'] != $this->id) { 1149 $combinable = Db::getInstance()->getValue( 1150 ' 1151 SELECT id_cart_rule_1 1152 FROM '._DB_PREFIX_.'cart_rule_combination 1153 WHERE (id_cart_rule_1 = '.(int) $this->id.' AND id_cart_rule_2 = '.(int) $otherCartRule['id_cart_rule'].') 1154 OR (id_cart_rule_2 = '.(int) $this->id.' AND id_cart_rule_1 = '.(int) $otherCartRule['id_cart_rule'].')' 1155 ); 1156 if (!$combinable) { 1157 $cartRule = new CartRule((int) $otherCartRule['id_cart_rule'], $context->cart->id_lang); 1158 // The cart rules are not combinable and the cart rule currently in the cart has priority over the one tested 1159 if ($cartRule->priority <= $this->priority) { 1160 return (!$displayError) ? false : Tools::displayError('This voucher is not combinable with an other voucher already in your cart:').' '.$cartRule->name; 1161 } // 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 1162 else { 1163 $context->cart->removeCartRule($cartRule->id); 1164 } 1165 } 1166 } 1167 } 1168 } 1169 1170 if (!$nbProducts) { 1171 return (!$displayError) ? false : Tools::displayError('Cart is empty'); 1172 } 1173 1174 if (!$displayError) { 1175 return true; 1176 } 1177 } 1178 1179 /** 1180 * @param $type 1181 * @param $list 1182 * 1183 * @return bool 1184 * 1185 * @since 1.0.0 1186 * @version 1.0.0 Initial version 1187 * @throws PrestaShopException 1188 * @throws PrestaShopDatabaseException 1189 */ 1190 public static function cleanProductRuleIntegrity($type, $list) 1191 { 1192 // Type must be available in the 'type' enum of the table cart_rule_product_rule 1193 if (!in_array($type, ['products', 'categories', 'attributes', 'manufacturers', 'suppliers'])) { 1194 return false; 1195 } 1196 1197 // This check must not be removed because this var is used a few lines below 1198 $list = (is_array($list) ? implode(',', array_map('intval', $list)) : (int) $list); 1199 if (!preg_match('/^[0-9,]+$/', $list)) { 1200 return false; 1201 } 1202 1203 // Delete associated restrictions on cart rules 1204 Db::getInstance()->execute( 1205 ' 1206 DELETE crprv 1207 FROM `'._DB_PREFIX_.'cart_rule_product_rule` crpr 1208 LEFT JOIN `'._DB_PREFIX_.'cart_rule_product_rule_value` crprv ON crpr.`id_product_rule` = crprv.`id_product_rule` 1209 WHERE crpr.`type` = "'.pSQL($type).'" 1210 AND crprv.`id_item` IN ('.$list.')' 1211 ); // $list is checked a few lines above 1212 1213 // Delete the product rules that does not have any values 1214 if (Db::getInstance()->Affected_Rows() > 0) { 1215 Db::getInstance()->delete( 1216 'cart_rule_product_rule', 'NOT EXISTS (SELECT 1 FROM `'._DB_PREFIX_.'cart_rule_product_rule_value` 1217 WHERE `'._DB_PREFIX_.'cart_rule_product_rule`.`id_product_rule` = `'._DB_PREFIX_.'cart_rule_product_rule_value`.`id_product_rule`)' 1218 ); 1219 } 1220 // If the product rules were the only conditions of a product rule group, delete the product rule group 1221 if (Db::getInstance()->Affected_Rows() > 0) { 1222 Db::getInstance()->delete( 1223 'cart_rule_product_rule_group', 'NOT EXISTS (SELECT 1 FROM `'._DB_PREFIX_.'cart_rule_product_rule` 1224 WHERE `'._DB_PREFIX_.'cart_rule_product_rule`.`id_product_rule_group` = `'._DB_PREFIX_.'cart_rule_product_rule_group`.`id_product_rule_group`)' 1225 ); 1226 } 1227 1228 // If the product rule group were the only restrictions of a cart rule, update de cart rule restriction cache 1229 if (Db::getInstance()->Affected_Rows() > 0) { 1230 Db::getInstance()->execute( 1231 ' 1232 UPDATE `'._DB_PREFIX_.'cart_rule` cr 1233 LEFT JOIN `'._DB_PREFIX_.'cart_rule_product_rule_group` crprg ON cr.id_cart_rule = crprg.id_cart_rule 1234 SET product_restriction = IF(crprg.id_product_rule_group IS NULL, 0, 1)' 1235 ); 1236 } 1237 1238 return true; 1239 } 1240 1241 /** 1242 * @param string $name 1243 * @param int $idLang 1244 * 1245 * @param bool $extended 1246 * 1247 * @return array 1248 * 1249 * @throws PrestaShopDatabaseException 1250 * @throws PrestaShopException 1251 * @since 1.0.0 1252 * @version 1.0.0 Initial version 1253 */ 1254 public static function getCartsRuleByCode($name, $idLang, $extended = false) 1255 { 1256 $sqlBase = 'SELECT cr.*, crl.* 1257 FROM '._DB_PREFIX_.'cart_rule cr 1258 LEFT JOIN '._DB_PREFIX_.'cart_rule_lang crl ON (cr.id_cart_rule = crl.id_cart_rule AND crl.id_lang = '.(int) $idLang.')'; 1259 if ($extended) { 1260 return Db::getInstance()->executeS('('.$sqlBase.' WHERE code LIKE \'%'.pSQL($name).'%\') UNION ('.$sqlBase.' WHERE name LIKE \'%'.pSQL($name).'%\')'); 1261 } else { 1262 return Db::getInstance()->executeS($sqlBase.' WHERE code LIKE \'%'.pSQL($name).'%\''); 1263 } 1264 } 1265 1266 /** 1267 * @see ObjectModel::add() 1268 * 1269 * @since 1.0.0 1270 * @version 1.0.0 Initial version 1271 * @throws PrestaShopException 1272 */ 1273 public function add($autoDate = true, $nullValues = false) 1274 { 1275 if (!$this->reduction_currency) { 1276 $this->reduction_currency = (int) Configuration::get('PS_CURRENCY_DEFAULT'); 1277 } 1278 1279 if (!parent::add($autoDate, $nullValues)) { 1280 return false; 1281 } 1282 1283 Configuration::updateGlobalValue('PS_CART_RULE_FEATURE_ACTIVE', '1'); 1284 1285 return true; 1286 } 1287 1288 /** 1289 * @param bool $nullValues 1290 * 1291 * @return bool 1292 * 1293 * @since 1.0.0 1294 * @version 1.0.0 Initial version 1295 * @throws PrestaShopException 1296 */ 1297 public function update($nullValues = false) 1298 { 1299 Cache::clean('getContextualValue_'.$this->id.'_*'); 1300 1301 if (!$this->reduction_currency) { 1302 $this->reduction_currency = (int) Configuration::get('PS_CURRENCY_DEFAULT'); 1303 } 1304 1305 return parent::update($nullValues); 1306 } 1307 1308 /** 1309 * @see ObjectModel::delete() 1310 * 1311 * @since 1.0.0 1312 * @version 1.0.0 Initial version 1313 * @throws PrestaShopException 1314 */ 1315 public function delete() 1316 { 1317 if (!parent::delete()) { 1318 return false; 1319 } 1320 1321 Configuration::updateGlobalValue('PS_CART_RULE_FEATURE_ACTIVE', static::isCurrentlyUsed($this->def['table'], true)); 1322 1323 $r = Db::getInstance()->delete('cart_cart_rule', '`id_cart_rule` = '.(int) $this->id); 1324 $r &= Db::getInstance()->delete('cart_rule_carrier', '`id_cart_rule` = '.(int) $this->id); 1325 $r &= Db::getInstance()->delete('cart_rule_shop', '`id_cart_rule` = '.(int) $this->id); 1326 $r &= Db::getInstance()->delete('cart_rule_group', '`id_cart_rule` = '.(int) $this->id); 1327 $r &= Db::getInstance()->delete('cart_rule_country', '`id_cart_rule` = '.(int) $this->id); 1328 $r &= Db::getInstance()->delete('cart_rule_combination', '`id_cart_rule_1` = '.(int) $this->id.' OR `id_cart_rule_2` = '.(int) $this->id); 1329 $r &= Db::getInstance()->delete('cart_rule_product_rule_group', '`id_cart_rule` = '.(int) $this->id); 1330 $r &= Db::getInstance()->delete( 1331 'cart_rule_product_rule', 'NOT EXISTS (SELECT 1 FROM `'._DB_PREFIX_.'cart_rule_product_rule_group` 1332 WHERE `'._DB_PREFIX_.'cart_rule_product_rule`.`id_product_rule_group` = `'._DB_PREFIX_.'cart_rule_product_rule_group`.`id_product_rule_group`)' 1333 ); 1334 $r &= Db::getInstance()->delete( 1335 'cart_rule_product_rule_value', 'NOT EXISTS (SELECT 1 FROM `'._DB_PREFIX_.'cart_rule_product_rule` 1336 WHERE `'._DB_PREFIX_.'cart_rule_product_rule_value`.`id_product_rule` = `'._DB_PREFIX_.'cart_rule_product_rule`.`id_product_rule`)' 1337 ); 1338 1339 return $r; 1340 } 1341 1342 /** 1343 * @param int $idCustomer 1344 * 1345 * @return bool 1346 * 1347 * @since 1.0.0 1348 * @version 1.0.0 Initial version 1349 * @throws PrestaShopException 1350 */ 1351 public function usedByCustomer($idCustomer) 1352 { 1353 return (bool) Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue( 1354 (new DbQuery()) 1355 ->select('`id_cart_rule`') 1356 ->from('order_cart_rule', 'ocr') 1357 ->leftJoin('orders', 'o', 'ocr.`id_order` = o.`id_order`') 1358 ->where('ocr.`id_cart_rule` = '.(int) $this->id) 1359 ->where('o.`id_customer` = '.(int) $idCustomer) 1360 ); 1361 } 1362 1363 /** 1364 * The reduction value is POSITIVE 1365 * 1366 * @param bool $useTax 1367 * @param Context $context 1368 * @param null $filter 1369 * @param null $package 1370 * @param bool $useCache Allow using cache to avoid multiple free gift using multishipping 1371 * 1372 * @return float|int|string 1373 * @throws Adapter_Exception 1374 * @throws PrestaShopDatabaseException 1375 * @throws PrestaShopException 1376 * @since 1.0.0 1377 * @version 1.0.0 Initial version 1378 */ 1379 public function getContextualValue($useTax, Context $context = null, $filter = null, $package = null, $useCache = true) 1380 { 1381 if (!static::isFeatureActive()) { 1382 return 0; 1383 } 1384 if (!$context) { 1385 $context = Context::getContext(); 1386 } 1387 if (!$filter) { 1388 $filter = static::FILTER_ACTION_ALL; 1389 } 1390 $roundType = (int) Configuration::get('PS_ROUND_TYPE'); 1391 $displayDecimals = 0; 1392 if ($context->currency->decimals) { 1393 $displayDecimals = Configuration::get('PS_PRICE_DISPLAY_PRECISION'); 1394 } 1395 1396 $allProducts = $context->cart->getProducts(); 1397 $packageProducts = (is_null($package) ? $allProducts : $package['products']); 1398 1399 $reductionValue = 0; 1400 1401 $cacheId = 'getContextualValue_'.(int) $this->id.'_'.(int) $useTax.'_'.(int) $context->cart->id.'_'.(int) $filter; 1402 foreach ($packageProducts as $product) { 1403 $cacheId .= '_'.(int) $product['id_product'].'_'.(int) $product['id_product_attribute'].(isset($product['in_stock']) ? '_'.(int) $product['in_stock'] : ''); 1404 } 1405 1406 if (Cache::isStored($cacheId)) { 1407 return Cache::retrieve($cacheId); 1408 } 1409 1410 $allCartRulesIds = $context->cart->getOrderedCartRulesIds(); 1411 1412 $cartAmountTaxIncluded = $context->cart->getOrderTotal(true, Cart::ONLY_PRODUCTS); 1413 $cartAmountTaxExcluded = $context->cart->getOrderTotal(false, Cart::ONLY_PRODUCTS); 1414 1415 // Free shipping on selected carriers 1416 if ($this->free_shipping && in_array($filter, [static::FILTER_ACTION_ALL, static::FILTER_ACTION_ALL_NOCAP, static::FILTER_ACTION_SHIPPING])) { 1417 if (!$this->carrier_restriction) { 1418 $reductionValue += $context->cart->getOrderTotal($useTax, Cart::ONLY_SHIPPING, is_null($package) ? null : $package['products'], is_null($package) ? null : $package['id_carrier']); 1419 } else { 1420 $data = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS( 1421 (new DbQuery()) 1422 ->select('crc.`id_cart_rule`, c.`id_carrier`') 1423 ->from('cart_rule_carrier', 'crc') 1424 ->innerJoin('carrier', 'c', 'c.`id_reference` = crc.`id_carrier` AND c.`deleted` = 0') 1425 ->where('crc.`id_cart_rule` = '.(int) $this->id) 1426 ->where('c.`id_carrier` = '.(int) $context->cart->id_carrier) 1427 ); 1428 1429 if ($data) { 1430 foreach ($data as $cartRule) { 1431 $reductionValue += $context->cart->getCarrierCost((int) $cartRule['id_carrier'], $useTax, $context->country); 1432 } 1433 } 1434 } 1435 } 1436 1437 if (in_array($filter, [static::FILTER_ACTION_ALL, static::FILTER_ACTION_ALL_NOCAP, static::FILTER_ACTION_REDUCTION])) { 1438 // Discount (%) on the whole order 1439 if ($this->reduction_percent && $this->reduction_product == 0) { 1440 // Do not give a reduction on free products! 1441 $orderTotal = $context->cart->getOrderTotal($useTax, Cart::ONLY_PRODUCTS, $packageProducts); 1442 foreach ($context->cart->getCartRules(static::FILTER_ACTION_GIFT) as $cartRule) { 1443 $reduction = $cartRule['obj']->getContextualValue( 1444 $useTax, $context, 1445 static::FILTER_ACTION_GIFT, $package 1446 ); 1447 if ($roundType === Order::ROUND_ITEM) { 1448 $reduction = round($reduction, $displayDecimals); 1449 } 1450 $orderTotal -= $reduction; 1451 } 1452 1453 $reductionValue += round( 1454 $orderTotal * $this->reduction_percent / 100, 1455 _TB_PRICE_DATABASE_PRECISION_ 1456 ); 1457 } 1458 1459 // Discount (%) on a specific product 1460 if ($this->reduction_percent && $this->reduction_product > 0) { 1461 foreach ($packageProducts as $product) { 1462 if ($product['id_product'] == $this->reduction_product) { 1463 if ($useTax == true) { 1464 $reduction = $product['total_wt']; 1465 } else { 1466 $reduction = $product['total']; 1467 } 1468 $reductionValue += round( 1469 $reduction * $this->reduction_percent / 100, 1470 _TB_PRICE_DATABASE_PRECISION_ 1471 ); 1472 } 1473 } 1474 } 1475 1476 // Discount (%) on the cheapest product 1477 if ($this->reduction_percent && $this->reduction_product == -1) { 1478 $minPrice = false; 1479 $cheapestProduct = null; 1480 $selectedProducts = $this->checkProductRestrictions($context, true); 1481 foreach ($allProducts as $product) { 1482 if (!is_array($selectedProducts) || 1483 (!in_array($product['id_product'].'-'.$product['id_product_attribute'], $selectedProducts) && !in_array($product['id_product'].'-0', $selectedProducts)) 1484 ) { 1485 continue; 1486 } 1487 1488 $price = $product['price']; 1489 if ($useTax == true) { 1490 $price = round( 1491 $price * (1 + $product['rate'] / 100), 1492 _TB_PRICE_DATABASE_PRECISION_ 1493 ); 1494 } 1495 1496 if ($price > 0 && ($minPrice === false || $minPrice > $price)) { 1497 $minPrice = $price; 1498 $cheapestProduct = $product['id_product'].'-'.$product['id_product_attribute']; 1499 } 1500 } 1501 1502 // Check if the cheapest product is in the package 1503 $inPackage = false; 1504 foreach ($packageProducts as $product) { 1505 if ($product['id_product'].'-'.$product['id_product_attribute'] == $cheapestProduct || $product['id_product'].'-0' == $cheapestProduct) { 1506 $inPackage = true; 1507 } 1508 } 1509 if ($inPackage) { 1510 $reductionValue += round( 1511 $minPrice * $this->reduction_percent / 100, 1512 _TB_PRICE_DATABASE_PRECISION_ 1513 ); 1514 } 1515 } 1516 1517 // Discount (%) on the selection of products 1518 if ($this->reduction_percent && $this->reduction_product == -2) { 1519 $selectedProductsReduction = 0; 1520 $selectedProducts = $this->checkProductRestrictions($context, true); 1521 if (is_array($selectedProducts)) { 1522 foreach ($packageProducts as $product) { 1523 if (in_array($product['id_product'].'-'.$product['id_product_attribute'], $selectedProducts) 1524 || in_array($product['id_product'].'-0', $selectedProducts) 1525 ) { 1526 $price = $product['price']; 1527 if ($useTax == true) { 1528 $price = round( 1529 $price * (1 + $product['rate'] / 100), 1530 _TB_PRICE_DATABASE_PRECISION_ 1531 ); 1532 } 1533 1534 $selectedProductsReduction += $price * $product['cart_quantity']; 1535 } 1536 } 1537 } 1538 $reductionValue += round( 1539 $selectedProductsReduction * $this->reduction_percent / 100, 1540 _TB_PRICE_DATABASE_PRECISION_ 1541 ); 1542 } 1543 1544 // Discount (¤) 1545 if ($this->reduction_amount > 0) { 1546 $prorata = 1; 1547 if (!is_null($package) && count($allProducts)) { 1548 $totalProducts = $context->cart->getOrderTotal($useTax, Cart::ONLY_PRODUCTS); 1549 if ($totalProducts) { 1550 $prorata = $context->cart->getOrderTotal($useTax, Cart::ONLY_PRODUCTS, $package['products']) / $totalProducts; 1551 } 1552 } 1553 1554 $reductionAmount = $this->reduction_amount; 1555 $voucherCurrency = new Currency($this->reduction_currency); 1556 // First we convert the voucher value to the default currency. 1557 $reductionAmount = Tools::convertPrice( 1558 $reductionAmount, 1559 $voucherCurrency, 1560 false 1561 ); 1562 // Then we convert the voucher value to the cart currency. 1563 $reductionAmount = Tools::convertPrice( 1564 $reductionAmount, 1565 $context->currency, 1566 true 1567 ); 1568 1569 // If it has the same tax application that you need, then it's the right value, whatever the product! 1570 if ($this->reduction_tax == $useTax) { 1571 // The reduction cannot exceed the products total, except when we do not want it to be limited (for the partial use calculation) 1572 if ($filter != static::FILTER_ACTION_ALL_NOCAP) { 1573 $cartAmount = $context->cart->getOrderTotal($useTax, Cart::ONLY_PRODUCTS); 1574 $reductionAmount = min($reductionAmount, $cartAmount); 1575 } 1576 $reductionValue += $prorata * $reductionAmount; 1577 } else { 1578 if ($this->reduction_product > 0) { 1579 foreach ($context->cart->getProducts() as $product) { 1580 if ($product['id_product'] == $this->reduction_product) { 1581 $productPriceTaxIncluded = $product['price_wt']; 1582 $productPriceTaxExcluded = $product['price']; 1583 $productVatAmount = $productPriceTaxIncluded - $productPriceTaxExcluded; 1584 1585 if ($productVatAmount == 0 || $productPriceTaxExcluded == 0) { 1586 $productVatRate = 0; 1587 } else { 1588 $productVatRate = $productVatAmount / $productPriceTaxExcluded; 1589 } 1590 1591 if ($this->reduction_tax && !$useTax) { 1592 $reductionValue += round( 1593 $prorata * $reductionAmount 1594 / (1 + $productVatRate), 1595 _TB_PRICE_DATABASE_PRECISION_ 1596 ); 1597 } elseif (!$this->reduction_tax && $useTax) { 1598 $reductionValue += round( 1599 $prorata * $reductionAmount 1600 * (1 + $productVatRate), 1601 _TB_PRICE_DATABASE_PRECISION_ 1602 ); 1603 } 1604 } 1605 } 1606 } // Discount (¤) on the whole order 1607 elseif ($this->reduction_product == 0) { 1608 $cartAmountTaxExcluded = null; 1609 $cartAmountTaxIncluded = null; 1610 $cartAverageVatRate = $context->cart->getAverageProductsTaxRate($cartAmountTaxExcluded, $cartAmountTaxIncluded); 1611 1612 // The reduction cannot exceed the products total, except when we do not want it to be limited (for the partial use calculation) 1613 if ($filter != static::FILTER_ACTION_ALL_NOCAP) { 1614 $reductionAmount = min($reductionAmount, $this->reduction_tax ? $cartAmountTaxIncluded : $cartAmountTaxExcluded); 1615 } 1616 1617 if ($this->reduction_tax && !$useTax) { 1618 $reductionValue += round( 1619 $prorata * $reductionAmount 1620 / (1 + $cartAverageVatRate), 1621 _TB_PRICE_DATABASE_PRECISION_ 1622 ); 1623 } elseif (!$this->reduction_tax && $useTax) { 1624 $reductionValue += round( 1625 $prorata * $reductionAmount 1626 * (1 + $cartAverageVatRate), 1627 _TB_PRICE_DATABASE_PRECISION_ 1628 ); 1629 } 1630 } 1631 /* 1632 * Reduction on the cheapest or on the selection is not really meaningful and has been disabled in the backend 1633 * Please keep this code, so it won't be considered as a bug 1634 * elseif ($this->reduction_product == -1) 1635 * elseif ($this->reduction_product == -2) 1636 */ 1637 } 1638 1639 // Take care of the other cart rules values if the filter allow it 1640 if ($filter != static::FILTER_ACTION_ALL_NOCAP) { 1641 // Cart values 1642 $cart = Context::getContext()->cart; 1643 1644 if (!Validate::isLoadedObject($cart)) { 1645 $cart = new Cart(); 1646 } 1647 1648 $cartAverageVatRate = $cart->getAverageProductsTaxRate(); 1649 $currentCartAmount = $useTax ? $cartAmountTaxIncluded : $cartAmountTaxExcluded; 1650 1651 foreach ($allCartRulesIds as $currentCartRuleId) { 1652 if ((int) $currentCartRuleId['id_cart_rule'] == (int) $this->id) { 1653 break; 1654 } 1655 1656 $previousCartRule = new CartRule((int) $currentCartRuleId['id_cart_rule']); 1657 $previousReductionAmount = $previousCartRule->reduction_amount; 1658 1659 if ($previousCartRule->reduction_tax && !$useTax) { 1660 $previousReductionAmount = round( 1661 $previousReductionAmount 1662 * $prorata 1663 / (1 + $cartAverageVatRate), 1664 _TB_PRICE_DATABASE_PRECISION_ 1665 ); 1666 } elseif (!$previousCartRule->reduction_tax && $useTax) { 1667 $previousReductionAmount = round( 1668 $previousReductionAmount 1669 * $prorata 1670 * (1 + $cartAverageVatRate), 1671 _TB_PRICE_DATABASE_PRECISION_ 1672 ); 1673 } 1674 1675 $currentCartAmount = max($currentCartAmount - (float) $previousReductionAmount, 0); 1676 } 1677 1678 $reductionValue = min($reductionValue, $currentCartAmount); 1679 } 1680 } 1681 1682 if ($roundType === Order::ROUND_LINE) { 1683 $reductionValue = Tools::ps_round( 1684 $reductionValue, 1685 $displayDecimals 1686 ); 1687 } 1688 } 1689 1690 // Free gift 1691 if ((int) $this->gift_product && in_array($filter, [static::FILTER_ACTION_ALL, static::FILTER_ACTION_ALL_NOCAP, static::FILTER_ACTION_GIFT])) { 1692 $idAddress = (is_null($package) ? 0 : $package['id_address']); 1693 foreach ($packageProducts as $product) { 1694 if ($product['id_product'] == $this->gift_product && ($product['id_product_attribute'] == $this->gift_product_attribute || !(int) $this->gift_product_attribute)) { 1695 // The free gift coupon must be applied to one product only (needed for multi-shipping which manage multiple product lists) 1696 if (!isset(static::$onlyOneGift[$this->id.'-'.$this->gift_product]) 1697 || static::$onlyOneGift[$this->id.'-'.$this->gift_product] == $idAddress 1698 || static::$onlyOneGift[$this->id.'-'.$this->gift_product] == 0 1699 || $idAddress == 0 1700 || !$useCache 1701 ) { 1702 $reductionValue += ($useTax ? $product['price_wt'] : $product['price']); 1703 if ($useCache && (!isset(static::$onlyOneGift[$this->id.'-'.$this->gift_product]) || static::$onlyOneGift[$this->id.'-'.$this->gift_product] == 0)) { 1704 static::$onlyOneGift[$this->id.'-'.$this->gift_product] = $idAddress; 1705 } 1706 break; 1707 } 1708 } 1709 } 1710 } 1711 1712 Cache::store($cacheId, $reductionValue); 1713 1714 return $reductionValue; 1715 } 1716 1717 /* When an entity associated to a product rule (product, category, attribute, supplier, manufacturer...) is deleted, the product rules must be updated */ 1718 1719 /** 1720 * @param string $type 1721 * @param bool $activeOnly 1722 * @param bool $i18n 1723 * @param int $offset 1724 * @param int $limit 1725 * @param string $searchCartRuleName 1726 * 1727 * @return array|bool 1728 * @throws PrestaShopDatabaseException 1729 * 1730 * @since 1.0.0 1731 * @version 1.0.0 Initial version 1732 * @throws PrestaShopException 1733 * @throws PrestaShopException 1734 */ 1735 public function getAssociatedRestrictions($type, $activeOnly, $i18n, $offset = null, $limit = null, $searchCartRuleName = '') 1736 { 1737 $array = ['selected' => [], 'unselected' => []]; 1738 1739 if (!in_array($type, ['country', 'carrier', 'group', 'cart_rule', 'shop'])) { 1740 return false; 1741 } 1742 1743 $shopList = ''; 1744 if ($type == 'shop') { 1745 $shops = Context::getContext()->employee->getAssociatedShops(); 1746 if (count($shops)) { 1747 $shopList = ' AND t.id_shop IN ('.implode(array_map('intval', $shops), ',').') '; 1748 } 1749 } 1750 1751 if ($offset !== null && $limit !== null) { 1752 $sqlLimit = ' LIMIT '.(int) $offset.', '.(int) ($limit + 1); 1753 } else { 1754 $sqlLimit = ''; 1755 } 1756 1757 if (!Validate::isLoadedObject($this) || $this->{$type.'_restriction'} == 0) { 1758 $array['selected'] = Db::getInstance()->executeS( 1759 ' 1760 SELECT t.*'.($i18n ? ', tl.*' : '').', 1 as selected 1761 FROM `'._DB_PREFIX_.$type.'` t 1762 '.($i18n ? 'LEFT JOIN `'._DB_PREFIX_.$type.'_lang` tl ON (t.id_'.$type.' = tl.id_'.$type.' AND tl.id_lang = '.(int) Context::getContext()->language->id.')' : '').' 1763 WHERE 1 1764 '.($activeOnly ? 'AND t.active = 1' : '').' 1765 '.(in_array($type, ['carrier', 'shop']) ? ' AND t.deleted = 0' : '').' 1766 '.($type == 'cart_rule' ? 'AND t.id_cart_rule != '.(int) $this->id : ''). 1767 $shopList. 1768 (in_array($type, ['carrier', 'shop']) ? ' ORDER BY t.name ASC ' : ''). 1769 (in_array($type, ['country', 'group', 'cart_rule']) && $i18n ? ' ORDER BY tl.name ASC ' : ''). 1770 $sqlLimit 1771 ); 1772 } else { 1773 if ($type == 'cart_rule') { 1774 $array = $this->getCartRuleCombinations($offset, $limit, $searchCartRuleName); 1775 } else { 1776 $resource = Db::getInstance()->executeS( 1777 ' 1778 SELECT t.*'.($i18n ? ', tl.*' : '').', IF(crt.id_'.$type.' IS NULL, 0, 1) as selected 1779 FROM `'._DB_PREFIX_.$type.'` t 1780 '.($i18n ? 'LEFT JOIN `'._DB_PREFIX_.$type.'_lang` tl ON (t.id_'.$type.' = tl.id_'.$type.' AND tl.id_lang = '.(int) Context::getContext()->language->id.')' : '').' 1781 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.' 1782 WHERE 1 '.($activeOnly ? ' AND t.active = 1' : ''). 1783 $shopList 1784 .(in_array($type, ['carrier', 'shop']) ? ' AND t.deleted = 0' : ''). 1785 (in_array($type, ['carrier', 'shop']) ? ' ORDER BY t.name ASC ' : ''). 1786 (in_array($type, ['country', 'group', 'cart_rule']) && $i18n ? ' ORDER BY tl.name ASC ' : ''). 1787 $sqlLimit, 1788 false 1789 ); 1790 foreach ($resource as $row) { 1791 $array[($row['selected'] || $this->{$type.'_restriction'} == 0) ? 'selected' : 'unselected'][] = $row; 1792 } 1793 } 1794 } 1795 1796 return $array; 1797 } 1798 1799 /** 1800 * Find the cheapest product 1801 * 1802 * @param array $package 1803 * 1804 * @return null|string 1805 * 1806 * @throws PrestaShopDatabaseException 1807 * @throws PrestaShopException 1808 * @since 1.0.2 1809 */ 1810 public function findCheapestProduct($package) 1811 { 1812 $context = Context::getContext(); 1813 $cheapestProduct = null; 1814 $allProducts = $package['products']; 1815 1816 if ($this->reduction_percent && $this->reduction_product == -1) { 1817 $minPrice = false; 1818 $selectedProducts = $this->checkProductRestrictions($context, true); 1819 foreach ($allProducts as $product) { 1820 if (!is_array($selectedProducts) || 1821 (!in_array($product['id_product'].'-'.$product['id_product_attribute'], $selectedProducts) && !in_array($product['id_product'].'-0', $selectedProducts)) 1822 ) { 1823 continue; 1824 } 1825 1826 $price = $product['price']; 1827 if ($price > 0 && ($minPrice === false || $minPrice > $price)) { 1828 $minPrice = $price; 1829 $cheapestProduct = $product['id_product'].'-'.$product['id_product_attribute']; 1830 } 1831 } 1832 } 1833 1834 return $cheapestProduct; 1835 } 1836 1837 /** 1838 * @param int $offset 1839 * @param int $limit 1840 * @param string $search 1841 * 1842 * @return array 1843 * 1844 * @throws PrestaShopDatabaseException 1845 * @throws PrestaShopException 1846 * @since 1.0.0 1847 * @version 1.0.0 Initial version 1848 */ 1849 protected function getCartRuleCombinations($offset = null, $limit = null, $search = '') 1850 { 1851 $array = []; 1852 if ($offset !== null && $limit !== null) { 1853 $sqlLimit = ' LIMIT '.(int) $offset.', '.(int) ($limit + 1); 1854 } else { 1855 $sqlLimit = ''; 1856 } 1857 1858 $array['selected'] = Db::getInstance()->executeS( 1859 ' 1860 SELECT cr.*, crl.*, 1 AS selected 1861 FROM '._DB_PREFIX_.'cart_rule cr 1862 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.') 1863 WHERE cr.id_cart_rule != '.(int) $this->id.($search ? ' AND crl.name LIKE "%'.pSQL($search).'%"' : '').' 1864 AND ( 1865 cr.cart_rule_restriction = 0 1866 OR EXISTS ( 1867 SELECT 1 1868 FROM '._DB_PREFIX_.'cart_rule_combination 1869 WHERE cr.id_cart_rule = '._DB_PREFIX_.'cart_rule_combination.id_cart_rule_1 AND '.(int) $this->id.' = id_cart_rule_2 1870 ) 1871 OR EXISTS ( 1872 SELECT 1 1873 FROM '._DB_PREFIX_.'cart_rule_combination 1874 WHERE cr.id_cart_rule = '._DB_PREFIX_.'cart_rule_combination.id_cart_rule_2 AND '.(int) $this->id.' = id_cart_rule_1 1875 ) 1876 ) ORDER BY cr.id_cart_rule'.$sqlLimit 1877 ); 1878 1879 $array['unselected'] = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS( 1880 (new DbQuery()) 1881 ->select('cr.*, crl.*, 1 AS `selected`') 1882 ->from('cart_rule', 'cr') 1883 ->innerJoin('cart_rule_lang', 'crl', 'cr.`id_cart_rule` = crl.`id_cart_rule` AND crl.`id_lang` = '.(int) Context::getContext()->language->id) 1884 ->leftJoin('cart_rule_combination', 'crc1', 'cr.`id_cart_rule` = crc1.`id_cart_rule_1` AND crc1.`id_cart_rule_2` = '.(int) $this->id) 1885 ->leftJoin('cart_rule_combination', 'crc2', 'cr.`id_cart_rule` = crc2.`id_cart_rule_2` AND crc2.`id_cart_rule_1` = '.(int) $this->id) 1886 ->where('cr.`cart_rule_restriction` = 1') 1887 ->where('cr.`id_cart_rule` != '.(int) $this->id) 1888 ->where($search ? 'crl.`name` LIKE "%'.pSQL($search).'%"' : '') 1889 ->where('crc1.`id_cart_rule_1` IS NULL') 1890 ->where('crc2.`id_cart_rule_1` IS NULL') 1891 ->orderBy('cr.`id_cart_rule`') 1892 ->limit($limit, $offset) 1893 ); 1894 1895 return $array; 1896 } 1897} 1898