PageRenderTime 48ms CodeModel.GetById 15ms RepoModel.GetById 1ms app.codeStats 0ms

/app/code/core/Mage/SalesRule/Model/Validator.php

https://bitbucket.org/claudiu_marginean/magento-hg-mirror
PHP | 793 lines | 487 code | 86 blank | 220 comment | 75 complexity | b3be51ba9e43c939758bf1556321d35e MD5 | raw file
Possible License(s): CC-BY-SA-3.0, LGPL-2.1, GPL-2.0, WTFPL
  1. <?php
  2. /**
  3. * Magento
  4. *
  5. * NOTICE OF LICENSE
  6. *
  7. * This source file is subject to the Open Software License (OSL 3.0)
  8. * that is bundled with this package in the file LICENSE.txt.
  9. * It is also available through the world-wide-web at this URL:
  10. * http://opensource.org/licenses/osl-3.0.php
  11. * If you did not receive a copy of the license and are unable to
  12. * obtain it through the world-wide-web, please send an email
  13. * to license@magentocommerce.com so we can send you a copy immediately.
  14. *
  15. * DISCLAIMER
  16. *
  17. * Do not edit or add to this file if you wish to upgrade Magento to newer
  18. * versions in the future. If you wish to customize Magento for your
  19. * needs please refer to http://www.magentocommerce.com for more information.
  20. *
  21. * @category Mage
  22. * @package Mage_SalesRule
  23. * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com)
  24. * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
  25. */
  26. /**
  27. * SalesRule Validator Model
  28. *
  29. * Allows dispatching before and after events for each controller action
  30. *
  31. * @category Mage
  32. * @package Mage_SalesRule
  33. * @author Magento Core Team <core@magentocommerce.com>
  34. */
  35. class Mage_SalesRule_Model_Validator extends Mage_Core_Model_Abstract
  36. {
  37. /**
  38. * Rule source collection
  39. *
  40. * @var Mage_SalesRule_Model_Mysql4_Rule_Collection
  41. */
  42. protected $_rules;
  43. protected $_roundingDeltas = array();
  44. protected $_baseRoundingDeltas = array();
  45. /**
  46. * Defines if method Mage_SalesRule_Model_Validator::process() was already called
  47. * Used for clearing applied rule ids in Quote and in Address
  48. *
  49. * @deprecated since 1.4.2.0
  50. * @var bool
  51. */
  52. protected $_isFirstTimeProcessRun = false;
  53. /**
  54. * Defines if method Mage_SalesRule_Model_Validator::reset() wasn't called
  55. * Used for clearing applied rule ids in Quote and in Address
  56. *
  57. * @var bool
  58. */
  59. protected $_isFirstTimeResetRun = true;
  60. /**
  61. * Information about item totals for rules.
  62. * @var array
  63. */
  64. protected $_rulesItemTotals = array();
  65. /**
  66. * Store information about addresses which cart fixed rule applied for
  67. *
  68. * @var array
  69. */
  70. protected $_cartFixedRuleUsedForAddress = array();
  71. /**
  72. * Init validator
  73. * Init process load collection of rules for specific website,
  74. * customer group and coupon code
  75. *
  76. * @param int $websiteId
  77. * @param int $customerGroupId
  78. * @param string $couponCode
  79. * @return Mage_SalesRule_Model_Validator
  80. */
  81. public function init($websiteId, $customerGroupId, $couponCode)
  82. {
  83. $this->setWebsiteId($websiteId)
  84. ->setCustomerGroupId($customerGroupId)
  85. ->setCouponCode($couponCode);
  86. $key = $websiteId . '_' . $customerGroupId . '_' . $couponCode;
  87. if (!isset($this->_rules[$key])) {
  88. $this->_rules[$key] = Mage::getResourceModel('salesrule/rule_collection')
  89. ->setValidationFilter($websiteId, $customerGroupId, $couponCode)
  90. ->load();
  91. }
  92. return $this;
  93. }
  94. /**
  95. * Get rules collection for current object state
  96. *
  97. * @return Mage_SalesRule_Model_Mysql4_Rule_Collection
  98. */
  99. protected function _getRules()
  100. {
  101. $key = $this->getWebsiteId() . '_' . $this->getCustomerGroupId() . '_' . $this->getCouponCode();
  102. return $this->_rules[$key];
  103. }
  104. /**
  105. * Get address object which can be used for discount calculation
  106. *
  107. * @param Mage_Sales_Model_Quote_Item_Abstract $item
  108. * @return Mage_Sales_Model_Quote_Address
  109. */
  110. protected function _getAddress(Mage_Sales_Model_Quote_Item_Abstract $item)
  111. {
  112. if ($item instanceof Mage_Sales_Model_Quote_Address_Item) {
  113. $address = $item->getAddress();
  114. } elseif ($item->getQuote()->isVirtual()) {
  115. $address = $item->getQuote()->getBillingAddress();
  116. } else {
  117. $address = $item->getQuote()->getShippingAddress();
  118. }
  119. return $address;
  120. }
  121. /**
  122. * Check if rule can be applied for specific address/quote/customer
  123. *
  124. * @param Mage_SalesRule_Model_Rule $rule
  125. * @param Mage_Sales_Model_Quote_Address $address
  126. * @return bool
  127. */
  128. protected function _canProcessRule($rule, $address)
  129. {
  130. if ($rule->hasIsValidForAddress($address) && !$address->isObjectNew()) {
  131. return $rule->getIsValidForAddress($address);
  132. }
  133. /**
  134. * check per coupon usage limit
  135. */
  136. if ($rule->getCouponType() != Mage_SalesRule_Model_Rule::COUPON_TYPE_NO_COUPON) {
  137. $couponCode = $address->getQuote()->getCouponCode();
  138. if ($couponCode) {
  139. $coupon = Mage::getModel('salesrule/coupon');
  140. $coupon->load($couponCode, 'code');
  141. if ($coupon->getId()) {
  142. // check entire usage limit
  143. if ($coupon->getUsageLimit() && $coupon->getTimesUsed() >= $coupon->getUsageLimit()) {
  144. $rule->setIsValidForAddress($address, false);
  145. return false;
  146. }
  147. // check per customer usage limit
  148. $customerId = $address->getQuote()->getCustomerId();
  149. if ($customerId && $coupon->getUsagePerCustomer()) {
  150. $couponUsage = new Varien_Object();
  151. Mage::getResourceModel('salesrule/coupon_usage')->loadByCustomerCoupon(
  152. $couponUsage, $customerId, $coupon->getId());
  153. if ($couponUsage->getCouponId() &&
  154. $couponUsage->getTimesUsed() >= $coupon->getUsagePerCustomer()
  155. ) {
  156. $rule->setIsValidForAddress($address, false);
  157. return false;
  158. }
  159. }
  160. }
  161. }
  162. }
  163. /**
  164. * check per rule usage limit
  165. */
  166. $ruleId = $rule->getId();
  167. if ($ruleId && $rule->getUsesPerCustomer()) {
  168. $customerId = $address->getQuote()->getCustomerId();
  169. $ruleCustomer = Mage::getModel('salesrule/rule_customer');
  170. $ruleCustomer->loadByCustomerRule($customerId, $ruleId);
  171. if ($ruleCustomer->getId()) {
  172. if ($ruleCustomer->getTimesUsed() >= $rule->getUsesPerCustomer()) {
  173. $rule->setIsValidForAddress($address, false);
  174. return false;
  175. }
  176. }
  177. }
  178. $rule->afterLoad();
  179. /**
  180. * quote does not meet rule's conditions
  181. */
  182. if (!$rule->validate($address)) {
  183. $rule->setIsValidForAddress($address, false);
  184. return false;
  185. }
  186. /**
  187. * passed all validations, remember to be valid
  188. */
  189. $rule->setIsValidForAddress($address, true);
  190. return true;
  191. }
  192. /**
  193. * Quote item free shipping ability check
  194. * This process not affect information about applied rules, coupon code etc.
  195. * This information will be added during discount amounts processing
  196. *
  197. * @param Mage_Sales_Model_Quote_Item_Abstract $item
  198. * @return Mage_SalesRule_Model_Validator
  199. */
  200. public function processFreeShipping(Mage_Sales_Model_Quote_Item_Abstract $item)
  201. {
  202. $address = $this->_getAddress($item);
  203. $item->setFreeShipping(false);
  204. foreach ($this->_getRules() as $rule) {
  205. /* @var $rule Mage_SalesRule_Model_Rule */
  206. if (!$this->_canProcessRule($rule, $address)) {
  207. continue;
  208. }
  209. if (!$rule->getActions()->validate($item)) {
  210. continue;
  211. }
  212. switch ($rule->getSimpleFreeShipping()) {
  213. case Mage_SalesRule_Model_Rule::FREE_SHIPPING_ITEM:
  214. $item->setFreeShipping($rule->getDiscountQty() ? $rule->getDiscountQty() : true);
  215. break;
  216. case Mage_SalesRule_Model_Rule::FREE_SHIPPING_ADDRESS:
  217. $address->setFreeShipping(true);
  218. break;
  219. }
  220. if ($rule->getStopRulesProcessing()) {
  221. break;
  222. }
  223. }
  224. return $this;
  225. }
  226. /**
  227. * Reset quote and address applied rules
  228. *
  229. * @param Mage_Sales_Model_Quote_Address $address
  230. * @return Mage_SalesRule_Model_Validator
  231. */
  232. public function reset(Mage_Sales_Model_Quote_Address $address)
  233. {
  234. if ($this->_isFirstTimeResetRun) {
  235. $address->setAppliedRuleIds('');
  236. $address->getQuote()->setAppliedRuleIds('');
  237. $this->_isFirstTimeResetRun = false;
  238. }
  239. return $this;
  240. }
  241. /**
  242. * Quote item discount calculation process
  243. *
  244. * @param Mage_Sales_Model_Quote_Item_Abstract $item
  245. * @return Mage_SalesRule_Model_Validator
  246. */
  247. public function process(Mage_Sales_Model_Quote_Item_Abstract $item)
  248. {
  249. $item->setDiscountAmount(0);
  250. $item->setBaseDiscountAmount(0);
  251. $item->setDiscountPercent(0);
  252. $quote = $item->getQuote();
  253. $address = $this->_getAddress($item);
  254. $itemPrice = $this->_getItemPrice($item);
  255. $baseItemPrice = $this->_getItemBasePrice($item);
  256. $itemOriginalPrice = $item->getOriginalPrice();
  257. $baseItemOriginalPrice = $item->getBaseOriginalPrice();
  258. if ($itemPrice <= 0) {
  259. return $this;
  260. }
  261. $appliedRuleIds = array();
  262. foreach ($this->_getRules() as $rule) {
  263. /* @var $rule Mage_SalesRule_Model_Rule */
  264. if (!$this->_canProcessRule($rule, $address)) {
  265. continue;
  266. }
  267. if (!$rule->getActions()->validate($item)) {
  268. continue;
  269. }
  270. $qty = $this->_getItemQty($item, $rule);
  271. $rulePercent = min(100, $rule->getDiscountAmount());
  272. $discountAmount = 0;
  273. $baseDiscountAmount = 0;
  274. //discount for original price
  275. $originalDiscountAmount = 0;
  276. $baseOriginalDiscountAmount = 0;
  277. switch ($rule->getSimpleAction()) {
  278. case Mage_SalesRule_Model_Rule::TO_PERCENT_ACTION:
  279. $rulePercent = max(0, 100-$rule->getDiscountAmount());
  280. //no break;
  281. case Mage_SalesRule_Model_Rule::BY_PERCENT_ACTION:
  282. $step = $rule->getDiscountStep();
  283. if ($step) {
  284. $qty = floor($qty/$step)*$step;
  285. }
  286. $_rulePct = $rulePercent/100;
  287. $discountAmount = ($qty*$itemPrice - $item->getDiscountAmount()) * $_rulePct;
  288. $baseDiscountAmount= ($qty*$baseItemPrice - $item->getBaseDiscountAmount()) * $_rulePct;
  289. //get discount for original price
  290. $originalDiscountAmount = ($qty*$itemOriginalPrice - $item->getDiscountAmount()) * $_rulePct;
  291. $baseOriginalDiscountAmount= ($qty*$baseItemOriginalPrice - $item->getDiscountAmount()) * $_rulePct;
  292. if (!$rule->getDiscountQty() || $rule->getDiscountQty()>$qty) {
  293. $discountPercent = min(100, $item->getDiscountPercent()+$rulePercent);
  294. $item->setDiscountPercent($discountPercent);
  295. }
  296. break;
  297. case Mage_SalesRule_Model_Rule::TO_FIXED_ACTION:
  298. $quoteAmount = $quote->getStore()->convertPrice($rule->getDiscountAmount());
  299. $discountAmount = $qty*($itemPrice-$quoteAmount);
  300. $baseDiscountAmount= $qty*($baseItemPrice-$rule->getDiscountAmount());
  301. //get discount for original price
  302. $originalDiscountAmount = $qty*($itemOriginalPrice-$quoteAmount);
  303. $baseOriginalDiscountAmount= $qty*($baseItemOriginalPrice-$rule->getDiscountAmount());
  304. break;
  305. case Mage_SalesRule_Model_Rule::BY_FIXED_ACTION:
  306. $step = $rule->getDiscountStep();
  307. if ($step) {
  308. $qty = floor($qty/$step)*$step;
  309. }
  310. $quoteAmount = $quote->getStore()->convertPrice($rule->getDiscountAmount());
  311. $discountAmount = $qty*$quoteAmount;
  312. $baseDiscountAmount = $qty*$rule->getDiscountAmount();
  313. break;
  314. case Mage_SalesRule_Model_Rule::CART_FIXED_ACTION:
  315. if (empty($this->_rulesItemTotals[$rule->getId()])) {
  316. Mage::throwException(Mage::helper('salesrule')->__('Item totals are not set for rule.'));
  317. }
  318. /**
  319. * prevent applying whole cart discount for every shipping order, but only for first order
  320. */
  321. if ($quote->getIsMultiShipping()) {
  322. $usedForAddressId = $this->getCartFixedRuleUsedForAddress($rule->getId());
  323. if ($usedForAddressId && $usedForAddressId != $address->getId()) {
  324. break;
  325. } else {
  326. $this->setCartFixedRuleUsedForAddress($rule->getId(), $address->getId());
  327. }
  328. }
  329. $cartRules = $address->getCartFixedRules();
  330. if (!isset($cartRules[$rule->getId()])) {
  331. $cartRules[$rule->getId()] = $rule->getDiscountAmount();
  332. }
  333. if ($cartRules[$rule->getId()] > 0) {
  334. if ($this->_rulesItemTotals[$rule->getId()]['items_count'] <= 1) {
  335. $quoteAmount = $quote->getStore()->convertPrice($cartRules[$rule->getId()]);
  336. $baseDiscountAmount = min($baseItemPrice * $qty, $cartRules[$rule->getId()]);
  337. } else {
  338. $discountRate = $baseItemPrice * $qty /
  339. $this->_rulesItemTotals[$rule->getId()]['base_items_price'];
  340. $maximumItemDiscount = $rule->getDiscountAmount() * $discountRate;
  341. $quoteAmount = $quote->getStore()->convertPrice($maximumItemDiscount);
  342. $baseDiscountAmount = min($baseItemPrice * $qty, $maximumItemDiscount);
  343. $this->_rulesItemTotals[$rule->getId()]['items_count']--;
  344. }
  345. $discountAmount = min($itemPrice * $qty, $quoteAmount);
  346. $discountAmount = $quote->getStore()->roundPrice($discountAmount);
  347. $baseDiscountAmount = $quote->getStore()->roundPrice($baseDiscountAmount);
  348. //get discount for original price
  349. $originalDiscountAmount = min($itemOriginalPrice * $qty, $quoteAmount);
  350. $baseOriginalDiscountAmount = $quote->getStore()->roundPrice($originalDiscountAmount);
  351. $baseOriginalDiscountAmount = $quote->getStore()->roundPrice($baseItemOriginalPrice);
  352. $cartRules[$rule->getId()] -= $baseDiscountAmount;
  353. }
  354. $address->setCartFixedRules($cartRules);
  355. break;
  356. case Mage_SalesRule_Model_Rule::BUY_X_GET_Y_ACTION:
  357. $x = $rule->getDiscountStep();
  358. $y = $rule->getDiscountAmount();
  359. if (!$x || $y>=$x) {
  360. break;
  361. }
  362. $buyAndDiscountQty = $x + $y;
  363. $fullRuleQtyPeriod = floor($qty / $buyAndDiscountQty);
  364. $freeQty = $qty - $fullRuleQtyPeriod * $buyAndDiscountQty;
  365. $discountQty = $fullRuleQtyPeriod * $y;
  366. if ($freeQty > $x) {
  367. $discountQty += $freeQty - $x;
  368. }
  369. $discountAmount = $discountQty * $itemPrice;
  370. $baseDiscountAmount= $discountQty * $baseItemPrice;
  371. //get discount for original price
  372. $originalDiscountAmount = $discountQty * $itemOriginalPrice;
  373. $baseOriginalDiscountAmount= $discountQty * $baseItemOriginalPrice;
  374. break;
  375. }
  376. $result = new Varien_Object(array(
  377. 'discount_amount' => $discountAmount,
  378. 'base_discount_amount' => $baseDiscountAmount,
  379. ));
  380. Mage::dispatchEvent('salesrule_validator_process', array(
  381. 'rule' => $rule,
  382. 'item' => $item,
  383. 'address' => $address,
  384. 'quote' => $quote,
  385. 'qty' => $qty,
  386. 'result' => $result,
  387. ));
  388. $discountAmount = $result->getDiscountAmount();
  389. $baseDiscountAmount = $result->getBaseDiscountAmount();
  390. $percentKey = $item->getDiscountPercent();
  391. /**
  392. * Process "delta" rounding
  393. */
  394. if ($percentKey) {
  395. $delta = isset($this->_roundingDeltas[$percentKey]) ? $this->_roundingDeltas[$percentKey] : 0;
  396. $baseDelta = isset($this->_baseRoundingDeltas[$percentKey])
  397. ? $this->_baseRoundingDeltas[$percentKey]
  398. : 0;
  399. $discountAmount+= $delta;
  400. $baseDiscountAmount+=$baseDelta;
  401. $this->_roundingDeltas[$percentKey] = $discountAmount -
  402. $quote->getStore()->roundPrice($discountAmount);
  403. $this->_baseRoundingDeltas[$percentKey] = $baseDiscountAmount -
  404. $quote->getStore()->roundPrice($baseDiscountAmount);
  405. $discountAmount = $quote->getStore()->roundPrice($discountAmount);
  406. $baseDiscountAmount = $quote->getStore()->roundPrice($baseDiscountAmount);
  407. } else {
  408. $discountAmount = $quote->getStore()->roundPrice($discountAmount);
  409. $baseDiscountAmount = $quote->getStore()->roundPrice($baseDiscountAmount);
  410. }
  411. /**
  412. * We can't use row total here because row total not include tax
  413. * Discount can be applied on price included tax
  414. */
  415. $itemDiscountAmount = $item->getDiscountAmount();
  416. $itemBaseDiscountAmount = $item->getBaseDiscountAmount();
  417. $discountAmount = min($itemDiscountAmount + $discountAmount, $itemPrice * $qty);
  418. $baseDiscountAmount = min($itemBaseDiscountAmount + $baseDiscountAmount, $baseItemPrice * $qty);
  419. $item->setDiscountAmount($discountAmount);
  420. $item->setBaseDiscountAmount($baseDiscountAmount);
  421. $item->setOriginalDiscountAmount($originalDiscountAmount);
  422. $item->setBaseOriginalDiscountAmount($baseOriginalDiscountAmount);
  423. $appliedRuleIds[$rule->getRuleId()] = $rule->getRuleId();
  424. $this->_maintainAddressCouponCode($address, $rule);
  425. $this->_addDiscountDescription($address, $rule);
  426. if ($rule->getStopRulesProcessing()) {
  427. break;
  428. }
  429. }
  430. $item->setAppliedRuleIds(join(',',$appliedRuleIds));
  431. $address->setAppliedRuleIds($this->mergeIds($address->getAppliedRuleIds(), $appliedRuleIds));
  432. $quote->setAppliedRuleIds($this->mergeIds($quote->getAppliedRuleIds(), $appliedRuleIds));
  433. return $this;
  434. }
  435. /**
  436. * Apply discounts to shipping amount
  437. *
  438. * @param Mage_Sales_Model_Quote_Address $address
  439. * @return Mage_SalesRule_Model_Validator
  440. */
  441. public function processShippingAmount(Mage_Sales_Model_Quote_Address $address)
  442. {
  443. $shippingAmount = $address->getShippingAmountForDiscount();
  444. if ($shippingAmount!==null) {
  445. $baseShippingAmount = $address->getBaseShippingAmountForDiscount();
  446. } else {
  447. $shippingAmount = $address->getShippingAmount();
  448. $baseShippingAmount = $address->getBaseShippingAmount();
  449. }
  450. $quote = $address->getQuote();
  451. $appliedRuleIds = array();
  452. foreach ($this->_getRules() as $rule) {
  453. /* @var $rule Mage_SalesRule_Model_Rule */
  454. if (!$rule->getApplyToShipping() || !$this->_canProcessRule($rule, $address)) {
  455. continue;
  456. }
  457. $discountAmount = 0;
  458. $baseDiscountAmount = 0;
  459. $rulePercent = min(100, $rule->getDiscountAmount());
  460. switch ($rule->getSimpleAction()) {
  461. case Mage_SalesRule_Model_Rule::TO_PERCENT_ACTION:
  462. $rulePercent = max(0, 100-$rule->getDiscountAmount());
  463. case Mage_SalesRule_Model_Rule::BY_PERCENT_ACTION:
  464. $discountAmount = ($shippingAmount - $address->getShippingDiscountAmount()) * $rulePercent/100;
  465. $baseDiscountAmount= ($baseShippingAmount -
  466. $address->getBaseShippingDiscountAmount()) * $rulePercent/100;
  467. $discountPercent = min(100, $address->getShippingDiscountPercent()+$rulePercent);
  468. $address->setShippingDiscountPercent($discountPercent);
  469. break;
  470. case Mage_SalesRule_Model_Rule::TO_FIXED_ACTION:
  471. $quoteAmount = $quote->getStore()->convertPrice($rule->getDiscountAmount());
  472. $discountAmount = $shippingAmount-$quoteAmount;
  473. $baseDiscountAmount= $baseShippingAmount-$rule->getDiscountAmount();
  474. break;
  475. case Mage_SalesRule_Model_Rule::BY_FIXED_ACTION:
  476. $quoteAmount = $quote->getStore()->convertPrice($rule->getDiscountAmount());
  477. $discountAmount = $quoteAmount;
  478. $baseDiscountAmount = $rule->getDiscountAmount();
  479. break;
  480. case Mage_SalesRule_Model_Rule::CART_FIXED_ACTION:
  481. $cartRules = $address->getCartFixedRules();
  482. if (!isset($cartRules[$rule->getId()])) {
  483. $cartRules[$rule->getId()] = $rule->getDiscountAmount();
  484. }
  485. if ($cartRules[$rule->getId()] > 0) {
  486. $quoteAmount = $quote->getStore()->convertPrice($cartRules[$rule->getId()]);
  487. $discountAmount = min(
  488. $shippingAmount-$address->getShippingDiscountAmount(),
  489. $quoteAmount
  490. );
  491. $baseDiscountAmount = min(
  492. $baseShippingAmount-$address->getBaseShippingDiscountAmount(),
  493. $cartRules[$rule->getId()]
  494. );
  495. $cartRules[$rule->getId()] -= $baseDiscountAmount;
  496. }
  497. $address->setCartFixedRules($cartRules);
  498. break;
  499. }
  500. $discountAmount = min($address->getShippingDiscountAmount()+$discountAmount, $shippingAmount);
  501. $baseDiscountAmount = min(
  502. $address->getBaseShippingDiscountAmount()+$baseDiscountAmount,
  503. $baseShippingAmount
  504. );
  505. $address->setShippingDiscountAmount($discountAmount);
  506. $address->setBaseShippingDiscountAmount($baseDiscountAmount);
  507. $appliedRuleIds[$rule->getRuleId()] = $rule->getRuleId();
  508. $this->_maintainAddressCouponCode($address, $rule);
  509. $this->_addDiscountDescription($address, $rule);
  510. if ($rule->getStopRulesProcessing()) {
  511. break;
  512. }
  513. }
  514. $address->setAppliedRuleIds($this->mergeIds($address->getAppliedRuleIds(), $appliedRuleIds));
  515. $quote->setAppliedRuleIds($this->mergeIds($quote->getAppliedRuleIds(), $appliedRuleIds));
  516. return $this;
  517. }
  518. /**
  519. * Merge two sets of ids
  520. *
  521. * @param array|string $a1
  522. * @param array|string $a2
  523. * @param bool $asString
  524. * @return array
  525. */
  526. public function mergeIds($a1, $a2, $asString = true)
  527. {
  528. if (!is_array($a1)) {
  529. $a1 = empty($a1) ? array() : explode(',', $a1);
  530. }
  531. if (!is_array($a2)) {
  532. $a2 = empty($a2) ? array() : explode(',', $a2);
  533. }
  534. $a = array_unique(array_merge($a1, $a2));
  535. if ($asString) {
  536. $a = implode(',', $a);
  537. }
  538. return $a;
  539. }
  540. /**
  541. * Set information about usage cart fixed rule by quote address
  542. *
  543. * @param int $ruleId
  544. * @param int $itemId
  545. * @return void
  546. */
  547. public function setCartFixedRuleUsedForAddress($ruleId, $itemId)
  548. {
  549. $this->_cartFixedRuleUsedForAddress[$ruleId] = $itemId;
  550. }
  551. /**
  552. * Retrieve information about usage cart fixed rule by quote address
  553. *
  554. * @param int $ruleId
  555. * @return int|null
  556. */
  557. public function getCartFixedRuleUsedForAddress($ruleId)
  558. {
  559. if (isset($this->_cartFixedRuleUsedForAddress[$ruleId])) {
  560. return $this->_cartFixedRuleUsedForAddress[$ruleId];
  561. }
  562. return null;
  563. }
  564. /**
  565. * Calculate quote totals for each rule and save results
  566. *
  567. * @param mixed $items
  568. * @param Mage_Sales_Model_Quote_Address $address
  569. * @return Mage_SalesRule_Model_Validator
  570. */
  571. public function initTotals($items, Mage_Sales_Model_Quote_Address $address)
  572. {
  573. $address->setCartFixedRules(array());
  574. if (!$items) {
  575. return $this;
  576. }
  577. foreach ($this->_getRules() as $rule) {
  578. if (Mage_SalesRule_Model_Rule::CART_FIXED_ACTION == $rule->getSimpleAction()
  579. && $this->_canProcessRule($rule, $address)) {
  580. $ruleTotalItemsPrice = 0;
  581. $ruleTotalBaseItemsPrice = 0;
  582. $validItemsCount = 0;
  583. foreach ($items as $item) {
  584. //Skipping child items to avoid double calculations
  585. if ($item->getParentItemId()) {
  586. continue;
  587. }
  588. if (!$rule->getActions()->validate($item)) {
  589. continue;
  590. }
  591. $qty = $this->_getItemQty($item, $rule);
  592. $ruleTotalItemsPrice += $this->_getItemPrice($item) * $qty;
  593. $ruleTotalBaseItemsPrice += $this->_getItemBasePrice($item) * $qty;
  594. $validItemsCount++;
  595. }
  596. $this->_rulesItemTotals[$rule->getId()] = array(
  597. 'items_price' => $ruleTotalItemsPrice,
  598. 'base_items_price' => $ruleTotalBaseItemsPrice,
  599. 'items_count' => $validItemsCount,
  600. );
  601. }
  602. }
  603. return $this;
  604. }
  605. /**
  606. * Retrieve subordinate coupon IDs
  607. *
  608. * @return array
  609. */
  610. protected function _maintainAddressCouponCode($address, $rule)
  611. {
  612. foreach ($rule->getCoupons() as $coupon) {
  613. if (strtolower($coupon->getCode()) == strtolower($this->getCouponCode())) {
  614. $address->setCouponCode($this->getCouponCode());
  615. break;
  616. }
  617. }
  618. }
  619. /**
  620. * Add rule discount description label to address object
  621. *
  622. * @param Mage_Sales_Model_Quote_Address $address
  623. * @param Mage_SalesRule_Model_Rule $rule
  624. * @return Mage_SalesRule_Model_Validator
  625. */
  626. protected function _addDiscountDescription($address, $rule)
  627. {
  628. $description = $address->getDiscountDescriptionArray();
  629. $ruleLabel = $rule->getStoreLabel($address->getQuote()->getStore());
  630. $label = '';
  631. if ($ruleLabel) {
  632. $label = $ruleLabel;
  633. } else if ($address->getCouponCode()) {
  634. $label = $address->getCouponCode();
  635. }
  636. if (!empty($label)) {
  637. $description[$rule->getId()] = $label;
  638. }
  639. $address->setDiscountDescriptionArray($description);
  640. return $this;
  641. }
  642. /**
  643. * Return item price
  644. *
  645. * @param Mage_Sales_Model_Quote_Item_Abstract $item
  646. * @return float
  647. */
  648. protected function _getItemPrice($item)
  649. {
  650. $price = $item->getDiscountCalculationPrice();
  651. $calcPrice = $item->getCalculationPrice();
  652. return ($price !== null) ? $price : $calcPrice;
  653. }
  654. /**
  655. * Return item original price
  656. *
  657. * @param Mage_Sales_Model_Quote_Item_Abstract $item
  658. * @return float
  659. */
  660. protected function _getItemOriginalPrice($item)
  661. {
  662. $price = $item->getDiscountCalculationPrice();
  663. return ($price !== null) ? $price : $item->getCalculationPrice();
  664. }
  665. /**
  666. * Return item base price
  667. *
  668. * @param Mage_Sales_Model_Quote_Item_Abstract $item
  669. * @return float
  670. */
  671. protected function _getItemBasePrice($item)
  672. {
  673. $price = $item->getDiscountCalculationPrice();
  674. return ($price !== null) ? $item->getBaseDiscountCalculationPrice() : $item->getBaseCalculationPrice();
  675. }
  676. /**
  677. * Return discount item qty
  678. *
  679. * @param Mage_Sales_Model_Quote_Item_Abstract $item
  680. * @param Mage_SalesRule_Model_Rule $rule
  681. * @return int
  682. */
  683. protected function _getItemQty($item, $rule)
  684. {
  685. $qty = $item->getTotalQty();
  686. return $rule->getDiscountQty() ? min($qty, $rule->getDiscountQty()) : $qty;
  687. }
  688. /**
  689. * Convert address discount description array to string
  690. *
  691. * @param Mage_Sales_Model_Quote_Address $address
  692. * @param string $separator
  693. * @return Mage_SalesRule_Model_Validator
  694. */
  695. public function prepareDescription($address, $separator=', ')
  696. {
  697. $description = $address->getDiscountDescriptionArray();
  698. if (is_array($description) && !empty($description)) {
  699. $description = array_unique($description);
  700. $description = implode($separator, $description);
  701. } else {
  702. $description = '';
  703. }
  704. $address->setDiscountDescription($description);
  705. return $this;
  706. }
  707. }