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: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53:
54: class Mage_CatalogIndex_Model_Indexer extends Mage_Core_Model_Abstract
55: {
56: const REINDEX_TYPE_ALL = 0;
57: const REINDEX_TYPE_PRICE = 1;
58: const REINDEX_TYPE_ATTRIBUTE = 2;
59:
60: const STEP_SIZE = 1000;
61:
62: 63: 64: 65: 66: 67:
68: protected $_indexers = array();
69:
70: 71: 72: 73: 74:
75: protected $_priceIndexers = array('price', 'tier_price', 'minimal_price');
76:
77: 78: 79: 80: 81: 82:
83: protected $_attributeIndexers = array('eav');
84:
85: 86: 87: 88: 89:
90: protected $_productTypePriority = null;
91:
92: 93: 94: 95:
96: protected function _construct()
97: {
98: $this->_loadIndexers();
99: $this->_init('catalogindex/indexer');
100: }
101:
102: 103: 104: 105: 106:
107: protected function _loadIndexers()
108: {
109: foreach ($this->_getRegisteredIndexers() as $name=>$class) {
110: $this->_indexers[$name] = Mage::getSingleton($class);
111: }
112: return $this;
113: }
114:
115: 116: 117: 118: 119:
120: protected function _getRegisteredIndexers()
121: {
122: $result = array();
123: $indexerRegistry = Mage::getConfig()->getNode('global/catalogindex/indexer');
124:
125: foreach ($indexerRegistry->children() as $node) {
126: $result[$node->getName()] = (string) $node->class;
127: }
128: return $result;
129: }
130:
131: 132: 133: 134: 135: 136:
137: protected function _getIndexableAttributeCodes()
138: {
139: $result = array();
140: foreach ($this->_indexers as $indexer) {
141: $codes = $indexer->getIndexableAttributeCodes();
142:
143: if (is_array($codes))
144: $result = array_merge($result, $codes);
145: }
146: return $result;
147: }
148:
149: 150: 151: 152: 153:
154: protected function _getStores()
155: {
156: $stores = $this->getData('_stores');
157: if (is_null($stores)) {
158: $stores = Mage::app()->getStores();
159: $this->setData('_stores', $stores);
160: }
161: return $stores;
162: }
163:
164: 165: 166: 167: 168:
169: protected function _getWebsites()
170: {
171: $websites = $this->getData('_websites');
172: if (is_null($websites)) {
173: $websites = Mage::getModel('core/website')->getCollection()->load();
174:
175:
176: $this->setData('_websites', $websites);
177: }
178: return $websites;
179: }
180:
181: 182: 183: 184: 185: 186:
187: public function cleanup($product)
188: {
189: $this->_getResource()->clear(true, true, true, true, true, $product, ($product->getNeedStoreForReindex() === true ? $this->_getStores() : null));
190: return $this;
191: }
192:
193: 194: 195: 196: 197: 198: 199: 200:
201: public function plainReindex($products = null, $attributes = null, $stores = null)
202: {
203: 204: 205:
206: $flag = Mage::getModel('catalogindex/catalog_index_flag')->loadSelf();
207: if ($flag->getState() == Mage_CatalogIndex_Model_Catalog_Index_Flag::STATE_RUNNING) {
208: return $this;
209: }
210:
211: else {
212: $flag->setState(Mage_CatalogIndex_Model_Catalog_Index_Flag::STATE_RUNNING)->save();
213: }
214:
215: try {
216: 217: 218:
219: $websites = array();
220: $attributeCodes = $priceAttributeCodes = array();
221:
222:
223:
224:
225:
226:
227:
228: 229: 230:
231: if (is_null($stores)) {
232: $stores = $this->_getStores();
233: $websites = $this->_getWebsites();
234: }
235: elseif ($stores instanceof Mage_Core_Model_Store) {
236: $websites[] = $stores->getWebsiteId();
237: $stores = array($stores);
238: }
239: elseif (is_array($stores)) {
240: foreach ($stores as $one) {
241: $websites[] = Mage::app()->getStore($one)->getWebsiteId();
242: }
243: }
244: elseif (!is_array($stores)) {
245: Mage::throwException('Invalid stores supplied for indexing');
246: }
247:
248: 249: 250:
251: if (is_null($attributes)) {
252: $priceAttributeCodes = $this->_indexers['price']->getIndexableAttributeCodes();
253: $attributeCodes = $this->_indexers['eav']->getIndexableAttributeCodes();
254: }
255: elseif ($attributes instanceof Mage_Eav_Model_Entity_Attribute_Abstract) {
256: if ($this->_indexers['eav']->isAttributeIndexable($attributes)) {
257: $attributeCodes[] = $attributes->getAttributeId();
258: }
259: if ($this->_indexers['price']->isAttributeIndexable($attributes)) {
260: $priceAttributeCodes[] = $attributes->getAttributeId();
261: }
262: }
263: elseif ($attributes == self::REINDEX_TYPE_PRICE) {
264: $priceAttributeCodes = $this->_indexers['price']->getIndexableAttributeCodes();
265: }
266: elseif ($attributes == self::REINDEX_TYPE_ATTRIBUTE) {
267: $attributeCodes = $this->_indexers['eav']->getIndexableAttributeCodes();
268: }
269: else {
270: Mage::throwException('Invalid attributes supplied for indexing');
271: }
272:
273: 274: 275:
276: $this->_getResource()->clear(
277: $attributeCodes,
278: $priceAttributeCodes,
279: count($priceAttributeCodes)>0,
280: count($priceAttributeCodes)>0,
281: count($priceAttributeCodes)>0,
282: $products,
283: $stores
284: );
285:
286: 287: 288: 289:
290: foreach ($websites as $website) {
291: $ws = Mage::app()->getWebsite($website);
292: if (!$ws) {
293: continue;
294: }
295:
296: $group = $ws->getDefaultGroup();
297: if (!$group) {
298: continue;
299: }
300:
301: $store = $group->getDefaultStore();
302:
303: 304: 305:
306: if (!$store) {
307: continue;
308: }
309:
310: foreach ($this->_getPriorifiedProductTypes() as $type) {
311: $collection = $this->_getProductCollection($store, $products);
312: $collection->addAttributeToFilter(
313: 'status',
314: array('in'=>Mage::getModel('catalog/product_status')->getSaleableStatusIds())
315: );
316: $collection->addFieldToFilter('type_id', $type);
317: $this->_walkCollection($collection, $store, array(), $priceAttributeCodes);
318: if (!is_null($products) && !$this->getRetreiver($type)->getTypeInstance()->isComposite()) {
319: $this->_walkCollectionRelation($collection, $ws, array(), $priceAttributeCodes);
320: }
321: }
322: }
323:
324: 325: 326:
327: foreach ($stores as $store) {
328: foreach ($this->_getPriorifiedProductTypes() as $type) {
329: $collection = $this->_getProductCollection($store, $products);
330: Mage::getSingleton('catalog/product_visibility')->addVisibleInSiteFilterToCollection($collection);
331: $collection->addFieldToFilter('type_id', $type);
332:
333: $this->_walkCollection($collection, $store, $attributeCodes);
334: if (!is_null($products) && !$this->getRetreiver($type)->getTypeInstance()->isComposite()) {
335: $this->_walkCollectionRelation($collection, $store, $attributeCodes);
336: }
337: }
338: }
339:
340: $this->_afterPlainReindex($stores, $products);
341:
342: 343: 344:
345: if (Mage::helper('catalog/product_flat')->isBuilt()) {
346: foreach ($stores as $store) {
347: $this->updateCatalogProductFlat($store, $products);
348: }
349: }
350:
351: } catch (Exception $e) {
352: $flag->delete();
353: throw $e;
354: }
355:
356: if ($flag->getState() == Mage_CatalogIndex_Model_Catalog_Index_Flag::STATE_RUNNING) {
357: $flag->delete();
358: }
359:
360: return $this;
361: }
362:
363: 364: 365: 366: 367: 368: 369:
370: protected function _afterPlainReindex($store, $products = null)
371: {
372: Mage::dispatchEvent('catalogindex_plain_reindex_after', array(
373: 'products' => $products
374: ));
375:
376: 377: 378:
379: if (Mage::helper('catalog/product_flat')->isBuilt()) {
380: if ($store instanceof Mage_Core_Model_Website) {
381: foreach ($store->getStores() as $storeObject) {
382: $this->_afterPlainReindex($storeObject->getId(), $products);
383: }
384: return $this;
385: }
386: elseif ($store instanceof Mage_Core_Model_Store) {
387: $store = $store->getId();
388: }
389:
390: elseif (is_array($store)) {
391: foreach ($store as $storeObject) {
392: $this->_afterPlainReindex($storeObject->getId(), $products);
393: }
394: return $this;
395: }
396:
397: $this->updateCatalogProductFlat($store, $products);
398: }
399:
400: return $this;
401: }
402:
403: 404: 405: 406: 407: 408: 409:
410: protected function _getProductCollection($store, $products)
411: {
412: $collection = Mage::getModel('catalog/product')
413: ->getCollection()
414: ->setStoreId($store)
415: ->addStoreFilter($store);
416: if ($products instanceof Mage_Catalog_Model_Product) {
417: $collection->addIdFilter($products->getId());
418: } else if (is_array($products) || is_numeric($products)) {
419: $collection->addIdFilter($products);
420: } elseif ($products instanceof Mage_Catalog_Model_Product_Condition_Interface) {
421: $products->applyToCollection($collection);
422: }
423:
424: return $collection;
425: }
426:
427: 428: 429: 430: 431: 432: 433: 434: 435:
436: public function _walkCollectionRelation($collection, $store, $attributes = array(), $prices = array())
437: {
438: if ($store instanceof Mage_Core_Model_Website) {
439: $storeObject = $store->getDefaultStore();
440: }
441: elseif ($store instanceof Mage_Core_Model_Store) {
442: $storeObject = $store;
443: }
444:
445: $statusCond = array(
446: 'in' => Mage::getSingleton('catalog/product_status')->getSaleableStatusIds()
447: );
448:
449: $productCount = $collection->getSize();
450: $iterateCount = ($productCount / self::STEP_SIZE);
451: for ($i = 0; $i < $iterateCount; $i++) {
452: $stepData = $collection
453: ->getAllIds(self::STEP_SIZE, $i * self::STEP_SIZE);
454: foreach ($this->_getPriorifiedProductTypes() as $type) {
455: $retriever = $this->getRetreiver($type);
456: if (!$retriever->getTypeInstance()->isComposite()) {
457: continue;
458: }
459:
460: $parentIds = $retriever->getTypeInstance()
461: ->getParentIdsByChild($stepData);
462: if ($parentIds) {
463: $parentCollection = $this->_getProductCollection($storeObject, $parentIds);
464: $parentCollection->addAttributeToFilter('status', $statusCond);
465: $parentCollection->addFieldToFilter('type_id', $type);
466: $this->_walkCollection($parentCollection, $storeObject, $attributes, $prices);
467:
468: $this->_afterPlainReindex($store, $parentIds);
469: }
470: }
471: }
472:
473: return $this;
474: }
475:
476: 477: 478: 479: 480: 481: 482: 483: 484:
485: protected function _walkCollection($collection, $store, $attributes = array(), $prices = array())
486: {
487: $productCount = $collection->getSize();
488: if (!$productCount) {
489: return $this;
490: }
491:
492: for ($i=0;$i<$productCount/self::STEP_SIZE;$i++) {
493: $this->_getResource()->beginTransaction();
494:
495: $stepData = $collection->getAllIds(self::STEP_SIZE, $i*self::STEP_SIZE);
496:
497: 498: 499:
500: if (count($attributes)) {
501: $this->_getResource()->reindexAttributes($stepData, $attributes, $store);
502: }
503:
504: 505: 506:
507: if (count($prices)) {
508: $this->_getResource()->reindexPrices($stepData, $prices, $store);
509: $this->_getResource()->reindexTiers($stepData, $store);
510: $this->_getResource()->reindexMinimalPrices($stepData, $store);
511: $this->_getResource()->reindexFinalPrices($stepData, $store);
512: }
513:
514: Mage::getResourceSingleton('catalog/product')->refreshEnabledIndex($store, $stepData);
515:
516: $kill = Mage::getModel('catalogindex/catalog_index_kill_flag')->loadSelf();
517: if ($kill->checkIsThisProcess()) {
518: $this->_getResource()->rollBack();
519: $kill->delete();
520: } else {
521: $this->_getResource()->commit();
522: }
523: }
524: return $this;
525: }
526:
527: 528: 529: 530: 531: 532:
533: public function getRetreiver($type)
534: {
535: return Mage::getSingleton('catalogindex/retreiver')->getRetreiver($type);
536: }
537:
538: 539: 540: 541: 542:
543: public function queueIndexing()
544: {
545: Mage::getModel('catalogindex/catalog_index_flag')
546: ->loadSelf()
547: ->setState(Mage_CatalogIndex_Model_Catalog_Index_Flag::STATE_QUEUED)
548: ->save();
549:
550: return $this;
551: }
552:
553: 554: 555: 556: 557: 558: 559: 560:
561: protected function _getPriorifiedProductTypes()
562: {
563: if (is_null($this->_productTypePriority)) {
564: $this->_productTypePriority = array();
565: $config = Mage::getConfig()->getNode('global/catalog/product/type');
566:
567: foreach ($config->children() as $type) {
568: $typeName = $type->getName();
569: $typePriority = (string) $type->index_priority;
570: $this->_productTypePriority[$typePriority] = $typeName;
571: }
572: ksort($this->_productTypePriority);
573: }
574: return $this->_productTypePriority;
575: }
576:
577: 578: 579: 580: 581: 582:
583: protected function _getBaseToSpecifiedCurrencyRate($code)
584: {
585: return Mage::app()->getStore()->getBaseCurrency()->getRate($code);
586: }
587:
588: 589: 590: 591: 592: 593: 594: 595: 596:
597: public function buildEntityPriceFilter($attributes, $values, &$filteredAttributes, $productCollection)
598: {
599: $additionalCalculations = array();
600: $filter = array();
601: $store = Mage::app()->getStore()->getId();
602: $website = Mage::app()->getStore()->getWebsiteId();
603:
604: $currentStoreCurrency = Mage::app()->getStore()->getCurrentCurrencyCode();
605:
606: foreach ($attributes as $attribute) {
607: $code = $attribute->getAttributeCode();
608: if (isset($values[$code])) {
609: foreach ($this->_priceIndexers as $indexerName) {
610: $indexer = $this->_indexers[$indexerName];
611:
612: if ($indexer->isAttributeIndexable($attribute)) {
613: if ($values[$code]) {
614: if (isset($values[$code]['from']) && isset($values[$code]['to'])
615: && (strlen($values[$code]['from']) == 0 && strlen($values[$code]['to']) == 0)) {
616: continue;
617: }
618: $table = $indexer->getResource()->getMainTable();
619: if (!isset($filter[$code])) {
620: $filter[$code] = $this->_getSelect();
621: $filter[$code]->from($table, array('entity_id'));
622: $filter[$code]->distinct(true);
623:
624: $response = new Varien_Object();
625: $response->setAdditionalCalculations(array());
626: $args = array(
627: 'select'=>$filter[$code],
628: 'table'=>$table,
629: 'store_id'=>$store,
630: 'response_object'=>$response,
631: );
632: Mage::dispatchEvent('catalogindex_prepare_price_select', $args);
633: $additionalCalculations[$code] = $response->getAdditionalCalculations();
634:
635: if ($indexer->isAttributeIdUsed()) {
636:
637: }
638: }
639: if (is_array($values[$code])) {
640: $rateConversion = 1;
641: $filter[$code]->distinct(true);
642:
643: if (isset($values[$code]['from']) && isset($values[$code]['to'])) {
644: if (isset($values[$code]['currency'])) {
645: $rateConversion = $this->_getBaseToSpecifiedCurrencyRate($values[$code]['currency']);
646: } else {
647: $rateConversion = $this->_getBaseToSpecifiedCurrencyRate($currentStoreCurrency);
648: }
649:
650: if (strlen($values[$code]['from'])>0) {
651: $filter[$code]->where(
652: "($table.min_price".implode('', $additionalCalculations[$code]).")*{$rateConversion} >= ?",
653: $values[$code]['from']
654: );
655: }
656:
657: if (strlen($values[$code]['to'])>0) {
658: $filter[$code]->where(
659: "($table.min_price".implode('', $additionalCalculations[$code]).")*{$rateConversion} <= ?",
660: $values[$code]['to']
661: );
662: }
663: }
664: }
665: $filter[$code]->where("$table.website_id = ?", $website);
666:
667: if ($code == 'price') {
668: $filter[$code]->where(
669: $table . '.customer_group_id = ?',
670: Mage::getSingleton('customer/session')->getCustomerGroupId()
671: );
672: }
673:
674: $filteredAttributes[]=$code;
675: }
676: }
677: }
678: }
679: }
680: return $filter;
681: }
682:
683: 684: 685: 686: 687: 688: 689: 690: 691:
692: public function buildEntityFilter($attributes, $values, &$filteredAttributes, $productCollection)
693: {
694: $filter = array();
695: $store = Mage::app()->getStore()->getId();
696:
697: foreach ($attributes as $attribute) {
698: $code = $attribute->getAttributeCode();
699: if (isset($values[$code])) {
700: foreach ($this->_attributeIndexers as $indexerName) {
701: $indexer = $this->_indexers[$indexerName];
702:
703: if ($indexer->isAttributeIndexable($attribute)) {
704: if ($values[$code]) {
705: if (isset($values[$code]['from']) && isset($values[$code]['to'])
706: && (!$values[$code]['from'] && !$values[$code]['to'])) {
707: continue;
708: }
709:
710: $table = $indexer->getResource()->getMainTable();
711: if (!isset($filter[$code])) {
712: $filter[$code] = $this->_getSelect();
713: $filter[$code]->from($table, array('entity_id'));
714: }
715: if ($indexer->isAttributeIdUsed()) {
716: $filter[$code]->where('attribute_id = ?', $attribute->getId());
717: }
718: if (is_array($values[$code])) {
719: if (isset($values[$code]['from']) && isset($values[$code]['to'])) {
720:
721: if ($values[$code]['from']) {
722: if (!is_numeric($values[$code]['from'])) {
723: $values[$code]['from'] = date("Y-m-d H:i:s", strtotime($values[$code]['from']));
724: }
725:
726: $filter[$code]->where("value >= ?", $values[$code]['from']);
727: }
728:
729:
730: if ($values[$code]['to']) {
731: if (!is_numeric($values[$code]['to'])) {
732: $values[$code]['to'] = date("Y-m-d H:i:s", strtotime($values[$code]['to']));
733: }
734: $filter[$code]->where("value <= ?", $values[$code]['to']);
735: }
736: } else {
737: $filter[$code]->where('value in (?)', $values[$code]);
738: }
739: } else {
740: $filter[$code]->where('value = ?', $values[$code]);
741: }
742: $filter[$code]->where('store_id = ?', $store);
743: $filteredAttributes[]=$code;
744: }
745: }
746: }
747: }
748: }
749: return $filter;
750: }
751:
752: 753: 754: 755: 756:
757: protected function _getSelect()
758: {
759: return $this->_getResource()->getReadConnection()->select();
760: }
761:
762: 763: 764: 765: 766: 767: 768:
769: protected function _addFilterableAttributesToCollection($collection)
770: {
771: $attributeCodes = $this->_getIndexableAttributeCodes();
772: foreach ($attributeCodes as $code) {
773: $collection->addAttributeToSelect($code);
774: }
775:
776: return $this;
777: }
778:
779: 780: 781: 782: 783: 784:
785: public function prepareCatalogProductFlatColumns(Varien_Object $object)
786: {
787: $this->_getResource()->prepareCatalogProductFlatColumns($object);
788:
789: return $this;
790: }
791:
792: 793: 794: 795: 796: 797:
798: public function prepareCatalogProductFlatIndexes(Varien_Object $object)
799: {
800: $this->_getResource()->prepareCatalogProductFlatIndexes($object);
801:
802: return $this;
803: }
804:
805: 806: 807: 808: 809: 810: 811: 812:
813: public function updateCatalogProductFlat($store, $products = null, $resourceTable = null)
814: {
815: if ($store instanceof Mage_Core_Model_Store) {
816: $store = $store->getId();
817: }
818: if ($products instanceof Mage_Catalog_Model_Product) {
819: $products = $products->getId();
820: }
821: $this->_getResource()->updateCatalogProductFlat($store, $products, $resourceTable);
822:
823: return $this;
824: }
825: }
826: