1: <?php
2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25:
26:
27:
28: 29: 30: 31: 32: 33: 34:
35: class Mage_CatalogRule_Model_Resource_Rule extends Mage_Rule_Model_Resource_Abstract
36: {
37: 38: 39:
40: const SECONDS_IN_DAY = 86400;
41:
42: 43: 44: 45: 46:
47: protected $_associatedEntitiesMap = array(
48: 'website' => array(
49: 'associations_table' => 'catalogrule/website',
50: 'rule_id_field' => 'rule_id',
51: 'entity_id_field' => 'website_id'
52: ),
53: 'customer_group' => array(
54: 'associations_table' => 'catalogrule/customer_group',
55: 'rule_id_field' => 'rule_id',
56: 'entity_id_field' => 'customer_group_id'
57: )
58: );
59:
60: 61: 62:
63: protected function _construct()
64: {
65: $this->_init('catalogrule/rule', 'rule_id');
66: }
67:
68: 69: 70: 71: 72: 73: 74:
75: protected function _afterLoad(Mage_Core_Model_Abstract $object)
76: {
77: $object->setData('customer_group_ids', (array)$this->getCustomerGroupIds($object->getId()));
78: $object->setData('website_ids', (array)$this->getWebsiteIds($object->getId()));
79:
80: return parent::_afterLoad($object);
81: }
82:
83: 84: 85: 86: 87: 88: 89: 90:
91: protected function _afterSave(Mage_Core_Model_Abstract $object)
92: {
93: if ($object->hasWebsiteIds()) {
94: $websiteIds = $object->getWebsiteIds();
95: if (!is_array($websiteIds)) {
96: $websiteIds = explode(',', (string)$websiteIds);
97: }
98: $this->bindRuleToEntity($object->getId(), $websiteIds, 'website');
99: }
100:
101: if ($object->hasCustomerGroupIds()) {
102: $customerGroupIds = $object->getCustomerGroupIds();
103: if (!is_array($customerGroupIds)) {
104: $customerGroupIds = explode(',', (string)$customerGroupIds);
105: }
106: $this->bindRuleToEntity($object->getId(), $customerGroupIds, 'customer_group');
107: }
108:
109: parent::_afterSave($object);
110: return $this;
111: }
112:
113: 114: 115: 116: 117: 118: 119:
120: public function updateRuleProductData(Mage_CatalogRule_Model_Rule $rule)
121: {
122: $ruleId = $rule->getId();
123: $write = $this->_getWriteAdapter();
124: $write->beginTransaction();
125: if ($rule->getProductsFilter()) {
126: $write->delete(
127: $this->getTable('catalogrule/rule_product'),
128: array(
129: 'rule_id=?' => $ruleId,
130: 'product_id IN (?)' => $rule->getProductsFilter()
131: )
132: );
133: } else {
134: $write->delete($this->getTable('catalogrule/rule_product'), $write->quoteInto('rule_id=?', $ruleId));
135: }
136:
137: if (!$rule->getIsActive()) {
138: $write->commit();
139: return $this;
140: }
141:
142: $websiteIds = $rule->getWebsiteIds();
143: if (!is_array($websiteIds)) {
144: $websiteIds = explode(',', $websiteIds);
145: }
146: if (empty($websiteIds)) {
147: return $this;
148: }
149:
150: Varien_Profiler::start('__MATCH_PRODUCTS__');
151: $productIds = $rule->getMatchingProductIds();
152: Varien_Profiler::stop('__MATCH_PRODUCTS__');
153:
154: $customerGroupIds = $rule->getCustomerGroupIds();
155: $fromTime = strtotime($rule->getFromDate());
156: $toTime = strtotime($rule->getToDate());
157: $toTime = $toTime ? ($toTime + self::SECONDS_IN_DAY - 1) : 0;
158: $sortOrder = (int)$rule->getSortOrder();
159: $actionOperator = $rule->getSimpleAction();
160: $actionAmount = $rule->getDiscountAmount();
161: $subActionOperator = $rule->getSubIsEnable() ? $rule->getSubSimpleAction() : '';
162: $subActionAmount = $rule->getSubDiscountAmount();
163: $actionStop = $rule->getStopRulesProcessing();
164:
165: $rows = array();
166:
167: try {
168: foreach ($productIds as $productId) {
169: foreach ($websiteIds as $websiteId) {
170: foreach ($customerGroupIds as $customerGroupId) {
171: $rows[] = array(
172: 'rule_id' => $ruleId,
173: 'from_time' => $fromTime,
174: 'to_time' => $toTime,
175: 'website_id' => $websiteId,
176: 'customer_group_id' => $customerGroupId,
177: 'product_id' => $productId,
178: 'action_operator' => $actionOperator,
179: 'action_amount' => $actionAmount,
180: 'action_stop' => $actionStop,
181: 'sort_order' => $sortOrder,
182: 'sub_simple_action' => $subActionOperator,
183: 'sub_discount_amount' => $subActionAmount,
184: );
185:
186: if (count($rows) == 1000) {
187: $write->insertMultiple($this->getTable('catalogrule/rule_product'), $rows);
188: $rows = array();
189: }
190: }
191: }
192: }
193: if (!empty($rows)) {
194: $write->insertMultiple($this->getTable('catalogrule/rule_product'), $rows);
195: }
196:
197: $write->commit();
198: } catch (Exception $e) {
199: $write->rollback();
200: throw $e;
201: }
202:
203:
204: return $this;
205: }
206:
207: 208: 209: 210: 211: 212: 213:
214: public function getRuleProductIds($ruleId)
215: {
216: $read = $this->_getReadAdapter();
217: $select = $read->select()->from($this->getTable('catalogrule/rule_product'), 'product_id')
218: ->where('rule_id=?', $ruleId);
219:
220: return $read->fetchCol($select);
221: }
222:
223: 224: 225: 226: 227: 228: 229: 230: 231:
232: public function removeCatalogPricesForDateRange($fromDate, $toDate, $productId = null)
233: {
234: $write = $this->_getWriteAdapter();
235: $conds = array();
236: $cond = $write->quoteInto('rule_date between ?', $this->formatDate($fromDate));
237: $cond = $write->quoteInto($cond.' and ?', $this->formatDate($toDate));
238: $conds[] = $cond;
239: if (!is_null($productId)) {
240: $conds[] = $write->quoteInto('product_id=?', $productId);
241: }
242:
243: 244: 245: 246:
247: $select = $this->_getWriteAdapter()->select()
248: ->from($this->getTable('catalogrule/rule_product_price'), 'product_id')
249: ->where(implode(' AND ', $conds))
250: ->group('product_id');
251:
252: $replace = $write->insertFromSelect(
253: $select,
254: $this->getTable('catalogrule/affected_product'),
255: array('product_id'),
256: true
257: );
258: $write->query($replace);
259: $write->delete($this->getTable('catalogrule/rule_product_price'), $conds);
260: return $this;
261: }
262:
263: 264: 265: 266: 267: 268: 269: 270:
271: public function deleteOldData($date, $productId = null)
272: {
273: $write = $this->_getWriteAdapter();
274: $conds = array();
275: $conds[] = $write->quoteInto('rule_date<?', $this->formatDate($date));
276: if (!is_null($productId)) {
277: $conds[] = $write->quoteInto('product_id=?', $productId);
278: }
279: $write->delete($this->getTable('catalogrule/rule_product_price'), $conds);
280: return $this;
281: }
282:
283: 284: 285: 286: 287: 288: 289: 290: 291: 292:
293: protected function _getRuleProductsStmt($fromDate, $toDate, $productId = null, $websiteId = null)
294: {
295: $read = $this->_getReadAdapter();
296: 297: 298: 299: 300: 301: 302: 303: 304: 305:
306: $select = $read->select()
307: ->from(array('rp' => $this->getTable('catalogrule/rule_product')))
308: ->where($read->quoteInto('rp.from_time = 0 or rp.from_time <= ?', $toDate)
309: . ' OR ' . $read->quoteInto('rp.to_time = 0 or rp.to_time >= ?', $fromDate))
310: ->order(array('rp.website_id', 'rp.customer_group_id', 'rp.product_id', 'rp.sort_order', 'rp.rule_id'));
311:
312: if (!is_null($productId)) {
313: $select->where('rp.product_id=?', $productId);
314: }
315:
316: 317: 318:
319: $priceAttr = Mage::getSingleton('eav/config')->getAttribute(Mage_Catalog_Model_Product::ENTITY, 'price');
320: $priceTable = $priceAttr->getBackend()->getTable();
321: $attributeId= $priceAttr->getId();
322:
323: $joinCondition = '%1$s.entity_id=rp.product_id AND (%1$s.attribute_id=' . $attributeId
324: . ') and %1$s.store_id=%2$s';
325:
326: $select->join(
327: array('pp_default'=>$priceTable),
328: sprintf($joinCondition, 'pp_default', Mage_Core_Model_App::ADMIN_STORE_ID),
329: array('default_price'=>'pp_default.value')
330: );
331:
332: if ($websiteId !== null) {
333: $website = Mage::app()->getWebsite($websiteId);
334: $defaultGroup = $website->getDefaultGroup();
335: if ($defaultGroup instanceof Mage_Core_Model_Store_Group) {
336: $storeId = $defaultGroup->getDefaultStoreId();
337: } else {
338: $storeId = Mage_Core_Model_App::ADMIN_STORE_ID;
339: }
340:
341: $select->joinInner(
342: array('product_website' => $this->getTable('catalog/product_website')),
343: 'product_website.product_id=rp.product_id ' .
344: 'AND rp.website_id=product_website.website_id ' .
345: 'AND product_website.website_id='.$websiteId,
346: array()
347: );
348:
349: $tableAlias = 'pp'.$websiteId;
350: $fieldAlias = 'website_'.$websiteId.'_price';
351: $select->joinLeft(
352: array($tableAlias=>$priceTable),
353: sprintf($joinCondition, $tableAlias, $storeId),
354: array($fieldAlias=>$tableAlias.'.value')
355: );
356: } else {
357: foreach (Mage::app()->getWebsites() as $website) {
358: $websiteId = $website->getId();
359: $defaultGroup = $website->getDefaultGroup();
360: if ($defaultGroup instanceof Mage_Core_Model_Store_Group) {
361: $storeId = $defaultGroup->getDefaultStoreId();
362: } else {
363: $storeId = Mage_Core_Model_App::ADMIN_STORE_ID;
364: }
365:
366: $tableAlias = 'pp' . $websiteId;
367: $fieldAlias = 'website_' . $websiteId . '_price';
368: $select->joinLeft(
369: array($tableAlias => $priceTable),
370: sprintf($joinCondition, $tableAlias, $storeId),
371: array($fieldAlias => $tableAlias.'.value')
372: );
373: }
374: }
375:
376: return $read->query($select);
377: }
378:
379: 380: 381: 382: 383: 384: 385: 386: 387: 388: 389:
390: public function applyAllRulesForDateRange($fromDate = null, $toDate = null, $productId = null)
391: {
392: $write = $this->_getWriteAdapter();
393: $write->beginTransaction();
394:
395: Mage::dispatchEvent('catalogrule_before_apply', array('resource' => $this));
396:
397: $clearOldData = false;
398: if ($fromDate === null) {
399: $fromDate = mktime(0,0,0,date('m'),date('d')-1);
400: 401: 402: 403: 404:
405: $clearOldData = true;
406: }
407: if (is_string($fromDate)) {
408: $fromDate = strtotime($fromDate);
409: }
410: if ($toDate === null) {
411: $toDate = mktime(0,0,0,date('m'),date('d')+1);
412: }
413: if (is_string($toDate)) {
414: $toDate = strtotime($toDate);
415: }
416:
417: $product = null;
418: if ($productId instanceof Mage_Catalog_Model_Product) {
419: $product = $productId;
420: $productId = $productId->getId();
421: }
422:
423: $this->removeCatalogPricesForDateRange($fromDate, $toDate, $productId);
424: if ($clearOldData) {
425: $this->deleteOldData($fromDate, $productId);
426: }
427:
428: $dayPrices = array();
429:
430: try {
431: 432: 433: 434:
435: foreach (Mage::app()->getWebsites(false) as $website) {
436: $productsStmt = $this->_getRuleProductsStmt(
437: $fromDate,
438: $toDate,
439: $productId,
440: $website->getId()
441: );
442:
443: $dayPrices = array();
444: $stopFlags = array();
445: $prevKey = null;
446:
447: while ($ruleData = $productsStmt->fetch()) {
448: $ruleProductId = $ruleData['product_id'];
449: $productKey = $ruleProductId . '_'
450: . $ruleData['website_id'] . '_'
451: . $ruleData['customer_group_id'];
452:
453: if ($prevKey && ($prevKey != $productKey)) {
454: $stopFlags = array();
455: }
456:
457: 458: 459:
460: for ($time=$fromDate; $time<=$toDate; $time+=self::SECONDS_IN_DAY) {
461: if (($ruleData['from_time']==0 || $time >= $ruleData['from_time'])
462: && ($ruleData['to_time']==0 || $time <=$ruleData['to_time'])
463: ) {
464: $priceKey = $time . '_' . $productKey;
465:
466: if (isset($stopFlags[$priceKey])) {
467: continue;
468: }
469:
470: if (!isset($dayPrices[$priceKey])) {
471: $dayPrices[$priceKey] = array(
472: 'rule_date' => $time,
473: 'website_id' => $ruleData['website_id'],
474: 'customer_group_id' => $ruleData['customer_group_id'],
475: 'product_id' => $ruleProductId,
476: 'rule_price' => $this->_calcRuleProductPrice($ruleData),
477: 'latest_start_date' => $ruleData['from_time'],
478: 'earliest_end_date' => $ruleData['to_time'],
479: );
480: } else {
481: $dayPrices[$priceKey]['rule_price'] = $this->_calcRuleProductPrice(
482: $ruleData,
483: $dayPrices[$priceKey]
484: );
485: $dayPrices[$priceKey]['latest_start_date'] = max(
486: $dayPrices[$priceKey]['latest_start_date'],
487: $ruleData['from_time']
488: );
489: $dayPrices[$priceKey]['earliest_end_date'] = min(
490: $dayPrices[$priceKey]['earliest_end_date'],
491: $ruleData['to_time']
492: );
493: }
494:
495: if ($ruleData['action_stop']) {
496: $stopFlags[$priceKey] = true;
497: }
498: }
499: }
500:
501: $prevKey = $productKey;
502: if (count($dayPrices)>1000) {
503: $this->_saveRuleProductPrices($dayPrices);
504: $dayPrices = array();
505: }
506: }
507: $this->_saveRuleProductPrices($dayPrices);
508: }
509: $this->_saveRuleProductPrices($dayPrices);
510:
511: $write->delete($this->getTable('catalogrule/rule_group_website'), array());
512:
513: $timestamp = Mage::getModel('core/date')->gmtTimestamp();
514:
515: $select = $write->select()
516: ->distinct(true)
517: ->from(
518: $this->getTable('catalogrule/rule_product'),
519: array('rule_id', 'customer_group_id', 'website_id')
520: )->where("{$timestamp} >= from_time AND (({$timestamp} <= to_time AND to_time > 0) OR to_time = 0)");
521: $query = $select->insertFromSelect($this->getTable('catalogrule/rule_group_website'));
522: $write->query($query);
523:
524: $write->commit();
525: } catch (Exception $e) {
526: $write->rollback();
527: throw $e;
528: }
529:
530: $productCondition = Mage::getModel('catalog/product_condition')
531: ->setTable($this->getTable('catalogrule/affected_product'))
532: ->setPkFieldName('product_id');
533: Mage::dispatchEvent('catalogrule_after_apply', array(
534: 'product' => $product,
535: 'product_condition' => $productCondition
536: ));
537: $write->delete($this->getTable('catalogrule/affected_product'));
538:
539: return $this;
540: }
541:
542: 543: 544: 545: 546: 547: 548: 549:
550: protected function _calcRuleProductPrice($ruleData, $productData = null)
551: {
552: if ($productData !== null && isset($productData['rule_price'])) {
553: $productPrice = $productData['rule_price'];
554: } else {
555: $websiteId = $ruleData['website_id'];
556: if (isset($ruleData['website_'.$websiteId.'_price'])) {
557: $productPrice = $ruleData['website_'.$websiteId.'_price'];
558: } else {
559: $productPrice = $ruleData['default_price'];
560: }
561: }
562:
563: $productPrice = Mage::helper('catalogrule')->calcPriceRule(
564: $ruleData['action_operator'],
565: $ruleData['action_amount'],
566: $productPrice);
567:
568: return Mage::app()->getStore()->roundPrice($productPrice);
569: }
570:
571: 572: 573: 574: 575: 576: 577:
578: protected function _saveRuleProductPrices($arrData)
579: {
580: if (empty($arrData)) {
581: return $this;
582: }
583:
584: $adapter = $this->_getWriteAdapter();
585: $productIds = array();
586:
587: $adapter->beginTransaction();
588: try {
589: foreach ($arrData as $key => $data) {
590: $productIds['product_id'] = $data['product_id'];
591: $arrData[$key]['rule_date'] = $this->formatDate($data['rule_date'], false);
592: $arrData[$key]['latest_start_date'] = $this->formatDate($data['latest_start_date'], false);
593: $arrData[$key]['earliest_end_date'] = $this->formatDate($data['earliest_end_date'], false);
594: }
595: $adapter->insertOnDuplicate($this->getTable('catalogrule/affected_product'), array_unique($productIds));
596: $adapter->insertOnDuplicate($this->getTable('catalogrule/rule_product_price'), $arrData);
597:
598: } catch (Exception $e) {
599: $adapter->rollback();
600: throw $e;
601:
602: }
603: $adapter->commit();
604:
605: return $this;
606: }
607:
608: 609: 610: 611: 612: 613: 614: 615: 616: 617: 618:
619: public function getRulePrice($date, $wId, $gId, $pId)
620: {
621: $data = $this->getRulePrices($date, $wId, $gId, array($pId));
622: if (isset($data[$pId])) {
623: return $data[$pId];
624: }
625:
626: return false;
627: }
628:
629: 630: 631: 632: 633: 634: 635: 636: 637: 638: 639:
640: public function getRulePrices($date, $websiteId, $customerGroupId, $productIds)
641: {
642: $adapter = $this->_getReadAdapter();
643: $select = $adapter->select()
644: ->from($this->getTable('catalogrule/rule_product_price'), array('product_id', 'rule_price'))
645: ->where('rule_date = ?', $this->formatDate($date, false))
646: ->where('website_id = ?', $websiteId)
647: ->where('customer_group_id = ?', $customerGroupId)
648: ->where('product_id IN(?)', $productIds);
649: return $adapter->fetchPairs($select);
650: }
651:
652: 653: 654: 655: 656: 657: 658: 659: 660:
661: public function getRulesFromProduct($date, $websiteId, $customerGroupId, $productId)
662: {
663: $adapter = $this->_getReadAdapter();
664: if (is_string($date)) {
665: $date = strtotime($date);
666: }
667: $select = $adapter->select()
668: ->from($this->getTable('catalogrule/rule_product'))
669: ->where('website_id = ?', $websiteId)
670: ->where('customer_group_id = ?', $customerGroupId)
671: ->where('product_id = ?', $productId)
672: ->where('from_time = 0 or from_time < ?', $date)
673: ->where('to_time = 0 or to_time > ?', $date);
674:
675: return $adapter->fetchAll($select);
676: }
677:
678: 679: 680: 681: 682: 683: 684: 685: 686:
687: public function getRulesForProduct($date, $wId, $pId)
688: {
689: $read = $this->_getReadAdapter();
690: $select = $read->select()
691: ->from($this->getTable('catalogrule/rule_product_price'), '*')
692: ->where('rule_date=?', $this->formatDate($date, false))
693: ->where('website_id=?', $wId)
694: ->where('product_id=?', $pId);
695:
696: return $read->fetchAll($select);
697: }
698:
699: 700: 701: 702: 703: 704: 705: 706: 707:
708: public function applyToProduct($rule, $product, $websiteIds)
709: {
710: if (!$rule->getIsActive()) {
711: return $this;
712: }
713:
714: $ruleId = $rule->getId();
715: $productId = $product->getId();
716:
717: $write = $this->_getWriteAdapter();
718: $write->beginTransaction();
719:
720: $write->delete($this->getTable('catalogrule/rule_product'), array(
721: $write->quoteInto('rule_id=?', $ruleId),
722: $write->quoteInto('product_id=?', $productId),
723: ));
724:
725: if (!$rule->getConditions()->validate($product)) {
726: $write->delete($this->getTable('catalogrule/rule_product_price'), array(
727: $write->quoteInto('product_id=?', $productId),
728: ));
729: $write->commit();
730: return $this;
731: }
732:
733: $customerGroupIds = $rule->getCustomerGroupIds();
734: $fromTime = strtotime($rule->getFromDate());
735: $toTime = strtotime($rule->getToDate());
736: $toTime = $toTime ? $toTime + self::SECONDS_IN_DAY - 1 : 0;
737: $sortOrder = (int)$rule->getSortOrder();
738: $actionOperator = $rule->getSimpleAction();
739: $actionAmount = $rule->getDiscountAmount();
740: $actionStop = $rule->getStopRulesProcessing();
741: $subActionOperator = $rule->getSubIsEnable() ? $rule->getSubSimpleAction() : '';
742: $subActionAmount = $rule->getSubDiscountAmount();
743:
744: $rows = array();
745: try {
746: foreach ($websiteIds as $websiteId) {
747: foreach ($customerGroupIds as $customerGroupId) {
748: $rows[] = array(
749: 'rule_id' => $ruleId,
750: 'from_time' => $fromTime,
751: 'to_time' => $toTime,
752: 'website_id' => $websiteId,
753: 'customer_group_id' => $customerGroupId,
754: 'product_id' => $productId,
755: 'action_operator' => $actionOperator,
756: 'action_amount' => $actionAmount,
757: 'action_stop' => $actionStop,
758: 'sort_order' => $sortOrder,
759: 'sub_simple_action' => $subActionOperator,
760: 'sub_discount_amount' => $subActionAmount,
761: );
762:
763: if (count($rows) == 1000) {
764: $write->insertMultiple($this->getTable('catalogrule/rule_product'), $rows);
765: $rows = array();
766: }
767: }
768: }
769:
770: if (!empty($rows)) {
771: $write->insertMultiple($this->getTable('catalogrule/rule_product'), $rows);
772: }
773: } catch (Exception $e) {
774: $write->rollback();
775: throw $e;
776: }
777:
778: $this->applyAllRulesForDateRange(null, null, $product);
779:
780: $write->commit();
781:
782: return $this;
783: }
784: }
785: