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_Catalog_Model_Resource_Category_Indexer_Product extends Mage_Index_Model_Resource_Abstract
36: {
37: 38: 39: 40: 41:
42: protected $_categoryTable;
43:
44: 45: 46: 47: 48:
49: protected $_categoryProductTable;
50:
51: 52: 53: 54: 55:
56: protected $_productWebsiteTable;
57:
58: 59: 60: 61: 62:
63: protected $_storeTable;
64:
65: 66: 67: 68: 69:
70: protected $_groupTable;
71:
72: 73: 74: 75: 76:
77: protected $_storesInfo;
78:
79: 80: 81: 82:
83: protected function _construct()
84: {
85: $this->_init('catalog/category_product_index', 'category_id');
86: $this->_categoryTable = $this->getTable('catalog/category');
87: $this->_categoryProductTable = $this->getTable('catalog/category_product');
88: $this->_productWebsiteTable = $this->getTable('catalog/product_website');
89: $this->_storeTable = $this->getTable('core/store');
90: $this->_groupTable = $this->getTable('core/store_group');
91: }
92:
93: 94: 95: 96: 97: 98: 99: 100:
101: public function catalogProductSave(Mage_Index_Model_Event $event)
102: {
103: $productId = $event->getEntityPk();
104: $data = $event->getNewData();
105:
106: 107: 108:
109: if (!isset($data['category_ids'])) {
110: return $this;
111: }
112:
113: 114: 115:
116: $select = $this->_getWriteAdapter()->select()
117: ->from(array('cp' => $this->_categoryProductTable), 'category_id')
118: ->joinInner(array('ce' => $this->_categoryTable), 'ce.entity_id=cp.category_id', 'path')
119: ->where('cp.product_id=:product_id');
120:
121: 122: 123:
124: $categories = $this->_getWriteAdapter()->fetchPairs($select, array('product_id' => $productId));
125: $categoryIds = array();
126: $allCategoryIds = array();
127:
128: foreach ($categories as $id=>$path) {
129: $categoryIds[] = $id;
130: $allCategoryIds = array_merge($allCategoryIds, explode('/', $path));
131: }
132: $allCategoryIds = array_unique($allCategoryIds);
133: $allCategoryIds = array_diff($allCategoryIds, $categoryIds);
134:
135: 136: 137:
138: $this->_getWriteAdapter()->delete(
139: $this->getMainTable(),
140: array('product_id = ?' => $productId)
141: );
142:
143: $this->_refreshAnchorRelations($allCategoryIds, $productId);
144: $this->_refreshDirectRelations($categoryIds, $productId);
145: $this->_refreshRootRelations($productId);
146: return $this;
147: }
148:
149: 150: 151: 152: 153: 154:
155: public function catalogProductMassAction(Mage_Index_Model_Event $event)
156: {
157: $data = $event->getNewData();
158:
159: 160: 161:
162: if (!isset($data['product_ids'])) {
163: return $this;
164: }
165: $productIds = $data['product_ids'];
166: $categoryIds = array();
167: $allCategoryIds = array();
168:
169: 170: 171:
172: $adapter = $this->_getWriteAdapter();
173: $select = $adapter->select()
174: ->distinct(true)
175: ->from(array('cp' => $this->_categoryProductTable), array('category_id'))
176: ->join(
177: array('ce' => $this->_categoryTable),
178: 'ce.entity_id=cp.category_id',
179: array('path'))
180: ->where('cp.product_id IN(?)', $productIds);
181: $pairs = $adapter->fetchPairs($select);
182: foreach ($pairs as $categoryId => $categoryPath) {
183: $categoryIds[] = $categoryId;
184: $allCategoryIds = array_merge($allCategoryIds, explode('/', $categoryPath));
185: }
186:
187: $allCategoryIds = array_unique($allCategoryIds);
188: $allCategoryIds = array_diff($allCategoryIds, $categoryIds);
189:
190: 191: 192:
193: $this->_getWriteAdapter()->delete(
194: $this->getMainTable(), array('product_id IN(?)' => $productIds)
195: );
196:
197: $this->_refreshAnchorRelations($allCategoryIds, $productIds);
198: $this->_refreshDirectRelations($categoryIds, $productIds);
199: $this->_refreshRootRelations($productIds);
200: return $this;
201: }
202:
203: 204: 205: 206: 207:
208: protected function _getRootCategories()
209: {
210: $rootCategories = array();
211: $stores = $this->_getStoresInfo();
212: foreach ($stores as $storeInfo) {
213: if ($storeInfo['root_id']) {
214: $rootCategories[$storeInfo['root_id']] = $storeInfo['root_path'];
215: }
216: }
217:
218: return $rootCategories;
219: }
220:
221: 222: 223: 224: 225:
226: public function catalogCategorySave(Mage_Index_Model_Event $event)
227: {
228: $data = $event->getNewData();
229:
230: $checkRootCategories = false;
231: $processRootCategories = false;
232: $affectedRootCategoryIds = array();
233: $rootCategories = $this->_getRootCategories();
234:
235: 236: 237:
238: if (isset($data['affected_category_ids'])) {
239: $categoryIds = $data['affected_category_ids'];
240: $checkRootCategories = true;
241: } else if (isset($data['products_was_changed'])) {
242: $categoryIds = array($event->getEntityPk());
243:
244: if (isset($rootCategories[$event->getEntityPk()])) {
245: $processRootCategories = true;
246: $affectedRootCategoryIds[] = $event->getEntityPk();
247: }
248: } else {
249: return;
250: }
251:
252: $select = $this->_getWriteAdapter()->select()
253: ->from($this->_categoryTable, 'path')
254: ->where('entity_id IN (?)', $categoryIds);
255: $paths = $this->_getWriteAdapter()->fetchCol($select);
256: $allCategoryIds = array();
257: foreach ($paths as $path) {
258: if ($checkRootCategories) {
259: foreach ($rootCategories as $rootCategoryId => $rootCategoryPath) {
260: if (strpos($path, sprintf('%d/', $rootCategoryPath)) === 0 || $path == $rootCategoryPath) {
261: $affectedRootCategoryIds[$rootCategoryId] = $rootCategoryId;
262: }
263: }
264: }
265: $allCategoryIds = array_merge($allCategoryIds, explode('/', $path));
266: }
267: $allCategoryIds = array_unique($allCategoryIds);
268:
269: if ($checkRootCategories && count($affectedRootCategoryIds) > 1) {
270: $processRootCategories = true;
271: }
272:
273: 274: 275:
276: $anchorInfo = $this->_getAnchorAttributeInfo();
277: $bind = array(
278: 'attribute_id' => $anchorInfo['id'],
279: 'store_id' => Mage_Catalog_Model_Abstract::DEFAULT_STORE_ID,
280: 'e_value' => 1
281: );
282: $select = $this->_getReadAdapter()->select()
283: ->distinct(true)
284: ->from(array('ce' => $this->_categoryTable), array('entity_id'))
285: ->joinInner(
286: array('dca'=>$anchorInfo['table']),
287: "dca.entity_id=ce.entity_id AND dca.attribute_id=:attribute_id AND dca.store_id=:store_id",
288: array())
289: ->where('dca.value=:e_value')
290: ->where('ce.entity_id IN (?)', $allCategoryIds);
291: $anchorIds = $this->_getWriteAdapter()->fetchCol($select, $bind);
292: 293: 294:
295: $deleteCategoryIds = array_merge($anchorIds,$categoryIds);
296:
297: $this->_getWriteAdapter()->delete(
298: $this->getMainTable(),
299: $this->_getWriteAdapter()->quoteInto('category_id IN(?)', $deleteCategoryIds)
300: );
301:
302: $directIds = array_diff($categoryIds, $anchorIds);
303: if ($anchorIds) {
304: $this->_refreshAnchorRelations($anchorIds);
305: }
306: if ($directIds) {
307: $this->_refreshDirectRelations($directIds);
308: }
309:
310: 311: 312:
313: if ($processRootCategories) {
314: $reindexRootCategoryIds = array_diff($affectedRootCategoryIds, $anchorIds);
315: if ($reindexRootCategoryIds) {
316: $this->_refreshNotAnchorRootCategories($reindexRootCategoryIds);
317: }
318: }
319:
320: }
321:
322: 323: 324: 325: 326: 327:
328: protected function _refreshNotAnchorRootCategories(array $categoryIds = null)
329: {
330: if (empty($categoryIds)) {
331: return $this;
332: }
333:
334: $adapter = $this->_getWriteAdapter();
335:
336:
337: $where = array(
338: 'category_id IN(?)' => $categoryIds,
339: 'is_parent=?' => 0
340: );
341: $adapter->delete($this->getMainTable(), $where);
342:
343: $stores = $this->_getStoresInfo();
344: 345: 346:
347: foreach ($stores as $storeData) {
348: $storeId = $storeData['store_id'];
349: $websiteId = $storeData['website_id'];
350: $rootPath = $storeData['root_path'];
351: $rootId = $storeData['root_id'];
352: if (!in_array($rootId, $categoryIds)) {
353: continue;
354: }
355:
356: $select = $adapter->select()
357: ->distinct(true)
358: ->from(array('cc' => $this->getTable('catalog/category')), null)
359: ->join(
360: array('i' => $this->getMainTable()),
361: 'i.category_id = cc.entity_id and i.store_id = 1',
362: array())
363: ->joinLeft(
364: array('ie' => $this->getMainTable()),
365: 'ie.category_id = ' . (int)$rootId
366: . ' AND ie.product_id=i.product_id AND ie.store_id = ' . (int)$storeId,
367: array())
368: ->where('cc.path LIKE ?', $rootPath . '/%')
369: ->where('ie.category_id IS NULL')
370: ->columns(array(
371: 'category_id' => new Zend_Db_Expr($rootId),
372: 'product_id' => 'i.product_id',
373: 'position' => new Zend_Db_Expr('0'),
374: 'is_parent' => new Zend_Db_Expr('0'),
375: 'store_id' => new Zend_Db_Expr($storeId),
376: 'visibility' => 'i.visibility'
377: ));
378: $query = $select->insertFromSelect($this->getMainTable());
379: $adapter->query($query);
380:
381: $visibilityInfo = $this->_getVisibilityAttributeInfo();
382: $statusInfo = $this->_getStatusAttributeInfo();
383:
384: $select = $this->_getReadAdapter()->select()
385: ->from(array('pw' => $this->_productWebsiteTable), array())
386: ->joinLeft(
387: array('i' => $this->getMainTable()),
388: 'i.product_id = pw.product_id AND i.category_id = ' . (int)$rootId
389: . ' AND i.store_id = ' . (int) $storeId,
390: array())
391: ->join(
392: array('dv' => $visibilityInfo['table']),
393: "dv.entity_id = pw.product_id AND dv.attribute_id = {$visibilityInfo['id']} AND dv.store_id = 0",
394: array())
395: ->joinLeft(
396: array('sv' => $visibilityInfo['table']),
397: "sv.entity_id = pw.product_id AND sv.attribute_id = {$visibilityInfo['id']}"
398: . " AND sv.store_id = " . (int)$storeId,
399: array())
400: ->join(
401: array('ds' => $statusInfo['table']),
402: "ds.entity_id = pw.product_id AND ds.attribute_id = {$statusInfo['id']} AND ds.store_id = 0",
403: array())
404: ->joinLeft(
405: array('ss' => $statusInfo['table']),
406: "ss.entity_id = pw.product_id AND ss.attribute_id = {$statusInfo['id']}"
407: . " AND ss.store_id = " . (int)$storeId,
408: array())
409: ->where('i.product_id IS NULL')
410: ->where('pw.website_id=?', $websiteId)
411: ->where(
412: $this->_getWriteAdapter()->getCheckSql('ss.value_id IS NOT NULL', 'ss.value', 'ds.value') . ' = ?',
413: Mage_Catalog_Model_Product_Status::STATUS_ENABLED)
414: ->columns(array(
415: 'category_id' => new Zend_Db_Expr($rootId),
416: 'product_id' => 'pw.product_id',
417: 'position' => new Zend_Db_Expr('0'),
418: 'is_parent' => new Zend_Db_Expr('1'),
419: 'store_id' => new Zend_Db_Expr($storeId),
420: 'visibility' => $adapter->getCheckSql('sv.value_id IS NOT NULL', 'sv.value', 'dv.value')
421: ));
422:
423: $query = $select->insertFromSelect($this->getMainTable());
424: $this->_getWriteAdapter()->query($query);
425: }
426:
427: return $this;
428: }
429:
430:
431: 432: 433: 434: 435: 436: 437:
438: protected function _refreshDirectRelations($categoryIds = null, $productIds = null)
439: {
440: if (!$categoryIds && !$productIds) {
441: return $this;
442: }
443:
444: $visibilityInfo = $this->_getVisibilityAttributeInfo();
445: $statusInfo = $this->_getStatusAttributeInfo();
446: $adapter = $this->_getWriteAdapter();
447: 448: 449: 450: 451:
452: $isParent = new Zend_Db_Expr('1');
453: $select = $adapter->select()
454: ->from(array('cp' => $this->_categoryProductTable),
455: array('category_id', 'product_id', 'position', $isParent))
456: ->joinInner(array('pw' => $this->_productWebsiteTable), 'pw.product_id=cp.product_id', array())
457: ->joinInner(array('g' => $this->_groupTable), 'g.website_id=pw.website_id', array())
458: ->joinInner(array('s' => $this->_storeTable), 's.group_id=g.group_id', array('store_id'))
459: ->joinInner(array('rc' => $this->_categoryTable), 'rc.entity_id=g.root_category_id', array())
460: ->joinInner(
461: array('ce'=>$this->_categoryTable),
462: 'ce.entity_id=cp.category_id AND ('.
463: $adapter->quoteIdentifier('ce.path') . ' LIKE ' .
464: $adapter->getConcatSql(array($adapter->quoteIdentifier('rc.path') , $adapter->quote('/%'))) .
465: ' OR ce.entity_id=rc.entity_id)',
466: array())
467: ->joinLeft(
468: array('dv'=>$visibilityInfo['table']),
469: $adapter->quoteInto(
470: "dv.entity_id=cp.product_id AND dv.attribute_id=? AND dv.store_id=0",
471: $visibilityInfo['id']),
472: array()
473: )
474: ->joinLeft(
475: array('sv'=>$visibilityInfo['table']),
476: $adapter->quoteInto(
477: "sv.entity_id=cp.product_id AND sv.attribute_id=? AND sv.store_id=s.store_id",
478: $visibilityInfo['id']),
479: array('visibility' => $adapter->getCheckSql('sv.value_id IS NOT NULL',
480: $adapter->quoteIdentifier('sv.value'),
481: $adapter->quoteIdentifier('dv.value')
482: ))
483: )
484: ->joinLeft(
485: array('ds'=>$statusInfo['table']),
486: "ds.entity_id=cp.product_id AND ds.attribute_id={$statusInfo['id']} AND ds.store_id=0",
487: array())
488: ->joinLeft(
489: array('ss'=>$statusInfo['table']),
490: "ss.entity_id=cp.product_id AND ss.attribute_id={$statusInfo['id']} AND ss.store_id=s.store_id",
491: array())
492: ->where(
493: $adapter->getCheckSql('ss.value_id IS NOT NULL',
494: $adapter->quoteIdentifier('ss.value'),
495: $adapter->quoteIdentifier('ds.value')
496: ) . ' = ?',
497: Mage_Catalog_Model_Product_Status::STATUS_ENABLED
498: );
499: if ($categoryIds) {
500: $select->where('cp.category_id IN (?)', $categoryIds);
501: }
502: if ($productIds) {
503: $select->where('cp.product_id IN(?)', $productIds);
504: }
505: $sql = $select->insertFromSelect(
506: $this->getMainTable(),
507: array('category_id', 'product_id', 'position', 'is_parent', 'store_id', 'visibility'),
508: true
509: );
510: $adapter->query($sql);
511: return $this;
512: }
513:
514: 515: 516: 517: 518: 519: 520:
521: protected function _refreshAnchorRelations($categoryIds = null, $productIds = null)
522: {
523: if (!$categoryIds && !$productIds) {
524: return $this;
525: }
526:
527: $anchorInfo = $this->_getAnchorAttributeInfo();
528: $visibilityInfo = $this->_getVisibilityAttributeInfo();
529: $statusInfo = $this->_getStatusAttributeInfo();
530:
531: 532: 533:
534: $adapter = $this->_getReadAdapter();
535: $isParent = $adapter->getCheckSql('MIN(cp.category_id)=ce.entity_id', 1, 0);
536: $position = 'MIN('.
537: $adapter->getCheckSql(
538: 'cp.category_id = ce.entity_id',
539: 'cp.position',
540: '(cc.position + 1) * ('.$adapter->quoteIdentifier('cc.level').' + 1) * 10000 + cp.position'
541: )
542: .')';
543:
544: $select = $adapter->select()
545: ->distinct(true)
546: ->from(array('ce' => $this->_categoryTable), array('entity_id'))
547: ->joinInner(
548: array('cc' => $this->_categoryTable),
549: $adapter->quoteIdentifier('cc.path') .
550: ' LIKE ('.$adapter->getConcatSql(array($adapter->quoteIdentifier('ce.path'),$adapter->quote('/%'))).')'
551: . ' OR cc.entity_id=ce.entity_id'
552: , array()
553: )
554: ->joinInner(
555: array('cp' => $this->_categoryProductTable),
556: 'cp.category_id=cc.entity_id',
557: array('cp.product_id', 'position' => $position, 'is_parent' => $isParent)
558: )
559: ->joinInner(array('pw' => $this->_productWebsiteTable), 'pw.product_id=cp.product_id', array())
560: ->joinInner(array('g' => $this->_groupTable), 'g.website_id=pw.website_id', array())
561: ->joinInner(array('s' => $this->_storeTable), 's.group_id=g.group_id', array('store_id'))
562: ->joinInner(array('rc' => $this->_categoryTable), 'rc.entity_id=g.root_category_id', array())
563: ->joinLeft(
564: array('dca'=>$anchorInfo['table']),
565: "dca.entity_id=ce.entity_id AND dca.attribute_id={$anchorInfo['id']} AND dca.store_id=0",
566: array())
567: ->joinLeft(
568: array('sca'=>$anchorInfo['table']),
569: "sca.entity_id=ce.entity_id AND sca.attribute_id={$anchorInfo['id']} AND sca.store_id=s.store_id",
570: array())
571: ->joinLeft(
572: array('dv'=>$visibilityInfo['table']),
573: "dv.entity_id=pw.product_id AND dv.attribute_id={$visibilityInfo['id']} AND dv.store_id=0",
574: array())
575: ->joinLeft(
576: array('sv'=>$visibilityInfo['table']),
577: "sv.entity_id=pw.product_id AND sv.attribute_id={$visibilityInfo['id']} AND sv.store_id=s.store_id",
578: array('visibility' => $adapter->getCheckSql(
579: 'MIN(sv.value_id) IS NOT NULL',
580: 'MIN(sv.value)', 'MIN(dv.value)'
581: ))
582: )
583: ->joinLeft(
584: array('ds'=>$statusInfo['table']),
585: "ds.entity_id=pw.product_id AND ds.attribute_id={$statusInfo['id']} AND ds.store_id=0",
586: array())
587: ->joinLeft(
588: array('ss'=>$statusInfo['table']),
589: "ss.entity_id=pw.product_id AND ss.attribute_id={$statusInfo['id']} AND ss.store_id=s.store_id",
590: array())
591: 592: 593:
594: ->where('('.
595: $adapter->quoteIdentifier('ce.path') . ' LIKE ' .
596: $adapter->getConcatSql(array($adapter->quoteIdentifier('rc.path'), $adapter->quote('/%'))) . ' AND ' .
597: $adapter->getCheckSql('sca.value_id IS NOT NULL',
598: $adapter->quoteIdentifier('sca.value'),
599: $adapter->quoteIdentifier('dca.value')) . '=1) OR ce.entity_id=rc.entity_id'
600: )
601: ->where(
602: $adapter->getCheckSql('ss.value_id IS NOT NULL', 'ss.value', 'ds.value') . '=?',
603: Mage_Catalog_Model_Product_Status::STATUS_ENABLED
604: )
605: ->group(array('ce.entity_id', 'cp.product_id', 's.store_id'));
606: if ($categoryIds) {
607: $select->where('ce.entity_id IN (?)', $categoryIds);
608: }
609: if ($productIds) {
610: $select->where('pw.product_id IN(?)', $productIds);
611: }
612:
613: $sql = $select->insertFromSelect($this->getMainTable());
614: $this->_getWriteAdapter()->query($sql);
615: return $this;
616: }
617:
618: 619: 620: 621: 622: 623:
624: protected function _refreshRootRelations($productIds)
625: {
626: $visibilityInfo = $this->_getVisibilityAttributeInfo();
627: $statusInfo = $this->_getStatusAttributeInfo();
628: $adapter = $this->_getWriteAdapter();
629: 630: 631:
632: $isParent = new Zend_Db_Expr('0');
633: $position = new Zend_Db_Expr('0');
634: $select = $this->_getReadAdapter()->select()
635: ->distinct(true)
636: ->from(array('pw' => $this->_productWebsiteTable), array())
637: ->joinInner(array('g' => $this->_groupTable), 'g.website_id=pw.website_id', array())
638: ->joinInner(array('s' => $this->_storeTable), 's.group_id=g.group_id', array())
639: ->joinInner(array('rc' => $this->_categoryTable), 'rc.entity_id=g.root_category_id',
640: array('entity_id'))
641: ->joinLeft(array('cp' => $this->_categoryProductTable), 'cp.product_id=pw.product_id',
642: array('pw.product_id', $position, $isParent, 's.store_id'))
643: ->joinLeft(
644: array('dv'=>$visibilityInfo['table']),
645: "dv.entity_id=pw.product_id AND dv.attribute_id={$visibilityInfo['id']} AND dv.store_id=0",
646: array())
647: ->joinLeft(
648: array('sv'=>$visibilityInfo['table']),
649: "sv.entity_id=pw.product_id AND sv.attribute_id={$visibilityInfo['id']} AND sv.store_id=s.store_id",
650: array('visibility' => $adapter->getCheckSql('sv.value_id IS NOT NULL',
651: $adapter->quoteIdentifier('sv.value'),
652: $adapter->quoteIdentifier('dv.value')
653: ))
654: )
655: ->joinLeft(
656: array('ds'=>$statusInfo['table']),
657: "ds.entity_id=pw.product_id AND ds.attribute_id={$statusInfo['id']} AND ds.store_id=0",
658: array())
659: ->joinLeft(
660: array('ss'=>$statusInfo['table']),
661: "ss.entity_id=pw.product_id AND ss.attribute_id={$statusInfo['id']} AND ss.store_id=s.store_id",
662: array())
663: 664: 665:
666: ->where('cp.product_id IS NULL')
667: ->where(
668: $adapter->getCheckSql('ss.value_id IS NOT NULL',
669: $adapter->quoteIdentifier('ss.value'),
670: $adapter->quoteIdentifier('ds.value')
671: ) . ' = ?', Mage_Catalog_Model_Product_Status::STATUS_ENABLED)
672: ->where('pw.product_id IN(?)', $productIds);
673:
674: $sql = $select->insertFromSelect($this->getMainTable());
675: $this->_getWriteAdapter()->query($sql);
676:
677: $select = $this->_getReadAdapter()->select()
678: ->from(array('pw' => $this->_productWebsiteTable), array())
679: ->joinInner(array('g' => $this->_groupTable), 'g.website_id = pw.website_id', array())
680: ->joinInner(array('s' => $this->_storeTable), 's.group_id = g.group_id', array())
681: ->joinLeft(
682: array('i' => $this->getMainTable()),
683: 'i.product_id = pw.product_id AND i.category_id = g.root_category_id', array())
684: ->joinLeft(
685: array('dv' => $visibilityInfo['table']),
686: "dv.entity_id = pw.product_id AND dv.attribute_id = {$visibilityInfo['id']} AND dv.store_id = 0",
687: array())
688: ->joinLeft(
689: array('sv' => $visibilityInfo['table']),
690: "sv.entity_id = pw.product_id AND sv.attribute_id = {$visibilityInfo['id']}"
691: . " AND sv.store_id = s.store_id",
692: array())
693: ->join(
694: array('ds' => $statusInfo['table']),
695: "ds.entity_id = pw.product_id AND ds.attribute_id = {$statusInfo['id']} AND ds.store_id = 0",
696: array())
697: ->joinLeft(
698: array('ss' => $statusInfo['table']),
699: "ss.entity_id = pw.product_id AND ss.attribute_id = {$statusInfo['id']} AND ss.store_id = s.store_id",
700: array())
701: ->where('i.product_id IS NULL')
702: ->where(
703: $adapter->getCheckSql('ss.value_id IS NOT NULL', 'ss.value', 'ds.value') . '=?',
704: Mage_Catalog_Model_Product_Status::STATUS_ENABLED)
705: ->where('pw.product_id IN(?)', $productIds)
706: ->columns(array(
707: 'category_id' => 'g.root_category_id',
708: 'product_id' => 'pw.product_id',
709: 'position' => $position,
710: 'is_parent' => new Zend_Db_Expr('1'),
711: 'store_id' => 's.store_id',
712: 'visibility' => $adapter->getCheckSql('sv.value_id IS NOT NULL', 'sv.value', 'dv.value'),
713: ));
714:
715: $sql = $select->insertFromSelect($this->getMainTable());
716: $this->_getWriteAdapter()->query($sql);
717:
718: return $this;
719: }
720:
721: 722: 723: 724: 725:
726: protected function _getAnchorAttributeInfo()
727: {
728: $isAnchorAttribute = Mage::getSingleton('eav/config')
729: ->getAttribute(Mage_Catalog_Model_Category::ENTITY, 'is_anchor');
730: $info = array(
731: 'id' => $isAnchorAttribute->getId() ,
732: 'table' => $isAnchorAttribute->getBackend()->getTable()
733: );
734: return $info;
735: }
736:
737: 738: 739: 740: 741:
742: protected function _getVisibilityAttributeInfo()
743: {
744: $visibilityAttribute = Mage::getSingleton('eav/config')
745: ->getAttribute(Mage_Catalog_Model_Product::ENTITY, 'visibility');
746: $info = array(
747: 'id' => $visibilityAttribute->getId() ,
748: 'table' => $visibilityAttribute->getBackend()->getTable()
749: );
750: return $info;
751: }
752:
753: 754: 755: 756: 757:
758: protected function _getStatusAttributeInfo()
759: {
760: $statusAttribute = Mage::getSingleton('eav/config')->getAttribute(Mage_Catalog_Model_Product::ENTITY, 'status');
761: $info = array(
762: 'id' => $statusAttribute->getId() ,
763: 'table' => $statusAttribute->getBackend()->getTable()
764: );
765: return $info;
766: }
767:
768: 769: 770: 771: 772:
773: public function reindexAll()
774: {
775: $this->useIdxTable(true);
776: $this->beginTransaction();
777: try {
778: $this->clearTemporaryIndexTable();
779: $idxTable = $this->getIdxTable();
780: $idxAdapter = $this->_getIndexAdapter();
781: $stores = $this->_getStoresInfo();
782: 783: 784:
785: foreach ($stores as $storeData) {
786: $storeId = $storeData['store_id'];
787: $websiteId = $storeData['website_id'];
788: $rootPath = $storeData['root_path'];
789: $rootId = $storeData['root_id'];
790: 791: 792:
793: $enabledTable = $this->_prepareEnabledProductsVisibility($websiteId, $storeId);
794: 795: 796:
797: $anchorTable = $this->_prepareAnchorCategories($storeId, $rootPath);
798: 799: 800:
801: $select = $idxAdapter->select();
802:
803: $select->from(
804: array('cp' => $this->_categoryProductTable),
805: array('category_id', 'product_id', 'position', 'is_parent' => new Zend_Db_Expr('1'),
806: 'store_id' => new Zend_Db_Expr($storeId))
807: )
808: ->joinInner(array('pv' => $enabledTable), 'pv.product_id=cp.product_id', array('visibility'))
809: ->joinLeft(array('ac' => $anchorTable), 'ac.category_id=cp.category_id', array())
810: ->where('ac.category_id IS NULL');
811:
812: $query = $select->insertFromSelect(
813: $idxTable,
814: array('category_id', 'product_id', 'position', 'is_parent', 'store_id', 'visibility'),
815: false
816: );
817: $idxAdapter->query($query);
818:
819: 820: 821:
822:
823: $select = $idxAdapter->select();
824: $select->from(
825: array('pv' => $enabledTable),
826: array(new Zend_Db_Expr($rootId), 'product_id', new Zend_Db_Expr('0'), new Zend_Db_Expr('1'),
827: new Zend_Db_Expr($storeId), 'visibility')
828: )
829: ->joinLeft(array('cp' => $this->_categoryProductTable), 'pv.product_id=cp.product_id', array())
830: ->where('cp.product_id IS NULL');
831:
832: $query = $select->insertFromSelect(
833: $idxTable,
834: array('category_id', 'product_id', 'position', 'is_parent', 'store_id', 'visibility'),
835: false
836: );
837: $idxAdapter->query($query);
838:
839: 840: 841:
842: $anchorProductsTable = $this->_getAnchorCategoriesProductsTemporaryTable();
843: $idxAdapter->delete($anchorProductsTable);
844:
845: $position = 'MIN('.
846: $idxAdapter->getCheckSql(
847: 'ca.category_id = ce.entity_id',
848: $idxAdapter->quoteIdentifier('cp.position'),
849: '('.$idxAdapter->quoteIdentifier('ce.position').' + 1) * '
850: .'('.$idxAdapter->quoteIdentifier('ce.level').' + 1 * 10000)'
851: .' + '.$idxAdapter->quoteIdentifier('cp.position')
852: )
853: .')';
854:
855:
856: $select = $idxAdapter->select()
857: ->useStraightJoin(true)
858: ->distinct(true)
859: ->from(array('ca' => $anchorTable), array('category_id'))
860: ->joinInner(
861: array('ce' => $this->_categoryTable),
862: $idxAdapter->quoteIdentifier('ce.path') . ' LIKE ' .
863: $idxAdapter->quoteIdentifier('ca.path') . ' OR ce.entity_id = ca.category_id',
864: array()
865: )
866: ->joinInner(
867: array('cp' => $this->_categoryProductTable),
868: 'cp.category_id = ce.entity_id',
869: array('product_id')
870: )
871: ->joinInner(
872: array('pv' => $enabledTable),
873: 'pv.product_id = cp.product_id',
874: array('position' => $position)
875: )
876: ->group(array('ca.category_id', 'cp.product_id'));
877: $query = $select->insertFromSelect($anchorProductsTable,
878: array('category_id', 'product_id', 'position'), false);
879: $idxAdapter->query($query);
880:
881: 882: 883:
884: $select = $idxAdapter->select()
885: ->from(
886: array('ap' => $anchorProductsTable),
887: array('category_id', 'product_id',
888: 'position',
889: 'is_parent' => $idxAdapter->getCheckSql('cp.product_id > 0', 1, 0),
890: 'store_id' => new Zend_Db_Expr($storeId))
891: )
892: ->joinLeft(
893: array('cp' => $this->_categoryProductTable),
894: 'cp.category_id=ap.category_id AND cp.product_id=ap.product_id',
895: array()
896: )
897: ->joinInner(array('pv' => $enabledTable), 'pv.product_id = ap.product_id', array('visibility'));
898:
899: $query = $select->insertFromSelect(
900: $idxTable,
901: array('category_id', 'product_id', 'position', 'is_parent', 'store_id', 'visibility'),
902: false
903: );
904: $idxAdapter->query($query);
905:
906: $select = $idxAdapter->select()
907: ->from(array('e' => $this->getTable('catalog/product')), null)
908: ->join(
909: array('ei' => $enabledTable),
910: 'ei.product_id = e.entity_id',
911: array())
912: ->joinLeft(
913: array('i' => $idxTable),
914: 'i.product_id = e.entity_id AND i.category_id = :category_id AND i.store_id = :store_id',
915: array())
916: ->where('i.product_id IS NULL')
917: ->columns(array(
918: 'category_id' => new Zend_Db_Expr($rootId),
919: 'product_id' => 'e.entity_id',
920: 'position' => new Zend_Db_Expr('0'),
921: 'is_parent' => new Zend_Db_Expr('1'),
922: 'store_id' => new Zend_Db_Expr($storeId),
923: 'visibility' => 'ei.visibility'
924: ));
925:
926: $query = $select->insertFromSelect(
927: $idxTable,
928: array('category_id', 'product_id', 'position', 'is_parent', 'store_id', 'visibility'),
929: false
930: );
931:
932: $idxAdapter->query($query, array('store_id' => $storeId, 'category_id' => $rootId));
933: }
934:
935: $this->syncData();
936:
937: 938: 939:
940: $this->clearTemporaryIndexTable();
941: $idxAdapter->delete($enabledTable);
942: $idxAdapter->delete($anchorTable);
943: $idxAdapter->delete($anchorProductsTable);
944: $this->commit();
945: } catch (Exception $e) {
946: $this->rollBack();
947: throw $e;
948: }
949: return $this;
950: }
951:
952:
953: 954: 955: 956: 957: 958: 959:
960: protected function _prepareEnabledProductsVisibility($websiteId, $storeId)
961: {
962: $statusAttribute = Mage::getSingleton('eav/config')->getAttribute(Mage_Catalog_Model_Product::ENTITY, 'status');
963: $visibilityAttribute = Mage::getSingleton('eav/config')
964: ->getAttribute(Mage_Catalog_Model_Product::ENTITY, 'visibility');
965: $statusAttributeId = $statusAttribute->getId();
966: $visibilityAttributeId = $visibilityAttribute->getId();
967: $statusTable = $statusAttribute->getBackend()->getTable();
968: $visibilityTable = $visibilityAttribute->getBackend()->getTable();
969:
970: 971: 972:
973: $tmpTable = $this->_getEnabledProductsTemporaryTable();
974: $this->_getIndexAdapter()->delete($tmpTable);
975:
976: $adapter = $this->_getIndexAdapter();
977: $visibilityExpr = $adapter->getCheckSql('pvs.value_id>0', $adapter->quoteIdentifier('pvs.value'),
978: $adapter->quoteIdentifier('pvd.value'));
979: $select = $adapter->select()
980: ->from(array('pw' => $this->_productWebsiteTable), array('product_id', 'visibility' => $visibilityExpr))
981: ->joinLeft(
982: array('pvd' => $visibilityTable),
983: $adapter->quoteInto('pvd.entity_id=pw.product_id AND pvd.attribute_id=? AND pvd.store_id=0',
984: $visibilityAttributeId),
985: array())
986: ->joinLeft(
987: array('pvs' => $visibilityTable),
988: $adapter->quoteInto('pvs.entity_id=pw.product_id AND pvs.attribute_id=? AND ', $visibilityAttributeId)
989: . $adapter->quoteInto('pvs.store_id=?', $storeId),
990: array())
991: ->joinLeft(
992: array('psd' => $statusTable),
993: $adapter->quoteInto('psd.entity_id=pw.product_id AND psd.attribute_id=? AND psd.store_id=0',
994: $statusAttributeId),
995: array())
996: ->joinLeft(
997: array('pss' => $statusTable),
998: $adapter->quoteInto('pss.entity_id=pw.product_id AND pss.attribute_id=? AND ', $statusAttributeId)
999: . $adapter->quoteInto('pss.store_id=?', $storeId),
1000: array())
1001: ->where('pw.website_id=?',$websiteId)
1002: ->where($adapter->getCheckSql('pss.value_id > 0',
1003: $adapter->quoteIdentifier('pss.value'),
1004: $adapter->quoteIdentifier('psd.value')) . ' = ?', Mage_Catalog_Model_Product_Status::STATUS_ENABLED);
1005:
1006: $query = $select->insertFromSelect($tmpTable, array('product_id' , 'visibility'), false);
1007: $adapter->query($query);
1008: return $tmpTable;
1009: }
1010:
1011: 1012: 1013: 1014: 1015:
1016: protected function _getEnabledProductsTemporaryTable()
1017: {
1018: if ($this->useIdxTable()) {
1019: return $this->getTable('catalog/category_product_enabled_indexer_idx');
1020: }
1021: return $this->getTable('catalog/category_product_enabled_indexer_tmp');
1022: }
1023:
1024: 1025: 1026: 1027: 1028:
1029: protected function _getStoresInfo()
1030: {
1031: if (is_null($this->_storesInfo)) {
1032: $adapter = $this->_getReadAdapter();
1033: $select = $adapter->select()
1034: ->from(array('s' => $this->getTable('core/store')), array('store_id', 'website_id'))
1035: ->join(
1036: array('sg' => $this->getTable('core/store_group')),
1037: 'sg.group_id = s.group_id',
1038: array())
1039: ->join(
1040: array('c' => $this->getTable('catalog/category')),
1041: 'c.entity_id = sg.root_category_id',
1042: array(
1043: 'root_path' => 'path',
1044: 'root_id' => 'entity_id'
1045: )
1046: );
1047: $this->_storesInfo = $adapter->fetchAll($select);
1048: }
1049:
1050: return $this->_storesInfo;
1051: }
1052:
1053:
1054: 1055: 1056: 1057: 1058:
1059: protected function _prepareAnchorCategories($storeId, $rootPath)
1060: {
1061: $isAnchorAttribute = Mage::getSingleton('eav/config')
1062: ->getAttribute(Mage_Catalog_Model_Category::ENTITY, 'is_anchor');
1063: $anchorAttributeId = $isAnchorAttribute->getId();
1064: $anchorTable = $isAnchorAttribute->getBackend()->getTable();
1065: $adapter = $this->_getIndexAdapter();
1066: $tmpTable = $this->_getAnchorCategoriesTemporaryTable();
1067: $adapter->delete($tmpTable);
1068:
1069: $anchorExpr = $adapter->getCheckSql('cas.value_id>0', $adapter->quoteIdentifier('cas.value'),
1070: $adapter->quoteIdentifier('cad.value'));
1071: $pathConcat = $adapter->getConcatSql(array($adapter->quoteIdentifier('ce.path'), $adapter->quote('/%')));
1072: $select = $adapter->select()
1073: ->from(
1074: array('ce' => $this->_categoryTable),
1075: array('category_id' => 'ce.entity_id', 'path' => $pathConcat))
1076: ->joinLeft(
1077: array('cad' => $anchorTable),
1078: $adapter->quoteInto("cad.entity_id=ce.entity_id AND cad.attribute_id=? AND cad.store_id=0",
1079: $anchorAttributeId),
1080: array())
1081: ->joinLeft(
1082: array('cas' => $anchorTable),
1083: $adapter->quoteInto("cas.entity_id=ce.entity_id AND cas.attribute_id=? AND ", $anchorAttributeId)
1084: . $adapter->quoteInto('cas.store_id=?', $storeId),
1085: array())
1086: ->where("{$anchorExpr} = 1 AND {$adapter->quoteIdentifier('ce.path')} LIKE ?", $rootPath . '%')
1087: ->orWhere('ce.path = ?', $rootPath);
1088:
1089: $query = $select->insertFromSelect($tmpTable, array('category_id' , 'path'), false);
1090: $adapter->query($query);
1091: return $tmpTable;
1092: }
1093:
1094: 1095: 1096: 1097: 1098:
1099: protected function _getAnchorCategoriesTemporaryTable()
1100: {
1101: if ($this->useIdxTable()) {
1102: return $this->getTable('catalog/category_anchor_indexer_idx');
1103: }
1104: return $this->getTable('catalog/category_anchor_indexer_tmp');
1105: }
1106:
1107: 1108: 1109: 1110: 1111:
1112: protected function _getAnchorCategoriesProductsTemporaryTable()
1113: {
1114: if ($this->useIdxTable()) {
1115: return $this->getTable('catalog/category_anchor_products_indexer_idx');
1116: }
1117: return $this->getTable('catalog/category_anchor_products_indexer_tmp');
1118: }
1119:
1120: 1121: 1122: 1123: 1124: 1125:
1126: public function getIdxTable($table = null)
1127: {
1128: if ($this->useIdxTable()) {
1129: return $this->getTable('catalog/category_product_indexer_idx');
1130: }
1131: return $this->getTable('catalog/category_product_indexer_tmp');
1132: }
1133: }
1134: