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_Product_Flat_Indexer extends Mage_Index_Model_Resource_Abstract
36: {
37: const XML_NODE_MAX_INDEX_COUNT = 'global/catalog/product/flat/max_index_count';
38: const XML_NODE_ATTRIBUTE_NODES = 'global/catalog/product/flat/attribute_nodes';
39:
40: 41: 42: 43: 44:
45: protected $_attributeCodes;
46:
47: 48: 49: 50: 51:
52: protected $_attributes;
53:
54: 55: 56: 57: 58:
59: protected $_systemAttributes = array('status', 'required_options', 'tax_class_id', 'weight');
60:
61: 62: 63: 64: 65:
66: protected $_entityTypeId;
67:
68: 69: 70: 71: 72:
73: protected $_columns;
74:
75: 76: 77: 78: 79:
80: protected $_indexes;
81:
82: 83: 84: 85: 86:
87: protected $_productTypes;
88:
89: 90: 91: 92: 93:
94: protected $_existsFlatTables = array();
95:
96: 97: 98: 99: 100:
101: protected $_preparedFlatTables = array();
102:
103: 104: 105: 106:
107: protected function _construct()
108: {
109: $this->_init('catalog/product', 'entity_id');
110: }
111:
112: 113: 114: 115: 116: 117:
118: public function rebuild($store = null)
119: {
120: if ($store === null) {
121: foreach (Mage::app()->getStores() as $store) {
122: $this->rebuild($store->getId());
123: }
124: $flag = $this->getFlatHelper()->getFlag();
125: $flag->setIsBuild(true)->save();
126:
127: return $this;
128: }
129:
130: $storeId = (int)Mage::app()->getStore($store)->getId();
131:
132: $this->prepareFlatTable($storeId);
133: $this->cleanNonWebsiteProducts($storeId);
134: $this->updateStaticAttributes($storeId);
135: $this->updateEavAttributes($storeId);
136: $this->updateEventAttributes($storeId);
137: $this->updateRelationProducts($storeId);
138: $this->cleanRelationProducts($storeId);
139:
140: return $this;
141: }
142:
143: 144: 145: 146: 147:
148: public function getFlatHelper()
149: {
150: return Mage::helper('catalog/product_flat');
151: }
152:
153: 154: 155: 156: 157:
158: public function getAttributeCodes()
159: {
160: if ($this->_attributeCodes === null) {
161: $adapter = $this->_getReadAdapter();
162: $this->_attributeCodes = array();
163:
164: $attributeNodes = Mage::getConfig()
165: ->getNode(self::XML_NODE_ATTRIBUTE_NODES)
166: ->children();
167: foreach ($attributeNodes as $node) {
168: $attributes = Mage::getConfig()->getNode((string)$node)->asArray();
169: $attributes = array_keys($attributes);
170: $this->_systemAttributes = array_unique(array_merge($attributes, $this->_systemAttributes));
171: }
172:
173: $bind = array(
174: 'backend_type' => Mage_Eav_Model_Entity_Attribute_Abstract::TYPE_STATIC,
175: 'entity_type_id' => $this->getEntityTypeId()
176: );
177:
178: $select = $adapter->select()
179: ->from(array('main_table' => $this->getTable('eav/attribute')))
180: ->join(
181: array('additional_table' => $this->getTable('catalog/eav_attribute')),
182: 'additional_table.attribute_id = main_table.attribute_id'
183: )
184: ->where('main_table.entity_type_id = :entity_type_id');
185: $whereCondition = array(
186: 'main_table.backend_type = :backend_type',
187: $adapter->quoteInto('additional_table.is_used_for_promo_rules = ?', 1),
188: $adapter->quoteInto('additional_table.used_in_product_listing = ?', 1),
189: $adapter->quoteInto('additional_table.used_for_sort_by = ?', 1),
190: $adapter->quoteInto('main_table.attribute_code IN(?)', $this->_systemAttributes)
191: );
192: if ($this->getFlatHelper()->isAddFilterableAttributes()) {
193: $whereCondition[] = $adapter->quoteInto('additional_table.is_filterable > ?', 0);
194: }
195:
196: $select->where(implode(' OR ', $whereCondition));
197: $attributesData = $adapter->fetchAll($select, $bind);
198: Mage::getSingleton('eav/config')
199: ->importAttributesData($this->getEntityType(), $attributesData);
200:
201: foreach ($attributesData as $data) {
202: $this->_attributeCodes[$data['attribute_id']] = $data['attribute_code'];
203: }
204: unset($attributesData);
205: }
206:
207: return $this->_attributeCodes;
208: }
209:
210: 211: 212: 213: 214:
215: public function getEntityType()
216: {
217: return Mage_Catalog_Model_Product::ENTITY;
218: }
219:
220: 221: 222: 223: 224:
225: public function getEntityTypeId()
226: {
227: if ($this->_entityTypeId === null) {
228: $this->_entityTypeId = Mage::getResourceModel('catalog/config')
229: ->getEntityTypeId();
230: }
231: return $this->_entityTypeId;
232: }
233:
234: 235: 236: 237: 238:
239: public function getAttributes()
240: {
241: if ($this->_attributes === null) {
242: $this->_attributes = array();
243: $attributeCodes = $this->getAttributeCodes();
244: $entity = Mage::getSingleton('eav/config')
245: ->getEntityType($this->getEntityType())
246: ->getEntity();
247:
248: foreach ($attributeCodes as $attributeCode) {
249: $attribute = Mage::getSingleton('eav/config')
250: ->getAttribute($this->getEntityType(), $attributeCode)
251: ->setEntity($entity);
252: try {
253:
254:
255: $attribute->usesSource() && $attribute->getSource();
256: $attribute->getBackend();
257: $this->_attributes[$attributeCode] = $attribute;
258: } catch (Exception $e) {
259: Mage::logException($e);
260: }
261: }
262: }
263:
264: return $this->_attributes;
265: }
266:
267: 268: 269: 270: 271: 272: 273:
274: public function getAttribute($attributeCode)
275: {
276: $attributes = $this->getAttributes();
277: if (!isset($attributes[$attributeCode])) {
278: $attribute = Mage::getModel('catalog/resource_eav_attribute')
279: ->loadByCode($this->getEntityTypeId(), $attributeCode);
280: if (!$attribute->getId()) {
281: Mage::throwException(Mage::helper('catalog')->__('Invalid attribute %s', $attributeCode));
282: }
283: $entity = Mage::getSingleton('eav/config')
284: ->getEntityType($this->getEntityType())
285: ->getEntity();
286: $attribute->setEntity($entity);
287:
288: return $attribute;
289: }
290:
291: return $attributes[$attributeCode];
292: }
293:
294: 295: 296: 297: 298: 299:
300: public function getFlatTableName($storeId)
301: {
302: return sprintf('%s_%s', $this->getTable('catalog/product_flat'), $storeId);
303: }
304:
305: 306: 307: 308: 309:
310: protected function _getFlatColumnsOldDefinition()
311: {
312: $columns = array();
313: $columns['entity_id'] = array(
314: 'type' => 'int(10)',
315: 'unsigned' => true,
316: 'is_null' => false,
317: 'default' => null,
318: 'extra' => null
319: );
320: if ($this->getFlatHelper()->isAddChildData()) {
321: $columns['child_id'] = array(
322: 'type' => 'int(10)',
323: 'unsigned' => true,
324: 'is_null' => true,
325: 'default' => null,
326: 'extra' => null
327: );
328: $columns['is_child'] = array(
329: 'type' => 'tinyint(1)',
330: 'unsigned' => true,
331: 'is_null' => false,
332: 'default' => 0,
333: 'extra' => null
334: );
335: }
336: $columns['attribute_set_id'] = array(
337: 'type' => 'smallint(5)',
338: 'unsigned' => true,
339: 'is_null' => false,
340: 'default' => 0,
341: 'extra' => null
342: );
343: $columns['type_id'] = array(
344: 'type' => 'varchar(32)',
345: 'unsigned' => false,
346: 'is_null' => false,
347: 'default' => Mage_Catalog_Model_Product_Type::TYPE_SIMPLE,
348: 'extra' => null
349: );
350:
351: return $columns;
352: }
353:
354: 355: 356: 357: 358:
359: protected function _getFlatColumnsDdlDefinition()
360: {
361: $columns = array();
362: $columns['entity_id'] = array(
363: 'type' => Varien_Db_Ddl_Table::TYPE_INTEGER,
364: 'length' => null,
365: 'unsigned' => true,
366: 'nullable' => false,
367: 'default' => false,
368: 'primary' => true,
369: 'comment' => 'Entity Id'
370: );
371: if ($this->getFlatHelper()->isAddChildData()) {
372: $columns['child_id'] = array(
373: 'type' => Varien_Db_Ddl_Table::TYPE_INTEGER,
374: 'length' => null,
375: 'unsigned' => true,
376: 'nullable' => true,
377: 'default' => null,
378: 'primary' => true,
379: 'comment' => 'Child Id'
380: );
381: $columns['is_child'] = array(
382: 'type' => Varien_Db_Ddl_Table::TYPE_SMALLINT,
383: 'length' => 1,
384: 'unsigned' => true,
385: 'nullable' => false,
386: 'default' => '0',
387: 'comment' => 'Checks If Entity Is Child'
388: );
389: }
390: $columns['attribute_set_id'] = array(
391: 'type' => Varien_Db_Ddl_Table::TYPE_SMALLINT,
392: 'length' => 5,
393: 'unsigned' => true,
394: 'nullable' => false,
395: 'default' => '0',
396: 'comment' => 'Attribute Set Id'
397: );
398: $columns['type_id'] = array(
399: 'type' => Varien_Db_Ddl_Table::TYPE_TEXT,
400: 'length' => 32,
401: 'unsigned' => false,
402: 'nullable' => false,
403: 'default' => Mage_Catalog_Model_Product_Type::TYPE_SIMPLE,
404: 'comment' => 'Type Id'
405: );
406:
407: return $columns;
408: }
409:
410: 411: 412: 413: 414:
415: public function getFlatColumns()
416: {
417: if ($this->_columns === null) {
418: if (Mage::helper('core')->useDbCompatibleMode()) {
419: $this->_columns = $this->_getFlatColumnsOldDefinition();
420: } else {
421: $this->_columns = $this->_getFlatColumnsDdlDefinition();
422: }
423:
424: foreach ($this->getAttributes() as $attribute) {
425:
426: $columns = $attribute
427: ->setFlatAddFilterableAttributes($this->getFlatHelper()->isAddFilterableAttributes())
428: ->setFlatAddChildData($this->getFlatHelper()->isAddChildData())
429: ->getFlatColumns();
430: if ($columns !== null) {
431: $this->_columns = array_merge($this->_columns, $columns);
432: }
433: }
434:
435: $columnsObject = new Varien_Object();
436: $columnsObject->setColumns($this->_columns);
437: Mage::dispatchEvent('catalog_product_flat_prepare_columns',
438: array('columns' => $columnsObject)
439: );
440: $this->_columns = $columnsObject->getColumns();
441: }
442:
443: return $this->_columns;
444: }
445:
446: 447: 448: 449: 450:
451: public function getFlatIndexes()
452: {
453: if ($this->_indexes === null) {
454: $this->_indexes = array();
455:
456: if ($this->getFlatHelper()->isAddChildData()) {
457: $this->_indexes['PRIMARY'] = array(
458: 'type' => Varien_Db_Adapter_Interface::INDEX_TYPE_PRIMARY,
459: 'fields' => array('entity_id', 'child_id')
460: );
461: $this->_indexes['IDX_CHILD'] = array(
462: 'type' => Varien_Db_Adapter_Interface::INDEX_TYPE_INDEX,
463: 'fields' => array('child_id')
464: );
465: $this->_indexes['IDX_IS_CHILD'] = array(
466: 'type' => Varien_Db_Adapter_Interface::INDEX_TYPE_INDEX,
467: 'fields' => array('entity_id', 'is_child')
468: );
469: } else {
470: $this->_indexes['PRIMARY'] = array(
471: 'type' => Varien_Db_Adapter_Interface::INDEX_TYPE_PRIMARY,
472: 'fields' => array('entity_id')
473: );
474: }
475: $this->_indexes['IDX_TYPE_ID'] = array(
476: 'type' => Varien_Db_Adapter_Interface::INDEX_TYPE_INDEX,
477: 'fields' => array('type_id')
478: );
479: $this->_indexes['IDX_ATTRIBUTE_SET'] = array(
480: 'type' => Varien_Db_Adapter_Interface::INDEX_TYPE_INDEX,
481: 'fields' => array('attribute_set_id')
482: );
483:
484: foreach ($this->getAttributes() as $attribute) {
485:
486: $indexes = $attribute
487: ->setFlatAddFilterableAttributes($this->getFlatHelper()->isAddFilterableAttributes())
488: ->setFlatAddChildData($this->getFlatHelper()->isAddChildData())
489: ->getFlatIndexes();
490: if ($indexes !== null) {
491: $this->_indexes = array_merge($this->_indexes, $indexes);
492: }
493: }
494:
495: $indexesObject = new Varien_Object();
496: $indexesObject->setIndexes($this->_indexes);
497: Mage::dispatchEvent('catalog_product_flat_prepare_indexes', array(
498: 'indexes' => $indexesObject
499: ));
500: $this->_indexes = $indexesObject->getIndexes();
501: }
502:
503: return $this->_indexes;
504: }
505:
506: 507: 508: 509: 510: 511: 512: 513:
514: protected function _compareColumnProperties($column, $describe)
515: {
516: return Mage::getResourceHelper('catalog')->compareIndexColumnProperties($column, $describe);
517: }
518:
519: 520: 521: 522: 523: 524: 525: 526: 527: 528:
529: protected function _sqlColunmDefinition($fieldName, $fieldProp)
530: {
531: $fieldNameQuote = $this->_getWriteAdapter()->quoteIdentifier($fieldName);
532:
533: 534: 535: 536:
537: if ($fieldProp['is_null'] === false && $fieldProp['default'] === null) {
538: $defaultValue = '';
539: } else {
540: $defaultValue = $fieldProp['default'] === null ? ' DEFAULT NULL' : $this->_getReadAdapter()
541: ->quoteInto(' DEFAULT ?', $fieldProp['default']);
542: }
543:
544: return "{$fieldNameQuote} {$fieldProp['type']}"
545: . ($fieldProp['unsigned'] ? ' UNSIGNED' : '')
546: . ($fieldProp['extra'] ? ' ' . $fieldProp['extra'] : '')
547: . ($fieldProp['is_null'] === false ? ' NOT NULL' : '')
548: . $defaultValue;
549: }
550:
551: 552: 553: 554: 555: 556: 557: 558: 559: 560:
561: protected function _sqlIndexDefinition($indexName, $indexProp)
562: {
563: $fields = $indexProp['fields'];
564: if (is_array($fields)) {
565: $fieldSql = array();
566: foreach ($fields as $field) {
567: $fieldSql[] = $this->_getReadAdapter()->quoteIdentifier($field);
568: }
569: $fieldSql = join(',', $fieldSql);
570: }
571: else {
572: $fieldSql = $this->_getReadAdapter()->quoteIdentifier($fields);
573: }
574:
575: $indexNameQuote = $this->_getReadAdapter()->quoteIdentifier($indexName);
576: switch (strtolower($indexProp['type'])) {
577: case 'primary':
578: $condition = 'PRIMARY KEY';
579: break;
580: case 'unique':
581: $condition = 'UNIQUE ' . $indexNameQuote;
582: break;
583: case 'fulltext':
584: $condition = 'FULLTEXT ' . $indexNameQuote;
585: break;
586: default:
587: $condition = 'INDEX ' . $indexNameQuote;
588: break;
589: }
590:
591: return sprintf('%s (%s)', $condition, $fieldSql);
592: }
593:
594: 595: 596: 597: 598: 599: 600: 601: 602:
603: public function getFkName($priTableName, $priColumnName, $refTableName, $refColumnName)
604: {
605: return Mage::getSingleton('core/resource')
606: ->getFkName($priTableName, $priColumnName, $refTableName, $refColumnName);
607: }
608:
609: 610: 611: 612: 613: 614: 615:
616: public function prepareFlatTable($storeId)
617: {
618: if (isset($this->_preparedFlatTables[$storeId])) {
619: return $this;
620: }
621: $adapter = $this->_getWriteAdapter();
622: $tableName = $this->getFlatTableName($storeId);
623:
624:
625: $columns = $this->getFlatColumns();
626: if (Mage::helper('core')->useDbCompatibleMode()) {
627:
628: foreach ($columns as $key => $column) {
629: $columns[$key] = Mage::getResourceHelper('core')->convertOldColumnDefinition($column);
630: }
631: }
632:
633:
634: $indexesNeed = $this->getFlatIndexes();
635:
636: $maxIndex = Mage::getConfig()->getNode(self::XML_NODE_MAX_INDEX_COUNT);
637: if (count($indexesNeed) > $maxIndex) {
638: Mage::throwException(Mage::helper('catalog')->__("The Flat Catalog module has a limit of %2\$d filterable and/or sortable attributes. Currently there are %1\$d of them. Please reduce the number of filterable/sortable attributes in order to use this module", count($indexesNeed), $maxIndex));
639: }
640:
641:
642: $indexKeys = array();
643: $indexProps = array_values($indexesNeed);
644: $upperPrimaryKey = strtoupper(Varien_Db_Adapter_Interface::INDEX_TYPE_PRIMARY);
645: foreach ($indexProps as $i => $indexProp) {
646: $indexName = $adapter->getIndexName($tableName, $indexProp['fields'], $indexProp['type']);
647: $indexProp['type'] = strtoupper($indexProp['type']);
648: if ($indexProp['type'] == $upperPrimaryKey) {
649: $indexKey = $upperPrimaryKey;
650: } else {
651: $indexKey = $indexName;
652: }
653:
654: $indexProps[$i] = array(
655: 'KEY_NAME' => $indexName,
656: 'COLUMNS_LIST' => $indexProp['fields'],
657: 'INDEX_TYPE' => strtolower($indexProp['type'])
658: );
659: $indexKeys[$i] = $indexKey;
660: }
661: $indexesNeed = array_combine($indexKeys, $indexProps);
662:
663:
664: $foreignEntityKey = $this->getFkName($tableName, 'entity_id', 'catalog/product', 'entity_id');
665: $foreignChildKey = $this->getFkName($tableName, 'child_id', 'catalog/product', 'entity_id');
666:
667:
668: if (!$this->_isFlatTableExists($storeId)) {
669:
670: $table = $adapter->newTable($tableName);
671: foreach ($columns as $fieldName => $fieldProp) {
672: $table->addColumn(
673: $fieldName,
674: $fieldProp['type'],
675: isset($fieldProp['length']) ? $fieldProp['length'] : null,
676: array(
677: 'nullable' => isset($fieldProp['nullable']) ? (bool)$fieldProp['nullable'] : false,
678: 'unsigned' => isset($fieldProp['unsigned']) ? (bool)$fieldProp['unsigned'] : false,
679: 'default' => isset($fieldProp['default']) ? $fieldProp['default'] : false,
680: 'primary' => false,
681: ),
682: isset($fieldProp['comment']) ? $fieldProp['comment'] : $fieldName
683: );
684: }
685:
686: foreach ($indexesNeed as $indexProp) {
687: $table->addIndex($indexProp['KEY_NAME'], $indexProp['COLUMNS_LIST'],
688: array('type' => $indexProp['INDEX_TYPE']));
689: }
690:
691: $table->addForeignKey($foreignEntityKey,
692: 'entity_id', $this->getTable('catalog/product'), 'entity_id',
693: Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_CASCADE);
694:
695: if ($this->getFlatHelper()->isAddChildData()) {
696: $table->addForeignKey($foreignChildKey,
697: 'child_id', $this->getTable('catalog/product'), 'entity_id',
698: Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_CASCADE);
699: }
700: $table->setComment("Catalog Product Flat (Store {$storeId})");
701:
702: $adapter->createTable($table);
703:
704: $this->_existsFlatTables[$storeId] = true;
705: } else {
706: $adapter->resetDdlCache($tableName);
707:
708:
709: $describe = $adapter->describeTable($tableName);
710: $addColumns = array_diff_key($columns, $describe);
711: $dropColumns = array_diff_key($describe, $columns);
712: $modifyColumns = array();
713: foreach ($columns as $field => $fieldProp) {
714: if (isset($describe[$field]) && !$this->_compareColumnProperties($fieldProp, $describe[$field])) {
715: $modifyColumns[$field] = $fieldProp;
716: }
717: }
718:
719:
720: $addIndexes = array();
721: $dropIndexes = array();
722: $indexesNow = $adapter->getIndexList($tableName);
723: $newIndexes = $indexesNeed;
724: foreach ($indexesNow as $key => $indexNow) {
725: if (isset($indexesNeed[$key])) {
726: $indexNeed = $indexesNeed[$key];
727: if (($indexNeed['INDEX_TYPE'] != $indexNow['INDEX_TYPE'])
728: || ($indexNeed['COLUMNS_LIST'] != $indexNow['COLUMNS_LIST'])) {
729: $dropIndexes[$key] = $indexNow;
730: $addIndexes[$key] = $indexNeed;
731: }
732: unset($newIndexes[$key]);
733: } else {
734: $dropIndexes[$key] = $indexNow;
735: }
736: }
737: $addIndexes = $addIndexes + $newIndexes;
738:
739:
740: $addConstraints = array();
741: $addConstraints[$foreignEntityKey] = array(
742: 'table_index' => 'entity_id',
743: 'ref_table' => $this->getTable('catalog/product'),
744: 'ref_index' => 'entity_id',
745: 'on_update' => Varien_Db_Ddl_Table::ACTION_CASCADE,
746: 'on_delete' => Varien_Db_Ddl_Table::ACTION_CASCADE
747: );
748:
749:
750: $isAddChildData = $this->getFlatHelper()->isAddChildData();
751: if (!$isAddChildData && isset($describe['is_child'])) {
752: $adapter->delete($tableName, array('is_child = ?' => 1));
753: $adapter->dropForeignKey($tableName, $foreignChildKey);
754: }
755: if ($isAddChildData && !isset($describe['is_child'])) {
756: $adapter->delete($tableName);
757: $dropIndexes['PRIMARY'] = $indexesNow['PRIMARY'];
758: $addIndexes['PRIMARY'] = $indexesNeed['PRIMARY'];
759:
760: $addConstraints[$foreignChildKey] = array(
761: 'table_index' => 'child_id',
762: 'ref_table' => $this->getTable('catalog/product'),
763: 'ref_index' => 'entity_id',
764: 'on_update' => Varien_Db_Ddl_Table::ACTION_CASCADE,
765: 'on_delete' => Varien_Db_Ddl_Table::ACTION_CASCADE
766: );
767: }
768:
769:
770: foreach (array_keys($adapter->getForeignKeys($tableName)) as $constraintName) {
771: $adapter->dropForeignKey($tableName, $constraintName);
772: }
773:
774:
775: foreach ($dropIndexes as $indexProp) {
776: $adapter->dropIndex($tableName, $indexProp['KEY_NAME']);
777: }
778:
779:
780: foreach (array_keys($dropColumns) as $columnName) {
781: $adapter->dropColumn($tableName, $columnName);
782: }
783:
784:
785: foreach ($modifyColumns as $columnName => $columnProp) {
786: $columnProp = array_change_key_case($columnProp, CASE_UPPER);
787: if (!isset($columnProp['COMMENT'])) {
788: $columnProp['COMMENT'] = ucwords(str_replace('_', ' ', $columnName));
789: }
790: $adapter->changeColumn($tableName, $columnName, $columnName, $columnProp);
791: }
792:
793:
794: foreach ($addColumns as $columnName => $columnProp) {
795: $columnProp = array_change_key_case($columnProp, CASE_UPPER);
796: if (!isset($columnProp['COMMENT'])) {
797: $columnProp['COMMENT'] = ucwords(str_replace('_', ' ', $columnName));
798: }
799: $adapter->addColumn($tableName, $columnName, $columnProp);
800: }
801:
802:
803: foreach ($addIndexes as $indexProp) {
804: $adapter->addIndex($tableName, $indexProp['KEY_NAME'], $indexProp['COLUMNS_LIST'],
805: $indexProp['INDEX_TYPE']);
806: }
807:
808:
809: foreach ($addConstraints as $constraintName => $constraintProp) {
810: $adapter->addForeignKey($constraintName, $tableName,
811: $constraintProp['table_index'],
812: $constraintProp['ref_table'],
813: $constraintProp['ref_index'],
814: $constraintProp['on_delete'],
815: $constraintProp['on_update']
816: );
817: }
818: }
819:
820: $this->_preparedFlatTables[$storeId] = true;
821:
822: return $this;
823: }
824:
825: 826: 827: 828: 829: 830: 831:
832: public function updateStaticAttributes($storeId, $productIds = null)
833: {
834: if (!$this->_isFlatTableExists($storeId)) {
835: return $this;
836: }
837: $adapter = $this->_getWriteAdapter();
838: $websiteId = (int)Mage::app()->getStore($storeId)->getWebsite()->getId();
839:
840: $status = $this->getAttribute('status');
841:
842: $fieldList = array('entity_id', 'type_id', 'attribute_set_id');
843: $colsList = array('entity_id', 'type_id', 'attribute_set_id');
844: if ($this->getFlatHelper()->isAddChildData()) {
845: $fieldList = array_merge($fieldList, array('child_id', 'is_child'));
846: $isChild = new Zend_Db_Expr('0');
847: $colsList = array_merge($colsList, array('entity_id', $isChild));
848: }
849:
850: $columns = $this->getFlatColumns();
851: $bind = array(
852: 'website_id' => $websiteId,
853: 'store_id' => $storeId,
854: 'entity_type_id' => (int)$status->getEntityTypeId(),
855: 'attribute_id' => (int)$status->getId()
856: );
857:
858: $fieldExpr = $adapter->getCheckSql('t2.value_id > 0', 't2.value', 't1.value');
859: $select = $this->_getWriteAdapter()->select()
860: ->from(array('e' => $this->getTable('catalog/product')), $colsList)
861: ->join(
862: array('wp' => $this->getTable('catalog/product_website')),
863: 'e.entity_id = wp.product_id AND wp.website_id = :website_id',
864: array())
865: ->joinLeft(
866: array('t1' => $status->getBackend()->getTable()),
867: 'e.entity_id = t1.entity_id',
868: array())
869: ->joinLeft(
870: array('t2' => $status->getBackend()->getTable()),
871: 't2.entity_id = t1.entity_id'
872: . ' AND t1.entity_type_id = t2.entity_type_id'
873: . ' AND t1.attribute_id = t2.attribute_id'
874: . ' AND t2.store_id = :store_id',
875: array())
876: ->where('t1.entity_type_id = :entity_type_id')
877: ->where('t1.attribute_id = :attribute_id')
878: ->where('t1.store_id = ?', Mage_Core_Model_App::ADMIN_STORE_ID)
879: ->where("{$fieldExpr} = ?", Mage_Catalog_Model_Product_Status::STATUS_ENABLED);
880: foreach ($this->getAttributes() as $attributeCode => $attribute) {
881:
882: if ($attribute->getBackend()->getType() == 'static') {
883: if (!isset($columns[$attributeCode])) {
884: continue;
885: }
886: $fieldList[] = $attributeCode;
887: $select->columns($attributeCode, 'e');
888: }
889: }
890:
891: if ($productIds !== null) {
892: $select->where('e.entity_id IN(?)', $productIds);
893: }
894:
895: $sql = $select->insertFromSelect($this->getFlatTableName($storeId), $fieldList);
896: $adapter->query($sql, $bind);
897:
898: return $this;
899: }
900:
901: 902: 903: 904: 905: 906: 907:
908: public function cleanNonWebsiteProducts($storeId, $productIds = null)
909: {
910: if (!$this->_isFlatTableExists($storeId)) {
911: return $this;
912: }
913:
914: $websiteId = (int)Mage::app()->getStore($storeId)->getWebsite()->getId();
915: $adapter = $this->_getWriteAdapter();
916:
917: $joinCondition = array(
918: 'e.entity_id = wp.product_id',
919: 'wp.website_id = :website_id'
920: );
921: if ($this->getFlatHelper()->isAddChildData()) {
922: $joinCondition[] = 'e.child_id = wp.product_id';
923: }
924: $bind = array('website_id' => $websiteId);
925: $select = $adapter->select()
926: ->from(array('e' => $this->getFlatTableName($storeId)), null)
927: ->joinLeft(
928: array('wp' => $this->getTable('catalog/product_website')),
929: implode(' AND ', $joinCondition),
930: array());
931: if ($productIds !== null) {
932: $condition = array(
933: $adapter->quoteInto('e.entity_id IN(?)', $productIds)
934: );
935: if ($this->getFlatHelper()->isAddChildData()) {
936: $condition[] = $adapter->quoteInto('e.child_id IN(?)', $productIds);
937: }
938: $select->where(implode(' OR ', $condition));
939: }
940:
941: $sql = $select->deleteFromSelect('e');
942: $adapter->query($sql, $bind);
943:
944: return $this;
945: }
946:
947: 948: 949: 950: 951: 952: 953: 954:
955: public function updateAttribute($attribute, $storeId, $productIds = null)
956: {
957: if (!$this->_isFlatTableExists($storeId)) {
958: return $this;
959: }
960: $adapter = $this->_getWriteAdapter();
961: $flatTableName = $this->getFlatTableName($storeId);
962: $describe = $adapter->describeTable($flatTableName);
963:
964: if ($attribute->getBackend()->getType() == 'static') {
965: if (!isset($describe[$attribute->getAttributeCode()])) {
966: return $this;
967: }
968:
969: $select = $adapter->select()
970: ->join(
971: array('main_table' => $this->getTable('catalog/product')),
972: 'main_table.entity_id = e.entity_id',
973: array($attribute->getAttributeCode() => 'main_table.' . $attribute->getAttributeCode())
974: );
975: if ($this->getFlatHelper()->isAddChildData()) {
976: $select->where('e.is_child = ?', 0);
977: }
978: if ($productIds !== null) {
979: $select->where('main_table.entity_id IN(?)', $productIds);
980: }
981:
982: $sql = $select->crossUpdateFromSelect(array('e' => $flatTableName));
983: $adapter->query($sql);
984: } else {
985: $columns = $attribute->getFlatColumns();
986: if (!$columns) {
987: return $this;
988: }
989: foreach (array_keys($columns) as $columnName) {
990: if (!isset($describe[$columnName])) {
991: return $this;
992: }
993: }
994:
995: $select = $attribute->getFlatUpdateSelect($storeId);
996: if ($select instanceof Varien_Db_Select) {
997: if ($productIds !== null) {
998: $select->where('e.entity_id IN(?)', $productIds);
999: }
1000:
1001: $sql = $select->crossUpdateFromSelect(array('e' => $flatTableName));
1002: $adapter->query($sql);
1003: }
1004: }
1005:
1006: return $this;
1007: }
1008:
1009: 1010: 1011: 1012: 1013: 1014: 1015:
1016: public function updateEavAttributes($storeId, $productIds = null)
1017: {
1018: if (!$this->_isFlatTableExists($storeId)) {
1019: return $this;
1020: }
1021:
1022: foreach ($this->getAttributes() as $attribute) {
1023:
1024: if ($attribute->getBackend()->getType() != 'static') {
1025: $this->updateAttribute($attribute, $storeId, $productIds);
1026: }
1027: }
1028: return $this;
1029: }
1030:
1031: 1032: 1033: 1034: 1035:
1036: public function updateEventAttributes($storeId = null)
1037: {
1038: Mage::dispatchEvent('catalog_product_flat_rebuild', array(
1039: 'store_id' => $storeId,
1040: 'table' => $this->getFlatTableName($storeId)
1041: ));
1042: }
1043:
1044: 1045: 1046: 1047: 1048: 1049:
1050: public function getProductTypeInstances()
1051: {
1052: if ($this->_productTypes === null) {
1053: $this->_productTypes = array();
1054: $productEmulator = new Varien_Object();
1055:
1056: foreach (array_keys(Mage_Catalog_Model_Product_Type::getTypes()) as $typeId) {
1057: $productEmulator->setTypeId($typeId);
1058: $this->_productTypes[$typeId] = Mage::getSingleton('catalog/product_type')
1059: ->factory($productEmulator);
1060: }
1061: }
1062: return $this->_productTypes;
1063: }
1064:
1065: 1066: 1067: 1068: 1069: 1070: 1071:
1072: public function updateRelationProducts($storeId, $productIds = null)
1073: {
1074: if (!$this->getFlatHelper()->isAddChildData() || !$this->_isFlatTableExists($storeId)) {
1075: return $this;
1076: }
1077:
1078: $adapter = $this->_getWriteAdapter();
1079:
1080: foreach ($this->getProductTypeInstances() as $typeInstance) {
1081: if (!$typeInstance->isComposite()) {
1082: continue;
1083: }
1084: $relation = $typeInstance->getRelationInfo();
1085: if ($relation
1086: && $relation->getTable()
1087: && $relation->getParentFieldName()
1088: && $relation->getChildFieldName()
1089: ) {
1090: $columns = $this->getFlatColumns();
1091: $fieldList = array_keys($columns);
1092: unset($columns['entity_id']);
1093: unset($columns['child_id']);
1094: unset($columns['is_child']);
1095:
1096: $select = $adapter->select()
1097: ->from(
1098: array('t' => $this->getTable($relation->getTable())),
1099: array($relation->getParentFieldName(), $relation->getChildFieldName(), new Zend_Db_Expr('1')))
1100: ->join(
1101: array('e' => $this->getFlatTableName($storeId)),
1102: "e.entity_id = t.{$relation->getChildFieldName()}",
1103: array_keys($columns)
1104: );
1105: if ($relation->getWhere() !== null) {
1106: $select->where($relation->getWhere());
1107: }
1108: if ($productIds !== null) {
1109: $cond = array(
1110: $adapter->quoteInto("{$relation->getChildFieldName()} IN(?)", $productIds),
1111: $adapter->quoteInto("{$relation->getParentFieldName()} IN(?)", $productIds)
1112: );
1113:
1114: $select->where(implode(' OR ', $cond));
1115: }
1116: $sql = $select->insertFromSelect($this->getFlatTableName($storeId), $fieldList);
1117: $adapter->query($sql);
1118: }
1119: }
1120:
1121: return $this;
1122: }
1123:
1124: 1125: 1126: 1127: 1128: 1129: 1130:
1131: public function updateChildrenDataFromParent($storeId, $productIds = null)
1132: {
1133: if (!$this->getFlatHelper()->isAddChildData() || !$this->_isFlatTableExists($storeId)) {
1134: return $this;
1135: }
1136: $adapter = $this->_getWriteAdapter();
1137:
1138: $select = $adapter->select();
1139: foreach (array_keys($this->getFlatColumns()) as $columnName) {
1140: if ($columnName == 'entity_id' || $columnName == 'child_id' || $columnName == 'is_child') {
1141: continue;
1142: }
1143: $select->columns(array($columnName => new Zend_Db_Expr('t1.' . $columnName)));
1144: }
1145: $select
1146: ->joinLeft(
1147: array('t1' => $this->getFlatTableName($storeId)),
1148: $adapter->quoteInto('t2.child_id = t1.entity_id AND t1.is_child = ?', 0),
1149: array())
1150: ->where('t2.is_child = ?', 1);
1151:
1152: if ($productIds !== null) {
1153: $select->where('t2.child_id IN(?)', $productIds);
1154: }
1155:
1156: $sql = $select->crossUpdateFromSelect(array('t2' => $this->getFlatTableName($storeId)));
1157: $adapter->query($sql);
1158:
1159: return $this;
1160: }
1161:
1162: 1163: 1164: 1165: 1166: 1167:
1168: public function cleanRelationProducts($storeId)
1169: {
1170: if (!$this->getFlatHelper()->isAddChildData()) {
1171: return $this;
1172: }
1173:
1174: foreach ($this->getProductTypeInstances() as $typeInstance) {
1175: if (!$typeInstance->isComposite()) {
1176: continue;
1177: }
1178: $adapter = $this->_getWriteAdapter();
1179: $relation = $typeInstance->getRelationInfo();
1180: if ($relation
1181: && $relation->getTable()
1182: && $relation->getParentFieldName()
1183: && $relation->getChildFieldName()
1184: ) {
1185: $select = $this->_getWriteAdapter()->select()
1186: ->distinct(true)
1187: ->from(
1188: $this->getTable($relation->getTable()),
1189: "{$relation->getParentFieldName()}"
1190: );
1191: $joinLeftCond = array(
1192: "e.entity_id = t.{$relation->getParentFieldName()}",
1193: "e.child_id = t.{$relation->getChildFieldName()}"
1194: );
1195: if ($relation->getWhere() !== null) {
1196: $select->where($relation->getWhere());
1197: $joinLeftCond[] = $relation->getWhere();
1198: }
1199:
1200: $entitySelect = new Zend_Db_Expr($select->__toString());
1201:
1202: $select = $adapter->select()
1203: ->from(array('e' => $this->getFlatTableName($storeId)), null)
1204: ->joinLeft(
1205: array('t' => $this->getTable($relation->getTable())),
1206: implode(' AND ', $joinLeftCond),
1207: array())
1208: ->where('e.is_child = ?', 1)
1209: ->where('e.entity_id IN(?)', $entitySelect)
1210: ->where("t.{$relation->getChildFieldName()} IS NULL");
1211:
1212: $sql = $select->deleteFromSelect('e');
1213: $adapter->query($sql);
1214: }
1215: }
1216:
1217: return $this;
1218: }
1219:
1220: 1221: 1222: 1223: 1224: 1225: 1226:
1227: public function removeProduct($productIds, $storeId)
1228: {
1229: if (!$this->_isFlatTableExists($storeId)) {
1230: return $this;
1231: }
1232: $adapter = $this->_getWriteAdapter();
1233: $cond = array(
1234: $adapter->quoteInto('entity_id IN(?)', $productIds)
1235: );
1236: if ($this->getFlatHelper()->isAddChildData()) {
1237: $cond[] = $adapter->quoteInto('child_id IN(?)', $productIds);
1238: }
1239: $cond = implode(' OR ', $cond);
1240: $adapter->delete($this->getFlatTableName($storeId), $cond);
1241:
1242: return $this;
1243: }
1244:
1245: 1246: 1247: 1248: 1249: 1250: 1251:
1252: public function removeProductChildren($productIds, $storeId)
1253: {
1254: if (!$this->getFlatHelper()->isAddChildData()) {
1255: return $this;
1256: }
1257: $whereExpr = array(
1258: 'entity_id IN(?)' => $productIds,
1259: 'is_child = ?' => 1
1260: );
1261: $this->_getWriteAdapter()->delete($this->getFlatTableName($storeId), $whereExpr);
1262:
1263: return $this;
1264: }
1265:
1266: 1267: 1268: 1269: 1270: 1271: 1272:
1273: public function updateProduct($productIds, $storeId)
1274: {
1275: if (!$this->_isFlatTableExists($storeId)) {
1276: return $this;
1277: }
1278:
1279: $this->saveProduct($productIds, $storeId);
1280:
1281: Mage::dispatchEvent('catalog_product_flat_update_product', array(
1282: 'store_id' => $storeId,
1283: 'table' => $this->getFlatTableName($storeId),
1284: 'product_ids' => $productIds
1285: ));
1286:
1287: return $this;
1288: }
1289:
1290: 1291: 1292: 1293: 1294: 1295: 1296:
1297: public function saveProduct($productIds, $storeId)
1298: {
1299: if (!$this->_isFlatTableExists($storeId)) {
1300: return $this;
1301: }
1302:
1303: $this->updateStaticAttributes($storeId, $productIds);
1304: $this->updateEavAttributes($storeId, $productIds);
1305:
1306: return $this;
1307: }
1308:
1309: 1310: 1311: 1312: 1313: 1314:
1315: public function deleteFlatTable($storeId)
1316: {
1317: if ($this->_isFlatTableExists($storeId)) {
1318: $this->_getWriteAdapter()->dropTable($this->getFlatTableName($storeId));
1319: }
1320:
1321: return $this;
1322: }
1323:
1324: 1325: 1326: 1327: 1328: 1329:
1330: protected function _isFlatTableExists($storeId)
1331: {
1332: if (!isset($this->_existsFlatTables[$storeId])) {
1333: $tableName = $this->getFlatTableName($storeId);
1334: $isTableExists = $this->_getWriteAdapter()->isTableExists($tableName);
1335:
1336: $this->_existsFlatTables[$storeId] = $isTableExists ? true : false;
1337: }
1338:
1339: return $this->_existsFlatTables[$storeId];
1340: }
1341:
1342: 1343: 1344: 1345: 1346: 1347: 1348:
1349: protected function _arrayPrevKey(array $array, $key)
1350: {
1351: $prev = false;
1352: foreach (array_keys($array) as $k) {
1353: if ($k == $key) {
1354: return $prev;
1355: }
1356: $prev = $k;
1357: }
1358: return false;
1359: }
1360:
1361: 1362: 1363: 1364: 1365: 1366: 1367:
1368: protected function _arrayNextKey(array $array, $key)
1369: {
1370: $next = false;
1371: foreach (array_keys($array) as $k) {
1372: if ($next === true) {
1373: return $k;
1374: }
1375: if ($k == $key) {
1376: $next = true;
1377: }
1378: }
1379: return false;
1380: }
1381:
1382: 1383: 1384: 1385: 1386:
1387: public function reindexAll()
1388: {
1389: foreach (Mage::app()->getStores() as $storeId => $store) {
1390: $this->prepareFlatTable($storeId);
1391: $this->beginTransaction();
1392: try {
1393: $this->rebuild($store);
1394: $this->commit();
1395: } catch (Exception $e) {
1396: $this->rollBack();
1397: throw $e;
1398: }
1399: }
1400: $flag = $this->getFlatHelper()->getFlag();
1401: $flag->setIsBuild(true)->save();
1402:
1403: return $this;
1404: }
1405: }
1406: