PageRenderTime 53ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 0ms

/app/code/core/Mage/Catalog/Model/Resource/Product/Flat/Indexer.php

https://github.com/FiveDigital/magento2
PHP | 1327 lines | 879 code | 136 blank | 312 comment | 103 complexity | e04122acc6c201bbf6b3f470a7e4d886 MD5 | raw file
Possible License(s): CC-BY-SA-3.0
  1. <?php
  2. /**
  3. * Magento
  4. *
  5. * NOTICE OF LICENSE
  6. *
  7. * This source file is subject to the Open Software License (OSL 3.0)
  8. * that is bundled with this package in the file LICENSE.txt.
  9. * It is also available through the world-wide-web at this URL:
  10. * http://opensource.org/licenses/osl-3.0.php
  11. * If you did not receive a copy of the license and are unable to
  12. * obtain it through the world-wide-web, please send an email
  13. * to license@magentocommerce.com so we can send you a copy immediately.
  14. *
  15. * DISCLAIMER
  16. *
  17. * Do not edit or add to this file if you wish to upgrade Magento to newer
  18. * versions in the future. If you wish to customize Magento for your
  19. * needs please refer to http://www.magentocommerce.com for more information.
  20. *
  21. * @category Mage
  22. * @package Mage_Catalog
  23. * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com)
  24. * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
  25. */
  26. /**
  27. * Catalog Product Flat Indexer Resource Model
  28. *
  29. * @category Mage
  30. * @package Mage_Catalog
  31. * @author Magento Core Team <core@magentocommerce.com>
  32. */
  33. class Mage_Catalog_Model_Resource_Product_Flat_Indexer extends Mage_Index_Model_Resource_Abstract
  34. {
  35. const XML_NODE_MAX_INDEX_COUNT = 'global/catalog/product/flat/max_index_count';
  36. const XML_NODE_ATTRIBUTE_NODES = 'global/catalog/product/flat/attribute_nodes';
  37. /**
  38. * Attribute codes for flat
  39. *
  40. * @var array
  41. */
  42. protected $_attributeCodes;
  43. /**
  44. * Attribute objects for flat cache
  45. *
  46. * @var array
  47. */
  48. protected $_attributes;
  49. /**
  50. * Required system attributes for preload
  51. *
  52. * @var array
  53. */
  54. protected $_systemAttributes = array('status', 'required_options', 'tax_class_id', 'weight');
  55. /**
  56. * Eav Catalog_Product Entity Type Id
  57. *
  58. * @var int
  59. */
  60. protected $_entityTypeId;
  61. /**
  62. * Flat table columns cache
  63. *
  64. * @var array
  65. */
  66. protected $_columns;
  67. /**
  68. * Flat table indexes cache
  69. *
  70. * @var array
  71. */
  72. protected $_indexes;
  73. /**
  74. * Product Type Instances cache
  75. *
  76. * @var array
  77. */
  78. protected $_productTypes;
  79. /**
  80. * Exists flat tables cache
  81. *
  82. * @var array
  83. */
  84. protected $_existsFlatTables = array();
  85. /**
  86. * Flat tables which were prepared
  87. *
  88. * @var array
  89. */
  90. protected $_preparedFlatTables = array();
  91. /**
  92. * Initialize connection
  93. *
  94. */
  95. protected function _construct()
  96. {
  97. $this->_init('catalog_product_entity', 'entity_id');
  98. }
  99. /**
  100. * Rebuild Catalog Product Flat Data
  101. *
  102. * @param Mage_Core_Model_Store|int $store
  103. * @return Mage_Catalog_Model_Resource_Product_Flat_Indexer
  104. */
  105. public function rebuild($store = null)
  106. {
  107. if ($store === null) {
  108. foreach (Mage::app()->getStores() as $store) {
  109. $this->rebuild($store->getId());
  110. }
  111. return $this;
  112. }
  113. $storeId = (int)Mage::app()->getStore($store)->getId();
  114. $this->prepareFlatTable($storeId);
  115. $this->cleanNonWebsiteProducts($storeId);
  116. $this->updateStaticAttributes($storeId);
  117. $this->updateEavAttributes($storeId);
  118. $this->updateEventAttributes($storeId);
  119. $this->updateRelationProducts($storeId);
  120. $this->cleanRelationProducts($storeId);
  121. $flag = $this->getFlatHelper()->getFlag();
  122. $flag->setIsBuilt(true)->save();
  123. return $this;
  124. }
  125. /**
  126. * Retrieve Catalog Product Flat helper
  127. *
  128. * @return Mage_Catalog_Helper_Product_Flat
  129. */
  130. public function getFlatHelper()
  131. {
  132. return Mage::helper('Mage_Catalog_Helper_Product_Flat');
  133. }
  134. /**
  135. * Retrieve attribute codes using for flat
  136. *
  137. * @return array
  138. */
  139. public function getAttributeCodes()
  140. {
  141. if ($this->_attributeCodes === null) {
  142. $adapter = $this->_getReadAdapter();
  143. $this->_attributeCodes = array();
  144. $attributeNodes = Mage::getConfig()
  145. ->getNode(self::XML_NODE_ATTRIBUTE_NODES)
  146. ->children();
  147. foreach ($attributeNodes as $node) {
  148. $attributes = Mage::getConfig()->getNode((string)$node)->asArray();
  149. $attributes = array_keys($attributes);
  150. $this->_systemAttributes = array_unique(array_merge($attributes, $this->_systemAttributes));
  151. }
  152. $bind = array(
  153. 'backend_type' => Mage_Eav_Model_Entity_Attribute_Abstract::TYPE_STATIC,
  154. 'entity_type_id' => $this->getEntityTypeId()
  155. );
  156. $select = $adapter->select()
  157. ->from(array('main_table' => $this->getTable('eav_attribute')))
  158. ->join(
  159. array('additional_table' => $this->getTable('catalog_eav_attribute')),
  160. 'additional_table.attribute_id = main_table.attribute_id'
  161. )
  162. ->where('main_table.entity_type_id = :entity_type_id');
  163. $whereCondition = array(
  164. 'main_table.backend_type = :backend_type',
  165. $adapter->quoteInto('additional_table.is_used_for_promo_rules = ?', 1),
  166. $adapter->quoteInto('additional_table.used_in_product_listing = ?', 1),
  167. $adapter->quoteInto('additional_table.used_for_sort_by = ?', 1),
  168. $adapter->quoteInto('main_table.attribute_code IN(?)', $this->_systemAttributes)
  169. );
  170. if ($this->getFlatHelper()->isAddFilterableAttributes()) {
  171. $whereCondition[] = $adapter->quoteInto('additional_table.is_filterable > ?', 0);
  172. }
  173. $select->where(implode(' OR ', $whereCondition));
  174. $attributesData = $adapter->fetchAll($select, $bind);
  175. Mage::getSingleton('Mage_Eav_Model_Config')
  176. ->importAttributesData($this->getEntityType(), $attributesData);
  177. foreach ($attributesData as $data) {
  178. $this->_attributeCodes[$data['attribute_id']] = $data['attribute_code'];
  179. }
  180. unset($attributesData);
  181. }
  182. return $this->_attributeCodes;
  183. }
  184. /**
  185. * Retrieve entity type
  186. *
  187. * @return string
  188. */
  189. public function getEntityType()
  190. {
  191. return Mage_Catalog_Model_Product::ENTITY;
  192. }
  193. /**
  194. * Retrieve Catalog Entity Type Id
  195. *
  196. * @return int
  197. */
  198. public function getEntityTypeId()
  199. {
  200. if ($this->_entityTypeId === null) {
  201. $this->_entityTypeId = Mage::getResourceModel('Mage_Catalog_Model_Resource_Config')
  202. ->getEntityTypeId();
  203. }
  204. return $this->_entityTypeId;
  205. }
  206. /**
  207. * Retrieve attribute objects for flat
  208. *
  209. * @return array
  210. */
  211. public function getAttributes()
  212. {
  213. if ($this->_attributes === null) {
  214. $this->_attributes = array();
  215. $attributeCodes = $this->getAttributeCodes();
  216. $entity = Mage::getSingleton('Mage_Eav_Model_Config')
  217. ->getEntityType($this->getEntityType())
  218. ->getEntity();
  219. foreach ($attributeCodes as $attributeCode) {
  220. $attribute = Mage::getSingleton('Mage_Eav_Model_Config')
  221. ->getAttribute($this->getEntityType(), $attributeCode)
  222. ->setEntity($entity);
  223. try {
  224. // check if exists source and backend model.
  225. // To prevent exception when some module was disabled
  226. $attribute->usesSource() && $attribute->getSource();
  227. $attribute->getBackend();
  228. $this->_attributes[$attributeCode] = $attribute;
  229. } catch (Exception $e) {
  230. Mage::logException($e);
  231. }
  232. }
  233. }
  234. return $this->_attributes;
  235. }
  236. /**
  237. * Retrieve loaded attribute by code
  238. *
  239. * @param string $attributeCode
  240. * @throws Mage_Core_Exception
  241. * @return Mage_Eav_Model_Entity_Attribute
  242. */
  243. public function getAttribute($attributeCode)
  244. {
  245. $attributes = $this->getAttributes();
  246. if (!isset($attributes[$attributeCode])) {
  247. $attribute = Mage::getModel('Mage_Catalog_Model_Resource_Eav_Attribute')
  248. ->loadByCode($this->getEntityTypeId(), $attributeCode);
  249. if (!$attribute->getId()) {
  250. Mage::throwException(Mage::helper('Mage_Catalog_Helper_Data')->__('Invalid attribute %s', $attributeCode));
  251. }
  252. $entity = Mage::getSingleton('Mage_Eav_Model_Config')
  253. ->getEntityType($this->getEntityType())
  254. ->getEntity();
  255. $attribute->setEntity($entity);
  256. return $attribute;
  257. }
  258. return $attributes[$attributeCode];
  259. }
  260. /**
  261. * Retrieve Catalog Product Flat Table name
  262. *
  263. * @param int $storeId
  264. * @return string
  265. */
  266. public function getFlatTableName($storeId)
  267. {
  268. return sprintf('%s_%s', $this->getTable('catalog_product_flat'), $storeId);
  269. }
  270. /**
  271. * Retrieve catalog product flat columns array in old format (used before MMDB support)
  272. *
  273. * @return array
  274. */
  275. protected function _getFlatColumnsOldDefinition()
  276. {
  277. $columns = array();
  278. $columns['entity_id'] = array(
  279. 'type' => 'int(10)',
  280. 'unsigned' => true,
  281. 'is_null' => false,
  282. 'default' => null,
  283. 'extra' => null
  284. );
  285. if ($this->getFlatHelper()->isAddChildData()) {
  286. $columns['child_id'] = array(
  287. 'type' => 'int(10)',
  288. 'unsigned' => true,
  289. 'is_null' => true,
  290. 'default' => null,
  291. 'extra' => null
  292. );
  293. $columns['is_child'] = array(
  294. 'type' => 'tinyint(1)',
  295. 'unsigned' => true,
  296. 'is_null' => false,
  297. 'default' => 0,
  298. 'extra' => null
  299. );
  300. }
  301. $columns['attribute_set_id'] = array(
  302. 'type' => 'smallint(5)',
  303. 'unsigned' => true,
  304. 'is_null' => false,
  305. 'default' => 0,
  306. 'extra' => null
  307. );
  308. $columns['type_id'] = array(
  309. 'type' => 'varchar(32)',
  310. 'unsigned' => false,
  311. 'is_null' => false,
  312. 'default' => Mage_Catalog_Model_Product_Type::TYPE_SIMPLE,
  313. 'extra' => null
  314. );
  315. return $columns;
  316. }
  317. /**
  318. * Retrieve catalog product flat columns array in DDL format
  319. *
  320. * @return array
  321. */
  322. protected function _getFlatColumnsDdlDefinition()
  323. {
  324. $columns = array();
  325. $columns['entity_id'] = array(
  326. 'type' => Varien_Db_Ddl_Table::TYPE_INTEGER,
  327. 'length' => null,
  328. 'unsigned' => true,
  329. 'nullable' => false,
  330. 'default' => false,
  331. 'primary' => true,
  332. 'comment' => 'Entity Id'
  333. );
  334. if ($this->getFlatHelper()->isAddChildData()) {
  335. $columns['child_id'] = array(
  336. 'type' => Varien_Db_Ddl_Table::TYPE_INTEGER,
  337. 'length' => null,
  338. 'unsigned' => true,
  339. 'nullable' => true,
  340. 'default' => null,
  341. 'primary' => true,
  342. 'comment' => 'Child Id'
  343. );
  344. $columns['is_child'] = array(
  345. 'type' => Varien_Db_Ddl_Table::TYPE_SMALLINT,
  346. 'length' => 1,
  347. 'unsigned' => true,
  348. 'nullable' => false,
  349. 'default' => '0',
  350. 'comment' => 'Checks If Entity Is Child'
  351. );
  352. }
  353. $columns['attribute_set_id'] = array(
  354. 'type' => Varien_Db_Ddl_Table::TYPE_SMALLINT,
  355. 'length' => 5,
  356. 'unsigned' => true,
  357. 'nullable' => false,
  358. 'default' => '0',
  359. 'comment' => 'Attribute Set Id'
  360. );
  361. $columns['type_id'] = array(
  362. 'type' => Varien_Db_Ddl_Table::TYPE_TEXT,
  363. 'length' => 32,
  364. 'unsigned' => false,
  365. 'nullable' => false,
  366. 'default' => Mage_Catalog_Model_Product_Type::TYPE_SIMPLE,
  367. 'comment' => 'Type Id'
  368. );
  369. return $columns;
  370. }
  371. /**
  372. * Retrieve catalog product flat table columns array
  373. *
  374. * @return array
  375. */
  376. public function getFlatColumns()
  377. {
  378. if ($this->_columns === null) {
  379. if (Mage::helper('Mage_Core_Helper_Data')->useDbCompatibleMode()) {
  380. $this->_columns = $this->_getFlatColumnsOldDefinition();
  381. } else {
  382. $this->_columns = $this->_getFlatColumnsDdlDefinition();
  383. }
  384. foreach ($this->getAttributes() as $attribute) {
  385. /** @var $attribute Mage_Eav_Model_Entity_Attribute_Abstract */
  386. $columns = $attribute
  387. ->setFlatAddFilterableAttributes($this->getFlatHelper()->isAddFilterableAttributes())
  388. ->setFlatAddChildData($this->getFlatHelper()->isAddChildData())
  389. ->getFlatColumns();
  390. if ($columns !== null) {
  391. $this->_columns = array_merge($this->_columns, $columns);
  392. }
  393. }
  394. $columnsObject = new Varien_Object();
  395. $columnsObject->setColumns($this->_columns);
  396. Mage::dispatchEvent('catalog_product_flat_prepare_columns',
  397. array('columns' => $columnsObject)
  398. );
  399. $this->_columns = $columnsObject->getColumns();
  400. }
  401. return $this->_columns;
  402. }
  403. /**
  404. * Retrieve catalog product flat table indexes array
  405. *
  406. * @return array
  407. */
  408. public function getFlatIndexes()
  409. {
  410. if ($this->_indexes === null) {
  411. $this->_indexes = array();
  412. if ($this->getFlatHelper()->isAddChildData()) {
  413. $this->_indexes['PRIMARY'] = array(
  414. 'type' => Varien_Db_Adapter_Interface::INDEX_TYPE_PRIMARY,
  415. 'fields' => array('entity_id', 'child_id')
  416. );
  417. $this->_indexes['IDX_CHILD'] = array(
  418. 'type' => Varien_Db_Adapter_Interface::INDEX_TYPE_INDEX,
  419. 'fields' => array('child_id')
  420. );
  421. $this->_indexes['IDX_IS_CHILD'] = array(
  422. 'type' => Varien_Db_Adapter_Interface::INDEX_TYPE_INDEX,
  423. 'fields' => array('entity_id', 'is_child')
  424. );
  425. } else {
  426. $this->_indexes['PRIMARY'] = array(
  427. 'type' => Varien_Db_Adapter_Interface::INDEX_TYPE_PRIMARY,
  428. 'fields' => array('entity_id')
  429. );
  430. }
  431. $this->_indexes['IDX_TYPE_ID'] = array(
  432. 'type' => Varien_Db_Adapter_Interface::INDEX_TYPE_INDEX,
  433. 'fields' => array('type_id')
  434. );
  435. $this->_indexes['IDX_ATTRIBUTE_SET'] = array(
  436. 'type' => Varien_Db_Adapter_Interface::INDEX_TYPE_INDEX,
  437. 'fields' => array('attribute_set_id')
  438. );
  439. foreach ($this->getAttributes() as $attribute) {
  440. /** @var $attribute Mage_Eav_Model_Entity_Attribute */
  441. $indexes = $attribute
  442. ->setFlatAddFilterableAttributes($this->getFlatHelper()->isAddFilterableAttributes())
  443. ->setFlatAddChildData($this->getFlatHelper()->isAddChildData())
  444. ->getFlatIndexes();
  445. if ($indexes !== null) {
  446. $this->_indexes = array_merge($this->_indexes, $indexes);
  447. }
  448. }
  449. $indexesObject = new Varien_Object();
  450. $indexesObject->setIndexes($this->_indexes);
  451. Mage::dispatchEvent('catalog_product_flat_prepare_indexes', array(
  452. 'indexes' => $indexesObject
  453. ));
  454. $this->_indexes = $indexesObject->getIndexes();
  455. }
  456. return $this->_indexes;
  457. }
  458. /**
  459. * Compare Flat style with Describe style columns
  460. * If column a different - return false
  461. *
  462. * @param array $column
  463. * @param array $describe
  464. * @return bool
  465. */
  466. protected function _compareColumnProperties($column, $describe)
  467. {
  468. return Mage::getResourceHelper('Mage_Catalog')->compareIndexColumnProperties($column, $describe);
  469. }
  470. /**
  471. * Retrieve UNIQUE HASH for a Table foreign key
  472. *
  473. * @param string $priTableName the target table name
  474. * @param string $priColumnName the target table column name
  475. * @param string $refTableName the reference table name
  476. * @param string $refColumnName the reference table column name
  477. * @return string
  478. */
  479. public function getFkName($priTableName, $priColumnName, $refTableName, $refColumnName)
  480. {
  481. return Mage::getSingleton('Mage_Core_Model_Resource')
  482. ->getFkName($priTableName, $priColumnName, $refTableName, $refColumnName);
  483. }
  484. /**
  485. * Prepare flat table for store
  486. *
  487. * @param int $storeId
  488. * @throws Mage_Core_Exception
  489. * @return Mage_Catalog_Model_Resource_Product_Flat_Indexer
  490. */
  491. public function prepareFlatTable($storeId)
  492. {
  493. if (isset($this->_preparedFlatTables[$storeId])) {
  494. return $this;
  495. }
  496. $adapter = $this->_getWriteAdapter();
  497. $tableName = $this->getFlatTableName($storeId);
  498. // Extract columns we need to have in flat table
  499. $columns = $this->getFlatColumns();
  500. if (Mage::helper('Mage_Core_Helper_Data')->useDbCompatibleMode()) {
  501. /* Convert old format of flat columns to new MMDB format that uses DDL types and definitions */
  502. foreach ($columns as $key => $column) {
  503. $columns[$key] = Mage::getResourceHelper('Mage_Core')->convertOldColumnDefinition($column);
  504. }
  505. }
  506. // Extract indexes we need to have in flat table
  507. $indexesNeed = $this->getFlatIndexes();
  508. $maxIndex = Mage::getConfig()->getNode(self::XML_NODE_MAX_INDEX_COUNT);
  509. if (count($indexesNeed) > $maxIndex) {
  510. Mage::throwException(Mage::helper('Mage_Catalog_Helper_Data')->__("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));
  511. }
  512. // Process indexes to create names for them in MMDB-style and reformat to common index definition
  513. $indexKeys = array();
  514. $indexProps = array_values($indexesNeed);
  515. $upperPrimaryKey = strtoupper(Varien_Db_Adapter_Interface::INDEX_TYPE_PRIMARY);
  516. foreach ($indexProps as $i => $indexProp) {
  517. $indexName = $adapter->getIndexName($tableName, $indexProp['fields'], $indexProp['type']);
  518. $indexProp['type'] = strtoupper($indexProp['type']);
  519. if ($indexProp['type'] == $upperPrimaryKey) {
  520. $indexKey = $upperPrimaryKey;
  521. } else {
  522. $indexKey = $indexName;
  523. }
  524. $indexProps[$i] = array(
  525. 'KEY_NAME' => $indexName,
  526. 'COLUMNS_LIST' => $indexProp['fields'],
  527. 'INDEX_TYPE' => strtolower($indexProp['type'])
  528. );
  529. $indexKeys[$i] = $indexKey;
  530. }
  531. $indexesNeed = array_combine($indexKeys, $indexProps); // Array with index names as keys, except for primary
  532. // Foreign keys
  533. $foreignEntityKey = $this->getFkName($tableName, 'entity_id', 'catalog_product_entity', 'entity_id');
  534. $foreignChildKey = $this->getFkName($tableName, 'child_id', 'catalog_product_entity', 'entity_id');
  535. // Create table or modify existing one
  536. if (!$this->_isFlatTableExists($storeId)) {
  537. /** @var $table Varien_Db_Ddl_Table */
  538. $table = $adapter->newTable($tableName);
  539. foreach ($columns as $fieldName => $fieldProp) {
  540. $table->addColumn(
  541. $fieldName,
  542. $fieldProp['type'],
  543. isset($fieldProp['length']) ? $fieldProp['length'] : null,
  544. array(
  545. 'nullable' => isset($fieldProp['nullable']) ? (bool)$fieldProp['nullable'] : false,
  546. 'unsigned' => isset($fieldProp['unsigned']) ? (bool)$fieldProp['unsigned'] : false,
  547. 'default' => isset($fieldProp['default']) ? $fieldProp['default'] : false,
  548. 'primary' => false,
  549. ),
  550. isset($fieldProp['comment']) ? $fieldProp['comment'] : $fieldName
  551. );
  552. }
  553. foreach ($indexesNeed as $indexProp) {
  554. $table->addIndex($indexProp['KEY_NAME'], $indexProp['COLUMNS_LIST'],
  555. array('type' => $indexProp['INDEX_TYPE']));
  556. }
  557. $table->addForeignKey($foreignEntityKey,
  558. 'entity_id', $this->getTable('catalog_product_entity'), 'entity_id',
  559. Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_CASCADE);
  560. if ($this->getFlatHelper()->isAddChildData()) {
  561. $table->addForeignKey($foreignChildKey,
  562. 'child_id', $this->getTable('catalog_product_entity'), 'entity_id',
  563. Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_CASCADE);
  564. }
  565. $table->setComment("Catalog Product Flat (Store {$storeId})");
  566. $adapter->createTable($table);
  567. $this->_existsFlatTables[$storeId] = true;
  568. } else {
  569. $adapter->resetDdlCache($tableName);
  570. // Sort columns into added/altered/dropped lists
  571. $describe = $adapter->describeTable($tableName);
  572. $addColumns = array_diff_key($columns, $describe);
  573. $dropColumns = array_diff_key($describe, $columns);
  574. $modifyColumns = array();
  575. foreach ($columns as $field => $fieldProp) {
  576. if (isset($describe[$field]) && !$this->_compareColumnProperties($fieldProp, $describe[$field])) {
  577. $modifyColumns[$field] = $fieldProp;
  578. }
  579. }
  580. // Sort indexes into added/dropped lists. Altered indexes are put into both lists.
  581. $addIndexes = array();
  582. $dropIndexes = array();
  583. $indexesNow = $adapter->getIndexList($tableName); // Note: primary is always stored under 'PRIMARY' key
  584. $newIndexes = $indexesNeed;
  585. foreach ($indexesNow as $key => $indexNow) {
  586. if (isset($indexesNeed[$key])) {
  587. $indexNeed = $indexesNeed[$key];
  588. if (($indexNeed['INDEX_TYPE'] != $indexNow['INDEX_TYPE'])
  589. || ($indexNeed['COLUMNS_LIST'] != $indexNow['COLUMNS_LIST'])) {
  590. $dropIndexes[$key] = $indexNow;
  591. $addIndexes[$key] = $indexNeed;
  592. }
  593. unset($newIndexes[$key]);
  594. } else {
  595. $dropIndexes[$key] = $indexNow;
  596. }
  597. }
  598. $addIndexes = $addIndexes + $newIndexes;
  599. // Compose contstraints
  600. $addConstraints = array();
  601. $addConstraints[$foreignEntityKey] = array(
  602. 'table_index' => 'entity_id',
  603. 'ref_table' => $this->getTable('catalog_product_entity'),
  604. 'ref_index' => 'entity_id',
  605. 'on_update' => Varien_Db_Ddl_Table::ACTION_CASCADE,
  606. 'on_delete' => Varien_Db_Ddl_Table::ACTION_CASCADE
  607. );
  608. // Additional data from childs
  609. $isAddChildData = $this->getFlatHelper()->isAddChildData();
  610. if (!$isAddChildData && isset($describe['is_child'])) {
  611. $adapter->delete($tableName, array('is_child = ?' => 1));
  612. $adapter->dropForeignKey($tableName, $foreignChildKey);
  613. }
  614. if ($isAddChildData && !isset($describe['is_child'])) {
  615. $adapter->delete($tableName);
  616. $dropIndexes['PRIMARY'] = $indexesNow['PRIMARY'];
  617. $addIndexes['PRIMARY'] = $indexesNeed['PRIMARY'];
  618. $addConstraints[$foreignChildKey] = array(
  619. 'table_index' => 'child_id',
  620. 'ref_table' => $this->getTable('catalog_product_entity'),
  621. 'ref_index' => 'entity_id',
  622. 'on_update' => Varien_Db_Ddl_Table::ACTION_CASCADE,
  623. 'on_delete' => Varien_Db_Ddl_Table::ACTION_CASCADE
  624. );
  625. }
  626. // Drop constraints
  627. foreach (array_keys($adapter->getForeignKeys($tableName)) as $constraintName) {
  628. $adapter->dropForeignKey($tableName, $constraintName);
  629. }
  630. // Drop indexes
  631. foreach ($dropIndexes as $indexProp) {
  632. $adapter->dropIndex($tableName, $indexProp['KEY_NAME']);
  633. }
  634. // Drop columns
  635. foreach (array_keys($dropColumns) as $columnName) {
  636. $adapter->dropColumn($tableName, $columnName);
  637. }
  638. // Modify columns
  639. foreach ($modifyColumns as $columnName => $columnProp) {
  640. $columnProp = array_change_key_case($columnProp, CASE_UPPER);
  641. if (!isset($columnProp['COMMENT'])) {
  642. $columnProp['COMMENT'] = ucwords(str_replace('_', ' ', $columnName));
  643. }
  644. $adapter->changeColumn($tableName, $columnName, $columnName, $columnProp);
  645. }
  646. // Add columns
  647. foreach ($addColumns as $columnName => $columnProp) {
  648. $columnProp = array_change_key_case($columnProp, CASE_UPPER);
  649. if (!isset($columnProp['COMMENT'])) {
  650. $columnProp['COMMENT'] = ucwords(str_replace('_', ' ', $columnName));
  651. }
  652. $adapter->addColumn($tableName, $columnName, $columnProp);
  653. }
  654. // Add indexes
  655. foreach ($addIndexes as $indexProp) {
  656. $adapter->addIndex($tableName, $indexProp['KEY_NAME'], $indexProp['COLUMNS_LIST'],
  657. $indexProp['INDEX_TYPE']);
  658. }
  659. // Add constraints
  660. foreach ($addConstraints as $constraintName => $constraintProp) {
  661. $adapter->addForeignKey($constraintName, $tableName,
  662. $constraintProp['table_index'],
  663. $constraintProp['ref_table'],
  664. $constraintProp['ref_index'],
  665. $constraintProp['on_delete'],
  666. $constraintProp['on_update']
  667. );
  668. }
  669. }
  670. $this->_preparedFlatTables[$storeId] = true;
  671. return $this;
  672. }
  673. /**
  674. * Add or Update static attributes
  675. *
  676. * @param int $storeId
  677. * @param int|array $productIds update only product(s)
  678. * @return Mage_Catalog_Model_Resource_Product_Flat_Indexer
  679. */
  680. public function updateStaticAttributes($storeId, $productIds = null)
  681. {
  682. if (!$this->_isFlatTableExists($storeId)) {
  683. return $this;
  684. }
  685. $adapter = $this->_getWriteAdapter();
  686. $websiteId = (int)Mage::app()->getStore($storeId)->getWebsite()->getId();
  687. /* @var $status Mage_Eav_Model_Entity_Attribute */
  688. $status = $this->getAttribute('status');
  689. $fieldList = array('entity_id', 'type_id', 'attribute_set_id');
  690. $colsList = array('entity_id', 'type_id', 'attribute_set_id');
  691. if ($this->getFlatHelper()->isAddChildData()) {
  692. $fieldList = array_merge($fieldList, array('child_id', 'is_child'));
  693. $isChild = new Zend_Db_Expr('0');
  694. $colsList = array_merge($colsList, array('entity_id', $isChild));
  695. }
  696. $columns = $this->getFlatColumns();
  697. $bind = array(
  698. 'website_id' => $websiteId,
  699. 'store_id' => $storeId,
  700. 'entity_type_id' => (int)$status->getEntityTypeId(),
  701. 'attribute_id' => (int)$status->getId()
  702. );
  703. $fieldExpr = $adapter->getCheckSql('t2.value_id > 0', 't2.value', 't1.value');
  704. $select = $this->_getWriteAdapter()->select()
  705. ->from(array('e' => $this->getTable('catalog_product_entity')), $colsList)
  706. ->join(
  707. array('wp' => $this->getTable('catalog_product_website')),
  708. 'e.entity_id = wp.product_id AND wp.website_id = :website_id',
  709. array())
  710. ->joinLeft(
  711. array('t1' => $status->getBackend()->getTable()),
  712. 'e.entity_id = t1.entity_id',
  713. array())
  714. ->joinLeft(
  715. array('t2' => $status->getBackend()->getTable()),
  716. 't2.entity_id = t1.entity_id'
  717. . ' AND t1.entity_type_id = t2.entity_type_id'
  718. . ' AND t1.attribute_id = t2.attribute_id'
  719. . ' AND t2.store_id = :store_id',
  720. array())
  721. ->where('t1.entity_type_id = :entity_type_id')
  722. ->where('t1.attribute_id = :attribute_id')
  723. ->where('t1.store_id = ?', Mage_Core_Model_App::ADMIN_STORE_ID)
  724. ->where("{$fieldExpr} = ?", Mage_Catalog_Model_Product_Status::STATUS_ENABLED);
  725. foreach ($this->getAttributes() as $attributeCode => $attribute) {
  726. /** @var $attribute Mage_Eav_Model_Entity_Attribute */
  727. if ($attribute->getBackend()->getType() == 'static') {
  728. if (!isset($columns[$attributeCode])) {
  729. continue;
  730. }
  731. $fieldList[] = $attributeCode;
  732. $select->columns($attributeCode, 'e');
  733. }
  734. }
  735. if ($productIds !== null) {
  736. $select->where('e.entity_id IN(?)', $productIds);
  737. }
  738. $sql = $select->insertFromSelect($this->getFlatTableName($storeId), $fieldList);
  739. $adapter->query($sql, $bind);
  740. return $this;
  741. }
  742. /**
  743. * Remove non website products
  744. *
  745. * @param int $storeId
  746. * @param int|array $productIds
  747. * @return Mage_Catalog_Model_Resource_Product_Flat_Indexer
  748. */
  749. public function cleanNonWebsiteProducts($storeId, $productIds = null)
  750. {
  751. if (!$this->_isFlatTableExists($storeId)) {
  752. return $this;
  753. }
  754. $websiteId = (int)Mage::app()->getStore($storeId)->getWebsite()->getId();
  755. $adapter = $this->_getWriteAdapter();
  756. $joinCondition = array(
  757. 'e.entity_id = wp.product_id',
  758. 'wp.website_id = :website_id'
  759. );
  760. if ($this->getFlatHelper()->isAddChildData()) {
  761. $joinCondition[] = 'e.child_id = wp.product_id';
  762. }
  763. $bind = array('website_id' => $websiteId);
  764. $select = $adapter->select()
  765. ->from(array('e' => $this->getFlatTableName($storeId)), null)
  766. ->joinLeft(
  767. array('wp' => $this->getTable('catalog_product_website')),
  768. implode(' AND ', $joinCondition),
  769. array());
  770. if ($productIds !== null) {
  771. $condition = array(
  772. $adapter->quoteInto('e.entity_id IN(?)', $productIds)
  773. );
  774. if ($this->getFlatHelper()->isAddChildData()) {
  775. $condition[] = $adapter->quoteInto('e.child_id IN(?)', $productIds);
  776. }
  777. $select->where(implode(' OR ', $condition));
  778. }
  779. $sql = $select->deleteFromSelect('e');
  780. $adapter->query($sql, $bind);
  781. return $this;
  782. }
  783. /**
  784. * Update attribute flat data
  785. *
  786. * @param Mage_Eav_Model_Entity_Attribute $attribute
  787. * @param int $storeId
  788. * @param int|array $productIds update only product(s)
  789. * @return Mage_Catalog_Model_Resource_Product_Flat_Indexer
  790. */
  791. public function updateAttribute($attribute, $storeId, $productIds = null)
  792. {
  793. if (!$this->_isFlatTableExists($storeId)) {
  794. return $this;
  795. }
  796. $adapter = $this->_getWriteAdapter();
  797. $flatTableName = $this->getFlatTableName($storeId);
  798. $describe = $adapter->describeTable($flatTableName);
  799. if ($attribute->getBackend()->getType() == 'static') {
  800. if (!isset($describe[$attribute->getAttributeCode()])) {
  801. return $this;
  802. }
  803. $select = $adapter->select()
  804. ->join(
  805. array('main_table' => $this->getTable('catalog_product_entity')),
  806. 'main_table.entity_id = e.entity_id',
  807. array($attribute->getAttributeCode() => 'main_table.' . $attribute->getAttributeCode())
  808. );
  809. if ($this->getFlatHelper()->isAddChildData()) {
  810. $select->where('e.is_child = ?', 0);
  811. }
  812. if ($productIds !== null) {
  813. $select->where('main_table.entity_id IN(?)', $productIds);
  814. }
  815. $sql = $select->crossUpdateFromSelect(array('e' => $flatTableName));
  816. $adapter->query($sql);
  817. } else {
  818. $columns = $attribute->getFlatColumns();
  819. if (!$columns) {
  820. return $this;
  821. }
  822. foreach (array_keys($columns) as $columnName) {
  823. if (!isset($describe[$columnName])) {
  824. return $this;
  825. }
  826. }
  827. $select = $attribute->getFlatUpdateSelect($storeId);
  828. if ($select instanceof Varien_Db_Select) {
  829. if ($productIds !== null) {
  830. $select->where('e.entity_id IN(?)', $productIds);
  831. }
  832. $sql = $select->crossUpdateFromSelect(array('e' => $flatTableName));
  833. $adapter->query($sql);
  834. }
  835. }
  836. return $this;
  837. }
  838. /**
  839. * Update non static EAV attributes flat data
  840. *
  841. * @param int $storeId
  842. * @param int|array $productIds update only product(s)
  843. * @return Mage_Catalog_Model_Resource_Product_Flat_Indexer
  844. */
  845. public function updateEavAttributes($storeId, $productIds = null)
  846. {
  847. if (!$this->_isFlatTableExists($storeId)) {
  848. return $this;
  849. }
  850. foreach ($this->getAttributes() as $attribute) {
  851. /* @var $attribute Mage_Eav_Model_Entity_Attribute */
  852. if ($attribute->getBackend()->getType() != 'static') {
  853. $this->updateAttribute($attribute, $storeId, $productIds);
  854. }
  855. }
  856. return $this;
  857. }
  858. /**
  859. * Update events observer attributes
  860. *
  861. * @param int $storeId
  862. */
  863. public function updateEventAttributes($storeId = null)
  864. {
  865. Mage::dispatchEvent('catalog_product_flat_rebuild', array(
  866. 'store_id' => $storeId,
  867. 'table' => $this->getFlatTableName($storeId)
  868. ));
  869. }
  870. /**
  871. * Retrieve Product Type Instances
  872. * as key - type code, value - instance model
  873. *
  874. * @return array
  875. */
  876. public function getProductTypeInstances()
  877. {
  878. if ($this->_productTypes === null) {
  879. $this->_productTypes = array();
  880. $productEmulator = new Varien_Object();
  881. foreach (array_keys(Mage_Catalog_Model_Product_Type::getTypes()) as $typeId) {
  882. $productEmulator->setTypeId($typeId);
  883. $this->_productTypes[$typeId] = Mage::getSingleton('Mage_Catalog_Model_Product_Type')
  884. ->factory($productEmulator);
  885. }
  886. }
  887. return $this->_productTypes;
  888. }
  889. /**
  890. * Update relation products
  891. *
  892. * @param int $storeId
  893. * @param int|array $productIds Update child product(s) only
  894. * @return Mage_Catalog_Model_Resource_Product_Flat_Indexer
  895. */
  896. public function updateRelationProducts($storeId, $productIds = null)
  897. {
  898. if (!$this->getFlatHelper()->isAddChildData() || !$this->_isFlatTableExists($storeId)) {
  899. return $this;
  900. }
  901. $adapter = $this->_getWriteAdapter();
  902. foreach ($this->getProductTypeInstances() as $typeInstance) {
  903. if (!$typeInstance->isComposite()) {
  904. continue;
  905. }
  906. $relation = $typeInstance->getRelationInfo();
  907. if ($relation
  908. && $relation->getTable()
  909. && $relation->getParentFieldName()
  910. && $relation->getChildFieldName()
  911. ) {
  912. $columns = $this->getFlatColumns();
  913. $fieldList = array_keys($columns);
  914. unset($columns['entity_id']);
  915. unset($columns['child_id']);
  916. unset($columns['is_child']);
  917. $select = $adapter->select()
  918. ->from(
  919. array('t' => $this->getTable($relation->getTable())),
  920. array($relation->getParentFieldName(), $relation->getChildFieldName(), new Zend_Db_Expr('1')))
  921. ->join(
  922. array('e' => $this->getFlatTableName($storeId)),
  923. "e.entity_id = t.{$relation->getChildFieldName()}",
  924. array_keys($columns)
  925. );
  926. if ($relation->getWhere() !== null) {
  927. $select->where($relation->getWhere());
  928. }
  929. if ($productIds !== null) {
  930. $cond = array(
  931. $adapter->quoteInto("{$relation->getChildFieldName()} IN(?)", $productIds),
  932. $adapter->quoteInto("{$relation->getParentFieldName()} IN(?)", $productIds)
  933. );
  934. $select->where(implode(' OR ', $cond));
  935. }
  936. $sql = $select->insertFromSelect($this->getFlatTableName($storeId), $fieldList);
  937. $adapter->query($sql);
  938. }
  939. }
  940. return $this;
  941. }
  942. /**
  943. * Update children data from parent
  944. *
  945. * @param int $storeId
  946. * @param int|array $productIds
  947. * @return Mage_Catalog_Model_Resource_Product_Flat_Indexer
  948. */
  949. public function updateChildrenDataFromParent($storeId, $productIds = null)
  950. {
  951. if (!$this->getFlatHelper()->isAddChildData() || !$this->_isFlatTableExists($storeId)) {
  952. return $this;
  953. }
  954. $adapter = $this->_getWriteAdapter();
  955. $select = $adapter->select();
  956. foreach (array_keys($this->getFlatColumns()) as $columnName) {
  957. if ($columnName == 'entity_id' || $columnName == 'child_id' || $columnName == 'is_child') {
  958. continue;
  959. }
  960. $select->columns(array($columnName => new Zend_Db_Expr('t1.' . $columnName)));
  961. }
  962. $select
  963. ->joinLeft(
  964. array('t1' => $this->getFlatTableName($storeId)),
  965. $adapter->quoteInto('t2.child_id = t1.entity_id AND t1.is_child = ?', 0),
  966. array())
  967. ->where('t2.is_child = ?', 1);
  968. if ($productIds !== null) {
  969. $select->where('t2.child_id IN(?)', $productIds);
  970. }
  971. $sql = $select->crossUpdateFromSelect(array('t2' => $this->getFlatTableName($storeId)));
  972. $adapter->query($sql);
  973. return $this;
  974. }
  975. /**
  976. * Clean unused relation products
  977. *
  978. * @param int $storeId
  979. * @return Mage_Catalog_Model_Resource_Product_Flat_Indexer
  980. */
  981. public function cleanRelationProducts($storeId)
  982. {
  983. if (!$this->getFlatHelper()->isAddChildData()) {
  984. return $this;
  985. }
  986. foreach ($this->getProductTypeInstances() as $typeInstance) {
  987. if (!$typeInstance->isComposite()) {
  988. continue;
  989. }
  990. $adapter = $this->_getWriteAdapter();
  991. $relation = $typeInstance->getRelationInfo();
  992. if ($relation
  993. && $relation->getTable()
  994. && $relation->getParentFieldName()
  995. && $relation->getChildFieldName()
  996. ) {
  997. $select = $this->_getWriteAdapter()->select()
  998. ->distinct(true)
  999. ->from(
  1000. $this->getTable($relation->getTable()),
  1001. "{$relation->getParentFieldName()}"
  1002. );
  1003. $joinLeftCond = array(
  1004. "e.entity_id = t.{$relation->getParentFieldName()}",
  1005. "e.child_id = t.{$relation->getChildFieldName()}"
  1006. );
  1007. if ($relation->getWhere() !== null) {
  1008. $select->where($relation->getWhere());
  1009. $joinLeftCond[] = $relation->getWhere();
  1010. }
  1011. $entitySelect = new Zend_Db_Expr($select->__toString());
  1012. $select = $adapter->select()
  1013. ->from(array('e' => $this->getFlatTableName($storeId)), null)
  1014. ->joinLeft(
  1015. array('t' => $this->getTable($relation->getTable())),
  1016. implode(' AND ', $joinLeftCond),
  1017. array())
  1018. ->where('e.is_child = ?', 1)
  1019. ->where('e.entity_id IN(?)', $entitySelect)
  1020. ->where("t.{$relation->getChildFieldName()} IS NULL");
  1021. $sql = $select->deleteFromSelect('e');
  1022. $adapter->query($sql);
  1023. }
  1024. }
  1025. return $this;
  1026. }
  1027. /**
  1028. * Remove product data from flat
  1029. *
  1030. * @param int|array $productIds
  1031. * @param int $storeId
  1032. * @return Mage_Catalog_Model_Resource_Product_Flat_Indexer
  1033. */
  1034. public function removeProduct($productIds, $storeId)
  1035. {
  1036. if (!$this->_isFlatTableExists($storeId)) {
  1037. return $this;
  1038. }
  1039. $adapter = $this->_getWriteAdapter();
  1040. $cond = array(
  1041. $adapter->quoteInto('entity_id IN(?)', $productIds)
  1042. );
  1043. if ($this->getFlatHelper()->isAddChildData()) {
  1044. $cond[] = $adapter->quoteInto('child_id IN(?)', $productIds);
  1045. }
  1046. $cond = implode(' OR ', $cond);
  1047. $adapter->delete($this->getFlatTableName($storeId), $cond);
  1048. return $this;
  1049. }
  1050. /**
  1051. * Remove children from parent product
  1052. *
  1053. * @param int|array $productIds
  1054. * @param int $storeId
  1055. * @return Mage_Catalog_Model_Resource_Product_Flat_Indexer
  1056. */
  1057. public function removeProductChildren($productIds, $storeId)
  1058. {
  1059. if (!$this->getFlatHelper()->isAddChildData()) {
  1060. return $this;
  1061. }
  1062. $whereExpr = array(
  1063. 'entity_id IN(?)' => $productIds,
  1064. 'is_child = ?' => 1
  1065. );
  1066. $this->_getWriteAdapter()->delete($this->getFlatTableName($storeId), $whereExpr);
  1067. return $this;
  1068. }
  1069. /**
  1070. * Update flat data for product
  1071. *
  1072. * @param int|array $productIds
  1073. * @param int $storeId
  1074. * @return Mage_Catalog_Model_Resource_Product_Flat_Indexer
  1075. */
  1076. public function updateProduct($productIds, $storeId)
  1077. {
  1078. if (!$this->_isFlatTableExists($storeId)) {
  1079. return $this;
  1080. }
  1081. $this->saveProduct($productIds, $storeId);
  1082. Mage::dispatchEvent('catalog_product_flat_update_product', array(
  1083. 'store_id' => $storeId,
  1084. 'table' => $this->getFlatTableName($storeId),
  1085. 'product_ids' => $productIds
  1086. ));
  1087. return $this;
  1088. }
  1089. /**
  1090. * Save product(s) data for store
  1091. *
  1092. * @param int|array $productIds
  1093. * @param int $storeId
  1094. * @return Mage_Catalog_Model_Resource_Product_Flat_Indexer
  1095. */
  1096. public function saveProduct($productIds, $storeId)
  1097. {
  1098. if (!$this->_isFlatTableExists($storeId)) {
  1099. return $this;
  1100. }
  1101. $this->updateStaticAttributes($storeId, $productIds);
  1102. $this->updateEavAttributes($storeId, $productIds);
  1103. return $this;
  1104. }
  1105. /**
  1106. * Delete flat table process
  1107. *
  1108. * @param int $storeId
  1109. * @return Mage_Catalog_Model_Resource_Product_Flat_Indexer
  1110. */
  1111. public function deleteFlatTable($storeId)
  1112. {
  1113. if ($this->_isFlatTableExists($storeId)) {
  1114. $this->_getWriteAdapter()->dropTable($this->getFlatTableName($storeId));
  1115. }
  1116. return $this;
  1117. }
  1118. /**
  1119. * Check is flat table for store exists
  1120. *
  1121. * @param int $storeId
  1122. * @return bool
  1123. */
  1124. protected function _isFlatTableExists($storeId)
  1125. {
  1126. if (!isset($this->_existsFlatTables[$storeId])) {
  1127. $tableName = $this->getFlatTableName($storeId);
  1128. $isTableExists = $this->_getWriteAdapter()->isTableExists($tableName);
  1129. $this->_existsFlatTables[$storeId] = $isTableExists ? true : false;
  1130. }
  1131. return $this->_existsFlatTables[$storeId];
  1132. }
  1133. /**
  1134. * Retrieve previous key from array by key
  1135. *
  1136. * @param array $array
  1137. * @param mixed $key
  1138. * @return mixed
  1139. */
  1140. protected function _arrayPrevKey(array $array, $key)
  1141. {
  1142. $prev = false;
  1143. foreach (array_keys($array) as $k) {
  1144. if ($k == $key) {
  1145. return $prev;
  1146. }
  1147. $prev = $k;
  1148. }
  1149. return false;
  1150. }
  1151. /**
  1152. * Retrieve next key from array by key
  1153. *
  1154. * @param array $array
  1155. * @param mixed $key
  1156. * @return mixed
  1157. */
  1158. protected function _arrayNextKey(array $array, $key)
  1159. {
  1160. $next = false;
  1161. foreach (array_keys($array) as $k) {
  1162. if ($next === true) {
  1163. return $k;
  1164. }
  1165. if ($k == $key) {
  1166. $next = true;
  1167. }
  1168. }
  1169. return false;
  1170. }
  1171. /**
  1172. * Transactional rebuild Catalog Product Flat Data
  1173. *
  1174. * @return Mage_Catalog_Model_Resource_Product_Flat_Indexer
  1175. */
  1176. public function reindexAll()
  1177. {
  1178. foreach (Mage::app()->getStores() as $storeId => $store) {
  1179. $this->prepareFlatTable($storeId);
  1180. $this->beginTransaction();
  1181. try {
  1182. $this->rebuild($store);
  1183. $this->commit();
  1184. } catch (Exception $e) {
  1185. $this->rollBack();
  1186. throw $e;
  1187. }
  1188. }
  1189. return $this;
  1190. }
  1191. }