PageRenderTime 54ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 1ms

/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Category/Flat.php

https://bitbucket.org/claudiu_marginean/magento-hg-mirror
PHP | 1291 lines | 878 code | 88 blank | 325 comment | 79 complexity | 94befe41969287489e6629b1e93a77b0 MD5 | raw file
Possible License(s): CC-BY-SA-3.0, LGPL-2.1, GPL-2.0, WTFPL
  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) 2010 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. * Category flat model
  28. *
  29. * @category Mage
  30. * @package Mage_Catalog
  31. * @author Magento Core Team <core@magentocommerce.com>
  32. */
  33. class Mage_Catalog_Model_Resource_Eav_Mysql4_Category_Flat extends Mage_Core_Model_Mysql4_Abstract
  34. {
  35. protected $_storeId = null;
  36. protected $_loaded = false;
  37. protected $_nodes = array();
  38. protected $_columns = null;
  39. protected $_columnsSql = null;
  40. protected $_attributeCodes = null;
  41. /**
  42. * Inactive categories ids
  43. *
  44. * @var array
  45. */
  46. protected $_inactiveCategoryIds = null;
  47. protected $_isRebuilt = null;
  48. /**
  49. * array with root category id per store
  50. *
  51. * @var array
  52. */
  53. protected $_storesRootCategories;
  54. protected function _construct()
  55. {
  56. $this->_init('catalog/category_flat', 'entity_id');
  57. }
  58. /**
  59. * Set store id
  60. *
  61. * @param integer $storeId
  62. * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Category_Flat
  63. */
  64. public function setStoreId($storeId)
  65. {
  66. $this->_storeId = $storeId;
  67. return $this;
  68. }
  69. /**
  70. * Return store id
  71. *
  72. * @return integer
  73. */
  74. public function getStoreId()
  75. {
  76. if (is_null($this->_storeId)) {
  77. return Mage::app()->getStore()->getId();
  78. }
  79. return $this->_storeId;
  80. }
  81. /**
  82. * Get main table name
  83. *
  84. * @return string
  85. */
  86. public function getMainTable()
  87. {
  88. return $this->getMainStoreTable($this->getStoreId());
  89. }
  90. /**
  91. * Return name of table for given $storeId.
  92. *
  93. * @param integer $storeId
  94. * @return string
  95. */
  96. public function getMainStoreTable($storeId = 0)
  97. {
  98. $table = parent::getMainTable();
  99. if (is_string($storeId)) {
  100. $storeId = intval($storeId);
  101. }
  102. if ($this->getUseStoreTables() && $storeId) {
  103. $table .= '_store_'.$storeId;
  104. }
  105. return $table;
  106. }
  107. /**
  108. * Return true if need use for each store different table of flat categoris data.
  109. *
  110. * @return boolean
  111. */
  112. public function getUseStoreTables()
  113. {
  114. return true;
  115. }
  116. /**
  117. * Add inactive categories ids
  118. *
  119. * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Category_Flat
  120. */
  121. public function addInactiveCategoryIds($ids)
  122. {
  123. if (!is_array($this->_inactiveCategoryIds)) {
  124. $this->_initInactiveCategoryIds();
  125. }
  126. $this->_inactiveCategoryIds = array_merge($ids, $this->_inactiveCategoryIds);
  127. return $this;
  128. }
  129. /**
  130. * Retreive inactive categories ids
  131. *
  132. * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Category_Flat
  133. */
  134. protected function _initInactiveCategoryIds()
  135. {
  136. $this->_inactiveCategoryIds = array();
  137. Mage::dispatchEvent('catalog_category_tree_init_inactive_category_ids', array('tree'=>$this));
  138. return $this;
  139. }
  140. /**
  141. * Retreive inactive categories ids
  142. *
  143. * @return array
  144. */
  145. public function getInactiveCategoryIds()
  146. {
  147. if (!is_array($this->_inactiveCategoryIds)) {
  148. $this->_initInactiveCategoryIds();
  149. }
  150. return $this->_inactiveCategoryIds;
  151. }
  152. /**
  153. * Load nodes by parent id
  154. *
  155. * @param integer $parentId
  156. * @param integer $recursionLevel
  157. * @param integer $storeId
  158. * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Category_Flat
  159. */
  160. protected function _loadNodes($parentNode = null, $recursionLevel = 0, $storeId = 0)
  161. {
  162. $_conn = $this->_getReadAdapter();
  163. $startLevel = 1;
  164. $parentPath = '';
  165. if ($parentNode instanceof Mage_Catalog_Model_Category) {
  166. $parentPath = $parentNode->getPath();
  167. $startLevel = $parentNode->getLevel();
  168. } elseif (is_numeric($parentNode)) {
  169. $selectParent = $_conn->select()
  170. ->from($this->getMainStoreTable($storeId))
  171. ->where('entity_id = ?', $parentNode)
  172. ->where('store_id = ?', $storeId);
  173. if ($parentNode = $_conn->fetchRow($selectParent)) {
  174. $parentPath = $parentNode['path'];
  175. $startLevel = $parentNode['level'];
  176. }
  177. }
  178. $select = $_conn->select()
  179. ->from(array('main_table'=>$this->getMainStoreTable($storeId)), array('main_table.entity_id', 'main_table.name', 'main_table.path', 'main_table.is_active', 'main_table.is_anchor'))
  180. ->joinLeft(
  181. array('url_rewrite'=>$this->getTable('core/url_rewrite')),
  182. 'url_rewrite.category_id=main_table.entity_id AND url_rewrite.is_system=1 AND url_rewrite.product_id IS NULL AND url_rewrite.store_id="'.$storeId.'" AND url_rewrite.id_path LIKE "category/%"',
  183. array('request_path' => 'url_rewrite.request_path'))
  184. ->where('main_table.is_active = ?', '1')
  185. ->where('main_table.include_in_menu', '1')
  186. // ->order('main_table.path', 'ASC')
  187. ->order('main_table.position', 'ASC');
  188. if ($parentPath) {
  189. $select->where($_conn->quoteInto("main_table.path like ?", "$parentPath/%"));
  190. }
  191. if ($recursionLevel != 0) {
  192. $select->where("main_table.level <= ?", $startLevel + $recursionLevel);
  193. }
  194. $inactiveCategories = $this->getInactiveCategoryIds();
  195. if (!empty($inactiveCategories)) {
  196. $select->where('main_table.entity_id NOT IN (?)', $inactiveCategories);
  197. }
  198. $arrNodes = $_conn->fetchAll($select);
  199. $nodes = array();
  200. foreach ($arrNodes as $node) {
  201. $node['id'] = $node['entity_id'];
  202. $nodes[$node['id']] = Mage::getModel('catalog/category')->setData($node);
  203. }
  204. return $nodes;
  205. }
  206. /**
  207. * Creating sorted array of nodes
  208. *
  209. * @param array $children
  210. * @param string $path
  211. * @param Varien_Object $parent
  212. */
  213. public function addChildNodes($children, $path, $parent)
  214. {
  215. if (isset($children[$path])) {
  216. foreach ($children[$path] as $child) {
  217. $childrenNodes = $parent->getChildrenNodes();
  218. if ($childrenNodes && isset($childrenNodes[$child->getId()])) {
  219. $childrenNodes[$child['entity_id']]->setChildrenNodes(array($child->getId()=>$child));
  220. } else {
  221. if ($childrenNodes) {
  222. $childrenNodes[$child->getId()] = $child;
  223. } else {
  224. $childrenNodes = array($child->getId()=>$child);
  225. }
  226. $parent->setChildrenNodes($childrenNodes);
  227. }
  228. if ($path) {
  229. $childrenPath = explode('/', $path);
  230. } else {
  231. $childrenPath = array();
  232. }
  233. $childrenPath[] = $child->getId();
  234. $childrenPath = implode('/', $childrenPath);
  235. $this->addChildNodes($children, $childrenPath, $child);
  236. }
  237. }
  238. }
  239. /**
  240. * Return sorted array of nodes
  241. *
  242. * @param integer|null $parentId
  243. * @param integer $recursionLevel
  244. * @param integer $storeId
  245. * @return array
  246. */
  247. public function getNodes($parentId, $recursionLevel = 0, $storeId = 0)
  248. {
  249. if (!$this->_loaded) {
  250. $selectParent = $this->_getReadAdapter()->select()
  251. ->from($this->getMainStoreTable($storeId))
  252. ->where('entity_id = ?', $parentId);
  253. if ($parentNode = $this->_getReadAdapter()->fetchRow($selectParent)) {
  254. $parentNode['id'] = $parentNode['entity_id'];
  255. $parentNode = Mage::getModel('catalog/category')->setData($parentNode);
  256. $this->_nodes[$parentNode->getId()] = $parentNode;
  257. $nodes = $this->_loadNodes($parentNode, $recursionLevel, $storeId);
  258. $childrenItems = array();
  259. foreach ($nodes as $node) {
  260. $pathToParent = explode('/', $node->getPath());
  261. array_pop($pathToParent);
  262. $pathToParent = implode('/', $pathToParent);
  263. $childrenItems[$pathToParent][] = $node;
  264. }
  265. $this->addChildNodes($childrenItems, $parentNode->getPath(), $parentNode);
  266. $childrenNodes = $this->_nodes[$parentNode->getId()];
  267. if ($childrenNodes->getChildrenNodes()) {
  268. $this->_nodes = $childrenNodes->getChildrenNodes();
  269. }
  270. else {
  271. $this->_nodes = array();
  272. }
  273. $this->_loaded = true;
  274. }
  275. }
  276. return $this->_nodes;
  277. }
  278. /**
  279. * Return array or collection of categories
  280. *
  281. * @param integer $parent
  282. * @param integer $recursionLevel
  283. * @param boolean|string $sorted
  284. * @param boolean $asCollection
  285. * @param boolean $toLoad
  286. * @return array|Varien_Data_Collection
  287. */
  288. public function getCategories($parent, $recursionLevel = 0, $sorted=false, $asCollection=false, $toLoad=true)
  289. {
  290. if ($asCollection) {
  291. $parentPath = $this->_getReadAdapter()->fetchOne(new Zend_Db_Expr("
  292. SELECT path FROM {$this->getMainStoreTable($this->getStoreId())} WHERE entity_id = {$parent}
  293. "));
  294. $collection = Mage::getModel('catalog/category')->getCollection()
  295. ->addNameToResult()
  296. ->addUrlRewriteToResult()
  297. ->addParentPathFilter($parentPath)
  298. ->addStoreFilter()
  299. ->addIsActiveFilter()
  300. ->addAttributeToFilter('include_in_menu', 1)
  301. ->addSortedField($sorted);
  302. if ($toLoad) {
  303. return $collection->load();
  304. }
  305. return $collection;
  306. }
  307. return $this->getNodes($parent, $recursionLevel, Mage::app()->getStore()->getId());
  308. }
  309. /**
  310. * Return node with id $nodeId
  311. *
  312. * @param integer $nodeId
  313. * @param array $nodes
  314. * @return Varien_Object
  315. */
  316. public function getNodeById($nodeId, $nodes = null)
  317. {
  318. if (is_null($nodes)) {
  319. $nodes = $this->getNodes();
  320. }
  321. if (isset($nodes[$nodeId])) {
  322. return $nodes[$nodeId];
  323. }
  324. foreach ($nodes as $node) {
  325. // if ($node->getId() == $nodeId) {
  326. // return $node;
  327. // }
  328. if ($node->getChildrenNodes()) {
  329. return $this->getNodeById($nodeId, $node->getChildrenNodes());
  330. }
  331. }
  332. return array();
  333. }
  334. /**
  335. * Check if category flat data is rebuilt
  336. *
  337. * @return bool
  338. */
  339. public function isRebuilt()
  340. {
  341. if ($this->_isRebuilt === null) {
  342. $select = $this->_getReadAdapter()->select()
  343. ->from($this->getMainStoreTable(Mage::app()->getDefaultStoreView()->getId()), 'entity_id')
  344. ->limit(1);
  345. try {
  346. $this->_isRebuilt = (bool) $this->_getReadAdapter()->fetchOne($select);
  347. } catch (Exception $e) {
  348. $this->_isRebuilt = false;
  349. }
  350. }
  351. return $this->_isRebuilt;
  352. }
  353. protected function _getTableSqlSchema($storeId = 0)
  354. {
  355. $storeId = Mage::app()->getStore($storeId)->getId();
  356. $schema = "CREATE TABLE `{$this->getMainStoreTable($storeId)}` (
  357. `entity_id` int(10) unsigned not null,
  358. `store_id` smallint(5) unsigned not null default '0',
  359. `parent_id` int(10) unsigned not null default '0',
  360. `path` varchar(255) not null default '',
  361. `level` int(11) not null default '0',
  362. `position` int(11) not null default '0',
  363. `children_count` int(11) not null,
  364. `created_at` datetime not null default '0000-00-00 00:00:00',
  365. `updated_at` datetime not null default '0000-00-00 00:00:00',
  366. KEY `CATEGORY_FLAT_CATEGORY_ID` (`entity_id`),
  367. KEY `CATEGORY_FLAT_STORE_ID` (`store_id`),
  368. KEY `path` (`path`),
  369. KEY `IDX_LEVEL` (`level`),
  370. CONSTRAINT `FK_CATEGORY_FLAT_CATEGORY_ID_STORE_{$storeId}` FOREIGN KEY (`entity_id`)
  371. REFERENCES `{$this->getTable('catalog/category')}` (`entity_id`) ON DELETE CASCADE ON UPDATE CASCADE,
  372. CONSTRAINT `FK_CATEGORY_FLAT_STORE_ID_STORE_{$storeId}` FOREIGN KEY (`store_id`)
  373. REFERENCES `{$this->getTable('core/store')}` (`store_id`) ON DELETE CASCADE ON UPDATE CASCADE
  374. ) ENGINE=InnoDB DEFAULT CHARSET=utf8";
  375. return $schema;
  376. }
  377. /**
  378. * Rebuild flat data from eav
  379. *
  380. * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Category_Flat
  381. */
  382. public function rebuild($stores = null)
  383. {
  384. if ($stores === null) {
  385. $stores = Mage::app()->getStores();
  386. }
  387. if (!is_array($stores)) {
  388. $stores = array($stores);
  389. }
  390. $rootId = Mage_Catalog_Model_Category::TREE_ROOT_ID;
  391. $categories = array();
  392. $categoriesIds = array();
  393. /* @var $store Mage_Core_Model_Store */
  394. foreach ($stores as $store) {
  395. $this->_createTable($store->getId());
  396. if (!isset($categories[$store->getRootCategoryId()])) {
  397. $select = $this->_getWriteAdapter()->select()
  398. ->from($this->getTable('catalog/category'))
  399. ->where('path = ?', (string)$rootId)
  400. ->orWhere('path = ?', "{$rootId}/{$store->getRootCategoryId()}")
  401. ->orWhere('path LIKE ?', "{$rootId}/{$store->getRootCategoryId()}/%");
  402. $categories[$store->getRootCategoryId()] = $this->_getWriteAdapter()->fetchAll($select);
  403. $categoriesIds[$store->getRootCategoryId()] = array();
  404. foreach ($categories[$store->getRootCategoryId()] as $category) {
  405. $categoriesIds[$store->getRootCategoryId()][] = $category['entity_id'];
  406. }
  407. }
  408. $categoriesIdsChunks = array_chunk($categoriesIds[$store->getRootCategoryId()], 500);
  409. foreach ($categoriesIdsChunks as $categoriesIdsChunk) {
  410. $attributesData = $this->_getAttributeValues($categoriesIdsChunk, $store->getId());
  411. $data = array();
  412. foreach ($categories[$store->getRootCategoryId()] as $category) {
  413. if (!isset($attributesData[$category['entity_id']])) {
  414. continue;
  415. }
  416. $category['store_id'] = $store->getId();
  417. $data[] = $this->_prepareValuesToInsert(
  418. array_merge($category, $attributesData[$category['entity_id']])
  419. );
  420. }
  421. $this->_getWriteAdapter()->insertMultiple($this->getMainStoreTable($store->getId()), $data);
  422. }
  423. }
  424. return $this;
  425. }
  426. /**
  427. * Prepare array of column and columnValue pairs
  428. *
  429. * @param array $data
  430. * @return array
  431. */
  432. protected function _prepareValuesToInsert($data)
  433. {
  434. $values = array();
  435. foreach (array_keys($this->_columns) as $key => $column) {
  436. if (isset($data[$column])) {
  437. $values[$column] = $data[$column];
  438. } else {
  439. $values[$column] = '';
  440. }
  441. }
  442. return $values;
  443. }
  444. /**
  445. * Create Flate Table(s)
  446. *
  447. * @param array|int $stores
  448. * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Category_Flat
  449. */
  450. public function createTable($stores)
  451. {
  452. return $this->_createTable($stores);
  453. }
  454. /**
  455. * Creating table and adding attributes as fields to table
  456. *
  457. * @param array|integer $store
  458. * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Category_Flat
  459. */
  460. protected function _createTable($store)
  461. {
  462. $this->_getWriteAdapter()->query("DROP TABLE IF EXISTS `{$this->getMainStoreTable($store)}`;");
  463. $_tableSql = "CREATE TABLE `{$this->getMainStoreTable($store)}` (\n";
  464. if ($this->_columnsSql === null || $this->_columnsSql === null) {
  465. $this->_columns = array_merge($this->_getStaticColumns(), $this->_getEavColumns());
  466. foreach ($this->_columns as $columnName => $columnData) {
  467. $this->_columnsSql .= '`' . $columnName . '` ' . $columnData['type'];
  468. $this->_columnsSql .= $columnData['is_unsigned'] ? ' unsigned' : '';
  469. $this->_columnsSql .= ($columnData['is_null'] ? '' : ' not null');
  470. $this->_columnsSql .= ($columnData['default'] === false ? '' : ' default \'' . $columnData['default'] . '\'');
  471. $this->_columnsSql .= ",\n";
  472. }
  473. }
  474. $_tableSql .= $this->_columnsSql;
  475. $_tableSql .= "PRIMARY KEY (`entity_id`),
  476. KEY `IDX_STORE` (`store_id`),
  477. KEY `IDX_PATH` (`path`),
  478. KEY `IDX_LEVEL` (`level`),
  479. CONSTRAINT `FK_CATEGORY_FLAT_CATEGORY_ID_STORE_{$store}` FOREIGN KEY (`entity_id`)
  480. REFERENCES `{$this->getTable('catalog/category')}` (`entity_id`) ON DELETE CASCADE ON UPDATE CASCADE,
  481. CONSTRAINT `FK_CATEGORY_FLAT_STORE_ID_STORE_{$store}` FOREIGN KEY (`store_id`)
  482. REFERENCES `{$this->getTable('core/store')}` (`store_id`) ON DELETE CASCADE ON UPDATE CASCADE
  483. ) ENGINE=InnoDB DEFAULT CHARSET=utf8";
  484. $this->_getWriteAdapter()->query($_tableSql);
  485. return $this;
  486. }
  487. /**
  488. * Return array of static columns
  489. *
  490. * @return array
  491. */
  492. protected function _getStaticColumns()
  493. {
  494. $columns = array();
  495. $columnsToSkip = array('entity_type_id', 'attribute_set_id');
  496. $describe = $this->_getWriteAdapter()->describeTable($this->getTable('catalog/category'));
  497. foreach ($describe as $column) {
  498. if (in_array($column['COLUMN_NAME'], $columnsToSkip)) {
  499. continue;
  500. }
  501. $_type = '';
  502. $_is_unsigned = '';
  503. switch ($column['DATA_TYPE']) {
  504. case 'smallint':
  505. case 'int':
  506. $_type = $column['DATA_TYPE'] . '(11)';
  507. $_is_unsigned = (bool)$column['UNSIGNED'];
  508. if ($column['DEFAULT'] === '') {
  509. $column['DEFAULT'] = null;
  510. }
  511. break;
  512. case 'varchar':
  513. $_type = $column['DATA_TYPE'] . '(' . $column['LENGTH'] . ')';
  514. $_is_unsigned = null;
  515. break;
  516. case 'datetime':
  517. $_type = $column['DATA_TYPE'];
  518. $_is_unsigned = null;
  519. break;
  520. case 'decimal':
  521. $_type = $column['DATA_TYPE'] . '(' . $column['PRECISION'] . ',' . $column['SCALE'] . ')';
  522. $_is_unsigned = null;
  523. if ($column['DEFAULT'] === '') {
  524. $column['DEFAULT'] = null;
  525. }
  526. break;
  527. }
  528. $columns[$column['COLUMN_NAME']] = array(
  529. 'type' => $_type,
  530. 'is_unsigned' => $_is_unsigned,
  531. 'is_null' => $column['NULLABLE'],
  532. 'default' => ($column['DEFAULT'] === null ? false : $column['DEFAULT'])
  533. );
  534. }
  535. $columns['store_id'] = array(
  536. 'type' => 'smallint(5)',
  537. 'is_unsigned' => true,
  538. 'is_null' => false,
  539. 'default' => '0'
  540. );
  541. return $columns;
  542. }
  543. /**
  544. * Return array of eav columns, skip attribute with static type
  545. *
  546. * @return array
  547. */
  548. protected function _getEavColumns()
  549. {
  550. $columns = array();
  551. $attributes = $this->_getAttributes();
  552. foreach ($attributes as $attribute) {
  553. if ($attribute['backend_type'] == 'static') {
  554. continue;
  555. }
  556. $columns[$attribute['attribute_code']] = array();
  557. switch ($attribute['backend_type']) {
  558. case 'varchar':
  559. $columns[$attribute['attribute_code']] = array(
  560. 'type' => 'varchar(255)',
  561. 'is_unsigned' => null,
  562. 'is_null' => false,
  563. 'default' => ''
  564. );
  565. break;
  566. case 'int':
  567. $columns[$attribute['attribute_code']] = array(
  568. 'type' => 'int(10)',
  569. 'is_unsigned' => null,
  570. 'is_null' => false,
  571. 'default' => '0'
  572. );
  573. break;
  574. case 'text':
  575. $columns[$attribute['attribute_code']] = array(
  576. 'type' => 'text',
  577. 'is_unsigned' => null,
  578. 'is_null' => true,
  579. 'default' => null
  580. );
  581. break;
  582. case 'datetime':
  583. $columns[$attribute['attribute_code']] = array(
  584. 'type' => 'datetime',
  585. 'is_unsigned' => null,
  586. 'is_null' => false,
  587. 'default' => '0000-00-00 00:00:00'
  588. );
  589. break;
  590. case 'decimal':
  591. $columns[$attribute['attribute_code']] = array(
  592. 'type' => 'decimal(12,4)',
  593. 'is_unsigned' => null,
  594. 'is_null' => false,
  595. 'default' => '0.0000'
  596. );
  597. break;
  598. }
  599. }
  600. return $columns;
  601. }
  602. /**
  603. * Return array of attribute codes for entity type 'catalog_category'
  604. *
  605. * @return array
  606. */
  607. protected function _getAttributes()
  608. {
  609. if ($this->_attributeCodes === null) {
  610. $select = $this->_getWriteAdapter()->select()
  611. ->from($this->getTable('eav/entity_type'), array())
  612. ->join(
  613. $this->getTable('eav/attribute'),
  614. $this->getTable('eav/attribute').'.entity_type_id = '.$this->getTable('eav/entity_type').'.entity_type_id',
  615. $this->getTable('eav/attribute').'.*'
  616. )
  617. ->where($this->getTable('eav/entity_type').'.entity_type_code=?', 'catalog_category');
  618. $this->_attributeCodes = array();
  619. foreach ($this->_getWriteAdapter()->fetchAll($select) as $attribute) {
  620. $this->_attributeCodes[$attribute['attribute_id']] = $attribute;
  621. }
  622. }
  623. return $this->_attributeCodes;
  624. }
  625. /**
  626. * Return attribute values for given entities and store
  627. *
  628. * @param array $entityIds
  629. * @param integer $store_id
  630. * @return array
  631. */
  632. protected function _getAttributeValues($entityIds, $store_id)
  633. {
  634. if (!is_array($entityIds)) {
  635. $entityIds = array($entityIds);
  636. }
  637. $values = array();
  638. foreach ($entityIds as $entityId) {
  639. $values[$entityId] = array();
  640. }
  641. $attributes = $this->_getAttributes();
  642. $attributesType = array(
  643. 'varchar',
  644. 'int',
  645. 'decimal',
  646. 'text',
  647. 'datetime'
  648. );
  649. foreach ($attributesType as $type) {
  650. foreach ($this->_getAttributeTypeValues($type, $entityIds, $store_id) as $row) {
  651. $values[$row['entity_id']][$attributes[$row['attribute_id']]['attribute_code']] = $row['value'];
  652. }
  653. }
  654. return $values;
  655. }
  656. /**
  657. * Return attribute values for given entities and store of specific attribute type
  658. *
  659. * @param string $type
  660. * @param array $entityIds
  661. * @param integer $store_id
  662. * @return array
  663. */
  664. protected function _getAttributeTypeValues($type, $entityIds, $store_id)
  665. {
  666. $select = $this->_getWriteAdapter()->select()
  667. ->from(array('default' => $this->getTable('catalog/category') . '_' . $type), array('entity_id', 'attribute_id'))
  668. ->joinLeft(
  669. array('store' => $this->getTable('catalog/category') . '_' . $type),
  670. '`store`.entity_id = `default`.entity_id AND `store`.attribute_id = `default`.attribute_id AND `store`.store_id = ' . $store_id,
  671. array('value' => new Zend_Db_Expr('IF(`store`.`value_id`>0, `store`.`value`, `default`.`value`)'))
  672. )
  673. ->where('`default`.entity_id IN (?)', $entityIds)
  674. ->where('`default`.store_id = ?', 0);
  675. return $this->_getWriteAdapter()->fetchAll($select);
  676. }
  677. /**
  678. * Delete store table(s) of given stores;
  679. *
  680. * @param array|integer $stores
  681. * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Category_Flat
  682. */
  683. public function deleteStores($stores)
  684. {
  685. $this->_deleteTable($stores);
  686. return $this;
  687. }
  688. /**
  689. * Delete table(s) of given stores.
  690. *
  691. * @param array|integer $stores
  692. * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Category_Flat
  693. */
  694. protected function _deleteTable($stores)
  695. {
  696. if (!is_array($stores)) {
  697. $stores = array($stores);
  698. }
  699. foreach ($stores as $store) {
  700. $_tableExist = $this->_getWriteAdapter()->query(
  701. "DROP TABLE IF EXISTS `{$this->getMainStoreTable($store)}`"
  702. );
  703. }
  704. return $this;
  705. }
  706. /**
  707. * Synchronize flat data with eav model for category
  708. *
  709. * @param Varien_Object $category
  710. * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Category_Flat
  711. */
  712. protected function _synchronize($category)
  713. {
  714. $table = $this->getMainStoreTable($category->getStoreId());
  715. $data = $this->_prepareDataForAllFields($category);
  716. $this->_getWriteAdapter()->insertOnDuplicate($table, $data);
  717. return $this;
  718. }
  719. /**
  720. * Synchronize flat data with eav model.
  721. *
  722. * @param Mage_Catalog_Model_Category|int $category
  723. * @param array $storeIds
  724. * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Category_Flat
  725. */
  726. public function synchronize($category = null, $storeIds = array())
  727. {
  728. if (is_null($category)) {
  729. if (empty($storeIds)) {
  730. $storeIds = null;
  731. }
  732. $stores = $this->getStoresRootCategories($storeIds);
  733. $storesObjects = array();
  734. foreach ($stores as $storeId => $rootCategoryId) {
  735. $_store = new Varien_Object(array(
  736. 'store_id' => $storeId,
  737. 'root_category_id' => $rootCategoryId
  738. ));
  739. $_store->setIdFieldName('store_id');
  740. $storesObjects[] = $_store;
  741. }
  742. $this->rebuild($storesObjects);
  743. } else if ($category instanceof Mage_Catalog_Model_Category) {
  744. $categoryId = $category->getId();
  745. foreach ($category->getStoreIds() as $storeId) {
  746. if ($storeId == 0) {
  747. continue;
  748. }
  749. $attributeValues = $this->_getAttributeValues($categoryId, $storeId);
  750. $data = new Varien_Object($category->getData());
  751. $data->addData($attributeValues[$categoryId])
  752. ->setStoreId($storeId);
  753. $this->_synchronize($data);
  754. }
  755. } else if (is_numeric($category)) {
  756. $write = $this->_getWriteAdapter();
  757. $select = $write->select()
  758. ->from($this->getTable('catalog/category'))
  759. ->where('entity_id=?', $category);
  760. $row = $write->fetchRow($select);
  761. if (!$row) {
  762. return $this;
  763. }
  764. $stores = $this->getStoresRootCategories();
  765. $path = explode('/', $row['path']);
  766. foreach ($stores as $storeId => $rootCategoryId) {
  767. if (in_array($rootCategoryId, $path)) {
  768. $attributeValues = $this->_getAttributeValues($category, $storeId);
  769. $data = new Varien_Object($row);
  770. $data->addData($attributeValues[$category])
  771. ->setStoreId($storeId);
  772. $this->_synchronize($data);
  773. } else {
  774. $where = $write->quoteInto('entity_id=?', $category);
  775. $write->delete($this->getMainStoreTable($storeId), $where);
  776. }
  777. }
  778. }
  779. return $this;
  780. }
  781. public function removeStores($stores)
  782. {
  783. $this->_deleteTable($stores);
  784. return $this;
  785. }
  786. /**
  787. * Synchronize flat category data after move by affected category ids
  788. *
  789. * @param array $affectedCategoryIds
  790. * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Category_Flat
  791. */
  792. public function move(array $affectedCategoryIds)
  793. {
  794. $write = $this->_getWriteAdapter();
  795. $select = $write->select()
  796. ->from($this->getTable('catalog/category'), array('entity_id', 'path'))
  797. ->where('entity_id IN(?)', $affectedCategoryIds);
  798. $pairs = $write->fetchPairs($select);
  799. $pathCond = array($write->quoteInto('entity_id IN(?)', $affectedCategoryIds));
  800. $parentIds = array();
  801. foreach ($pairs as $path) {
  802. $pathCond[] = $write->quoteInto('path LIKE ?', $path . '/%');
  803. $parentIds = array_merge($parentIds, explode('/', $path));
  804. }
  805. $stores = $this->getStoresRootCategories();
  806. $where = join(' OR ', $pathCond);
  807. $lastId = 0;
  808. while (true) {
  809. $select = $write->select()
  810. ->from($this->getTable('catalog/category'))
  811. ->where('entity_id>?', $lastId)
  812. ->where($where)
  813. ->order('entity_id')
  814. ->limit(500);
  815. $rowSet = $write->fetchAll($select);
  816. if (!$rowSet) {
  817. break;
  818. }
  819. $addStores = array();
  820. $remStores = array();
  821. foreach ($rowSet as &$row) {
  822. $lastId = $row['entity_id'];
  823. $path = explode('/', $row['path']);
  824. foreach ($stores as $storeId => $rootCategoryId) {
  825. if (in_array($rootCategoryId, $path)) {
  826. $addStores[$storeId][$row['entity_id']] = $row;
  827. } else {
  828. $remStores[$storeId][] = $row['entity_id'];
  829. }
  830. }
  831. }
  832. // remove
  833. foreach ($remStores as $storeId => $categoryIds) {
  834. $where = $write->quoteInto('entity_id IN(?)', $categoryIds);
  835. $write->delete($this->getMainStoreTable($storeId), $where);
  836. }
  837. // add/update
  838. foreach ($addStores as $storeId => $storeCategoryIds) {
  839. $attributeValues = $this->_getAttributeValues(array_keys($storeCategoryIds), $storeId);
  840. foreach ($storeCategoryIds as $row) {
  841. $data = new Varien_Object($row);
  842. $data->addData($attributeValues[$row['entity_id']])
  843. ->setStoreId($storeId);
  844. $this->_synchronize($data);
  845. }
  846. }
  847. }
  848. return $this;
  849. }
  850. /**
  851. * Synchronize flat data with eav after moving category
  852. *
  853. * @param integer $categoryId
  854. * @param integer $prevParentId
  855. * @param integer $parentId
  856. * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Category_Flat
  857. */
  858. public function moveold($categoryId, $prevParentId, $parentId)
  859. {
  860. $_staticFields = array(
  861. 'parent_id',
  862. 'path',
  863. 'level',
  864. 'position',
  865. 'children_count',
  866. 'updated_at'
  867. );
  868. $prevParent = Mage::getModel('catalog/category')->load($prevParentId);
  869. $parent = Mage::getModel('catalog/category')->load($parentId);
  870. if ($prevParent->getStore()->getWebsiteId() != $parent->getStore()->getWebsiteId()) {
  871. foreach ($prevParent->getStoreIds() as $storeId) {
  872. $this->_getWriteAdapter()->delete(
  873. $this->getMainStoreTable($storeId),
  874. $this->_getWriteAdapter()->quoteInto('entity_id = ?', $categoryId)
  875. );
  876. }
  877. $categoryPath = $this->_getWriteAdapter()->fetchOne("
  878. SELECT
  879. path
  880. FROM
  881. {$this->getTable('catalog/category')}
  882. WHERE
  883. entity_id = '$categoryId'
  884. ");
  885. $select = $this->_getWriteAdapter()->select()
  886. ->from($this->getTable('catalog/category'), 'entity_id')
  887. ->where('path LIKE ?', "$categoryPath/%")
  888. ->orWhere('path = ?', $categoryPath);
  889. $_categories = $this->_getWriteAdapter()->fetchAll($select);
  890. foreach ($_categories as $_category) {
  891. foreach ($parent->getStoreIds() as $storeId) {
  892. $_tmpCategory = Mage::getModel('catalog/category')
  893. ->setStoreId($storeId)
  894. ->load($_category['entity_id']);
  895. $this->_synchronize($_tmpCategory);
  896. }
  897. }
  898. } else {
  899. foreach ($parent->getStoreIds() as $store) {
  900. $update = "UPDATE {$this->getMainStoreTable($store)}, {$this->getTable('catalog/category')} SET";
  901. foreach ($_staticFields as $field) {
  902. $update .= " {$this->getMainStoreTable($store)}.".$field."={$this->getTable('catalog/category')}.".$field.",";
  903. }
  904. $update = substr($update, 0, -1);
  905. $update .= " WHERE {$this->getMainStoreTable($store)}.entity_id = {$this->getTable('catalog/category')}.entity_id AND " .
  906. "({$this->getTable('catalog/category')}.path like '{$parent->getPath()}/%' OR " .
  907. "{$this->getTable('catalog/category')}.path like '{$prevParent->getPath()}/%')";
  908. $this->_getWriteAdapter()->query($update);
  909. }
  910. }
  911. $prevParent = null;
  912. $parent = null;
  913. $_tmpCategory = null;
  914. // $this->_move($categoryId, $prevParentPath, $parentPath);
  915. return $this;
  916. }
  917. /**
  918. * Prepare array of category data to insert or update.
  919. *
  920. * array(
  921. * 'field_name' => 'value'
  922. * )
  923. *
  924. * @param Mage_Catalog_Model_Category $category
  925. * @param array $replaceFields
  926. * @return array
  927. */
  928. protected function _prepareDataForAllFields($category, $replaceFields = array())
  929. {
  930. $table = $this->getMainStoreTable($category->getStoreId());
  931. $this->_getWriteAdapter()->resetDdlCache($table);
  932. $table = $this->_getWriteAdapter()->describeTable($table);
  933. $data = array();
  934. foreach ($table as $column=>$columnData) {
  935. if (null !== $category->getData($column)) {
  936. if (key_exists($column, $replaceFields)) {
  937. $value = $category->getData($replaceFields[$column]);
  938. } else {
  939. $value = $category->getData($column);
  940. }
  941. if (is_array($value)) {
  942. $value = implode(',', $value);
  943. }
  944. $data[$column] = $value;
  945. }
  946. }
  947. return $data;
  948. }
  949. /**
  950. * Retrieve attribute instance
  951. * Special for non static flat table
  952. *
  953. * @param mixed $attribute
  954. * @return Mage_Eav_Model_Entity_Attribute_Abstract
  955. */
  956. public function getAttribute($attribute)
  957. {
  958. return Mage::getSingleton('catalog/config')
  959. ->getAttribute('catalog_category', $attribute);
  960. }
  961. /**
  962. * Get count of active/not active children categories
  963. *
  964. * @param Mage_Catalog_Model_Category $category
  965. * @param bool $isActiveFlag
  966. * @return integer
  967. */
  968. public function getChildrenAmount($category, $isActiveFlag = true)
  969. {
  970. $_table = $this->getMainStoreTable($category->getStoreId());
  971. $select = $this->_getReadAdapter()->select()
  972. ->from($_table, "COUNT({$_table}.entity_id)")
  973. ->where("{$_table}.path LIKE ?", $category->getPath() . '/%')
  974. ->where("{$_table}.is_active = ?", (int) $isActiveFlag);
  975. return (int) $this->_getReadAdapter()->fetchOne($select);
  976. }
  977. /**
  978. * Get products count in category
  979. *
  980. * @param Mage_Catalog_Model_Category $category
  981. * @return integer
  982. */
  983. public function getProductCount($category)
  984. {
  985. $select = $this->_getReadAdapter()->select()
  986. ->from($this->getTable('catalog/category_product'), "COUNT({$this->getTable('catalog/category_product')}.product_id)")
  987. ->where("{$this->getTable('catalog/category_product')}.category_id = ?", $category->getId())
  988. ->group("{$this->getTable('catalog/category_product')}.category_id");
  989. return (int) $this->_getReadAdapter()->fetchOne($select);
  990. }
  991. /**
  992. * Return parent categories of category
  993. *
  994. * @param Mage_Catalog_Model_Category $category
  995. * @return array
  996. */
  997. public function getParentCategories($category, $isActive = true)
  998. {
  999. $categories = array();
  1000. $select = $this->_getReadAdapter()->select()
  1001. ->from(array('main_table' => $this->getMainStoreTable($category->getStoreId())), array('main_table.entity_id', 'main_table.name'))
  1002. ->joinLeft(
  1003. array('url_rewrite'=>$this->getTable('core/url_rewrite')),
  1004. 'url_rewrite.category_id=main_table.entity_id AND url_rewrite.is_system=1 AND url_rewrite.product_id IS NULL AND url_rewrite.store_id="'.$category->getStoreId().'" AND url_rewrite.id_path LIKE "category/%"',
  1005. array('request_path' => 'url_rewrite.request_path'))
  1006. ->where('main_table.entity_id IN (?)', array_reverse(explode(',', $category->getPathInStore())));
  1007. if ($isActive) {
  1008. $select->where('main_table.is_active = ?', '1');
  1009. }
  1010. $select->order('main_table.path ASC');
  1011. $result = $this->_getReadAdapter()->fetchAll($select);
  1012. foreach ($result as $row) {
  1013. $row['id'] = $row['entity_id'];
  1014. $categories[$row['entity_id']] = Mage::getModel('catalog/category')->setData($row);
  1015. }
  1016. return $categories;
  1017. }
  1018. /**
  1019. * Return parent category of current category with own custom design settings
  1020. *
  1021. * @param Mage_Catalog_Model_Category $category
  1022. * @return Mage_Catalog_Model_Category
  1023. */
  1024. public function getParentDesignCategory($category)
  1025. {
  1026. $pathIds = array_reverse($category->getPathIds());
  1027. $select = $this->_getReadAdapter()->select()
  1028. ->from(array('main_table' => $this->getMainStoreTable($category->getStoreId())), '*')
  1029. ->where('entity_id IN (?)', $pathIds)
  1030. ->where('custom_use_parent_settings = ?', 0)
  1031. ->where('level != ?', 0)
  1032. ->order('level DESC');
  1033. $result = $this->_getReadAdapter()->fetchRow($select);
  1034. return Mage::getModel('catalog/category')->setData($result);
  1035. }
  1036. /**
  1037. * Return children categories of category
  1038. *
  1039. * @param Mage_Catalog_Model_Category $category
  1040. * @return array
  1041. */
  1042. public function getChildrenCategories($category)
  1043. {
  1044. // $node = $this->getNodeById($category->getId());
  1045. // if ($node && $node->getChildrenNodes()) {
  1046. // return $node->getChildrenNodes();
  1047. // }
  1048. $categories = $this->_loadNodes($category, 1, $category->getStoreId());
  1049. return $categories;
  1050. }
  1051. /**
  1052. * Check is category in list of store categories
  1053. *
  1054. * @param Mage_Catalog_Model_Category $category
  1055. * @return boolean
  1056. */
  1057. public function isInRootCategoryList($category)
  1058. {
  1059. $innerSelect = $this->_getReadAdapter()->select()
  1060. ->from($this->getMainStoreTable($category->getStoreId()), new Zend_Db_Expr("CONCAT(path, '/%')"))
  1061. ->where('entity_id = ?', Mage::app()->getStore()->getRootCategoryId());
  1062. $select = $this->_getReadAdapter()->select()
  1063. ->from($this->getMainStoreTable($category->getStoreId()), 'entity_id')
  1064. ->where('entity_id = ?', $category->getId())
  1065. ->where(new Zend_Db_Expr("path LIKE ({$innerSelect->__toString()})"));
  1066. return (bool) $this->_getReadAdapter()->fetchOne($select);
  1067. }
  1068. /**
  1069. * Return children ids of category
  1070. *
  1071. * @param Mage_Catalog_Model_Category $category
  1072. * @param integer $level
  1073. * @return array
  1074. */
  1075. public function getChildren($category, $recursive = true, $isActive = true)
  1076. {
  1077. $select = $this->_getReadAdapter()->select()
  1078. ->from($this->getMainStoreTable($category->getStoreId()), 'entity_id')
  1079. ->where('path LIKE ?', "{$category->getPath()}/%");
  1080. if (!$recursive) {
  1081. $select->where('level <= ?', $category->getLevel() + 1);
  1082. }
  1083. if ($isActive) {
  1084. $select->where('is_active = ?', '1');
  1085. }
  1086. $_categories = $this->_getReadAdapter()->fetchAll($select);
  1087. $categoriesIds = array();
  1088. foreach ($_categories as $_category) {
  1089. $categoriesIds[] = $_category['entity_id'];
  1090. }
  1091. return $categoriesIds;
  1092. }
  1093. /**
  1094. * Return all children ids of category (with category id)
  1095. *
  1096. * @param Mage_Catalog_Model_Category $category
  1097. * @return array
  1098. */
  1099. public function getAllChildren($category)
  1100. {
  1101. $categoriesIds = $this->getChildren($category);
  1102. $myId = array($category->getId());
  1103. $categoriesIds = array_merge($myId, $categoriesIds);
  1104. return $categoriesIds;
  1105. }
  1106. /**
  1107. * Check if category id exist
  1108. *
  1109. * @param int $id
  1110. * @return bool
  1111. */
  1112. public function checkId($id)
  1113. {
  1114. $select = $this->_getReadAdapter()->select()
  1115. ->from($this->getMainStoreTable($this->getStoreId()), 'entity_id')
  1116. ->where('entity_id=?', $id);
  1117. return $this->_getReadAdapter()->fetchOne($select);
  1118. }
  1119. /**
  1120. * Get design update data of parent categories
  1121. *
  1122. * @param Mage_Catalog_Model_Category $category
  1123. * @return array
  1124. */
  1125. public function getDesignUpdateData($category)
  1126. {
  1127. $categories = array();
  1128. $pathIds = array();
  1129. foreach (array_reverse($category->getParentIds()) as $pathId) {
  1130. if ($pathId == Mage::app()->getStore()->getRootCategoryId()) {
  1131. $pathIds[] = $pathId;
  1132. break;
  1133. }
  1134. $pathIds[] = $pathId;
  1135. }
  1136. $select = $this->_getReadAdapter()->select()
  1137. ->from(
  1138. array('main_table' => $this->getMainStoreTable($category->getStoreId())),
  1139. array(
  1140. 'main_table.entity_id',
  1141. 'main_table.custom_design',
  1142. 'main_table.custom_design_from',
  1143. 'main_table.custom_design_to',
  1144. )
  1145. )
  1146. ->where('main_table.entity_id IN (?)', $pathIds)
  1147. ->where('main_table.is_active = ?', '1')
  1148. ->order('main_table.path DESC');
  1149. $result = $this->_getReadAdapter()->fetchAll($select);
  1150. foreach ($result as $row) {
  1151. $row['id'] = $row['entity_id'];
  1152. $categories[$row['entity_id']] = Mage::getModel('catalog/category')->setData($row);
  1153. }
  1154. return $categories;
  1155. }
  1156. /**
  1157. * Retrieve anchors above
  1158. *
  1159. * @param array $filterIds
  1160. * @param int $storeId
  1161. * @return array
  1162. */
  1163. public function getAnchorsAbove(array $filterIds, $storeId = 0)
  1164. {
  1165. $select = $this->_getReadAdapter()->select()
  1166. ->from(array('e' => $this->getMainStoreTable($storeId)), 'entity_id')
  1167. ->where('is_anchor = ?', 1)
  1168. ->where('entity_id IN (?)', $filterIds);
  1169. return $this->_getReadAdapter()->fetchCol($select);
  1170. }
  1171. /**
  1172. * Retrieve array with root category id per store
  1173. *
  1174. * @param int|array $storeIds result limitation
  1175. * @return array
  1176. */
  1177. public function getStoresRootCategories($storeIds = null)
  1178. {
  1179. if (is_null($this->_storesRootCategories)) {
  1180. $select = $this->_getWriteAdapter()->select()
  1181. ->from(array('cs' => $this->getTable('core/store')), array('store_id'))
  1182. ->join(
  1183. array('csg' => $this->getTable('core/store_group')),
  1184. 'csg.group_id = cs.group_id',
  1185. array('root_category_id'))
  1186. ->where('cs.store_id <> ?', 0);
  1187. $this->_storesRootCategories = $this->_getWriteAdapter()->fetchPairs($select);
  1188. }
  1189. if (!is_null($storeIds)) {
  1190. if (!is_array($storeIds)) {
  1191. $storeIds = array($storeIds);
  1192. }
  1193. $stores = array();
  1194. foreach ($this->_storesRootCategories as $storeId => $rootId) {
  1195. if (in_array($storeId, $storeIds)) {
  1196. $stores[$storeId] = $rootId;
  1197. }
  1198. }
  1199. return $stores;
  1200. }
  1201. return $this->_storesRootCategories;
  1202. }
  1203. }