PageRenderTime 52ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

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

https://bitbucket.org/andrewjleavitt/magestudy
PHP | 1186 lines | 692 code | 134 blank | 360 comment | 44 complexity | 2109ed716d448bb624ead1709fefa609 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. * Catalog category 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 extends Mage_Catalog_Model_Resource_Eav_Mysql4_Abstract
  34. {
  35. /**
  36. * Category tree object
  37. *
  38. * @var Varien_Data_Tree_Db
  39. */
  40. protected $_tree;
  41. /**
  42. * Catalog products table name
  43. *
  44. * @var string
  45. */
  46. protected $_categoryProductTable;
  47. /**
  48. * Id of 'is_active' category attribute
  49. *
  50. * @var int
  51. */
  52. protected $_isActiveAttributeId = null;
  53. /**
  54. * Store id
  55. *
  56. * @var int
  57. */
  58. protected $_storeId = null;
  59. /**
  60. * Class constructor
  61. */
  62. public function __construct()
  63. {
  64. $resource = Mage::getSingleton('core/resource');
  65. $this->setType('catalog_category')
  66. ->setConnection(
  67. $resource->getConnection('catalog_read'),
  68. $resource->getConnection('catalog_write')
  69. );
  70. $this->_categoryProductTable = $this->getTable('catalog/category_product');
  71. }
  72. /**
  73. * Set store Id
  74. *
  75. * @param integer $storeId
  76. * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Category
  77. */
  78. public function setStoreId($storeId)
  79. {
  80. $this->_storeId = $storeId;
  81. return $this;
  82. }
  83. /**
  84. * Return store id
  85. *
  86. * @return integer
  87. */
  88. public function getStoreId()
  89. {
  90. if (is_null($this->_storeId)) {
  91. return Mage::app()->getStore()->getId();
  92. }
  93. return $this->_storeId;
  94. }
  95. /**
  96. * Retrieve category tree object
  97. *
  98. * @return Varien_Data_Tree_Db
  99. */
  100. protected function _getTree()
  101. {
  102. if (!$this->_tree) {
  103. $this->_tree = Mage::getResourceModel('catalog/category_tree')
  104. ->load();
  105. }
  106. return $this->_tree;
  107. }
  108. /**
  109. * Process category data before delete
  110. * update children count for parent category
  111. * delete child categories
  112. *
  113. * @param Varien_Object $object
  114. * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Category
  115. */
  116. protected function _beforeDelete(Varien_Object $object)
  117. {
  118. parent::_beforeDelete($object);
  119. /**
  120. * Update children count for all parent categories
  121. */
  122. $parentIds = $object->getParentIds();
  123. $childDecrease = $object->getChildrenCount() + 1; // +1 is itself
  124. $this->_getWriteAdapter()->update(
  125. $this->getEntityTable(),
  126. array('children_count'=>new Zend_Db_Expr('`children_count`-'.$childDecrease)),
  127. $this->_getWriteAdapter()->quoteInto('entity_id IN(?)', $parentIds)
  128. );
  129. $this->deleteChildren($object);
  130. return $this;
  131. }
  132. /**
  133. * Delete children categories of specific category
  134. *
  135. * @param Varien_Object $object
  136. * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Category
  137. */
  138. public function deleteChildren(Varien_Object $object)
  139. {
  140. /**
  141. * Recursion use a lot of memmory, that why we run one request for delete children
  142. */
  143. /*if ($child = $this->_getTree()->getNodeById($object->getId())) {
  144. $children = $child->getChildren();
  145. foreach ($children as $child) {
  146. $childObject = Mage::getModel('catalog/category')->load($child->getId())->delete();
  147. }
  148. }*/
  149. $select = $this->_getWriteAdapter()->select()
  150. ->from($this->getEntityTable(), array('entity_id'))
  151. ->where($this->_getWriteAdapter()->quoteInto('`path` LIKE ?', $object->getPath().'/%'));
  152. $childrenIds = $this->_getWriteAdapter()->fetchCol($select);
  153. if (!empty($childrenIds)) {
  154. $this->_getWriteAdapter()->delete(
  155. $this->getEntityTable(),
  156. $this->_getWriteAdapter()->quoteInto('entity_id IN (?)', $childrenIds)
  157. );
  158. }
  159. /**
  160. * Add deleted children ids to object
  161. * This data can be used in after delete event
  162. */
  163. $object->setDeletedChildrenIds($childrenIds);
  164. return $this;
  165. }
  166. /**
  167. * Process category data before saving
  168. * prepare path and increment children count for parent categories
  169. *
  170. * @param Varien_Object $object
  171. * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Category
  172. */
  173. protected function _beforeSave(Varien_Object $object)
  174. {
  175. parent::_beforeSave($object);
  176. if (!$object->getId()) {
  177. $object->setPosition($this->_getMaxPosition($object->getPath()) + 1);
  178. $path = explode('/', $object->getPath());
  179. $level = count($path);
  180. $object->setLevel($level);
  181. if ($level) {
  182. $object->setParentId($path[$level - 1]);
  183. }
  184. $object->setPath($object->getPath() . '/');
  185. $toUpdateChild = explode('/',$object->getPath());
  186. $this->_getWriteAdapter()->update(
  187. $this->getEntityTable(),
  188. array('children_count'=>new Zend_Db_Expr('`children_count`+1')),
  189. $this->_getWriteAdapter()->quoteInto('entity_id IN(?)', $toUpdateChild)
  190. );
  191. }
  192. return $this;
  193. }
  194. /**
  195. * Process category data after save category object
  196. * save related products ids and update path value
  197. *
  198. * @param Varien_Object $object
  199. * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Category
  200. */
  201. protected function _afterSave(Varien_Object $object)
  202. {
  203. /**
  204. * Add identifier for new category
  205. */
  206. if (substr($object->getPath(), -1) == '/') {
  207. $object->setPath($object->getPath() . $object->getId());
  208. $this->_savePath($object);
  209. }
  210. $this->_saveCategoryProducts($object);
  211. return parent::_afterSave($object);
  212. }
  213. /**
  214. * Update path field
  215. *
  216. * @param Mage_Catalog_Model_Category $object
  217. * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Category
  218. */
  219. protected function _savePath($object)
  220. {
  221. if ($object->getId()) {
  222. $this->_getWriteAdapter()->update(
  223. $this->getEntityTable(),
  224. array('path'=>$object->getPath()),
  225. $this->_getWriteAdapter()->quoteInto('entity_id=?', $object->getId())
  226. );
  227. }
  228. return $this;
  229. }
  230. /**
  231. * Get maximum position of child categories by specific tree path
  232. *
  233. * @param string $path
  234. * @return int
  235. */
  236. protected function _getMaxPosition($path)
  237. {
  238. $select = $this->getReadConnection()->select();
  239. $select->from($this->getTable('catalog/category'), 'MAX(position)');
  240. $select->where('path ?', new Zend_Db_Expr("regexp '{$path}/[0-9]+\$'"));
  241. $result = 0;
  242. try {
  243. $result = (int) $this->getReadConnection()->fetchOne($select);
  244. } catch (Exception $e) {
  245. }
  246. return $result;
  247. }
  248. /**
  249. * Save category products relation
  250. *
  251. * @param Mage_Catalog_Model_Category $category
  252. * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Category
  253. */
  254. protected function _saveCategoryProducts($category)
  255. {
  256. $category->setIsChangedProductList(false);
  257. $id = $category->getId();
  258. /**
  259. * new category-product relationships
  260. */
  261. $products = $category->getPostedProducts();
  262. /**
  263. * Example re-save category
  264. */
  265. if (is_null($products)) {
  266. return $this;
  267. }
  268. /**
  269. * old category-product relationships
  270. */
  271. $oldProducts = $category->getProductsPosition();
  272. $insert = array_diff_key($products, $oldProducts);
  273. $delete = array_diff_key($oldProducts, $products);
  274. /**
  275. * Find product ids which are presented in both arrays
  276. * and saved before (check $oldProducts array)
  277. */
  278. $update = array_intersect_key($products, $oldProducts);
  279. $update = array_diff_assoc($update, $oldProducts);
  280. $adapter = $this->_getWriteAdapter();
  281. /**
  282. * Delete products from category
  283. */
  284. if (!empty($delete)) {
  285. $cond = $adapter->quoteInto('product_id IN(?) AND ', array_keys($delete))
  286. . $adapter->quoteInto('category_id=?', $id);
  287. $adapter->delete($this->_categoryProductTable, $cond);
  288. }
  289. /**
  290. * Add products to category
  291. */
  292. if (!empty($insert)) {
  293. $data = array();
  294. foreach ($insert as $productId => $position) {
  295. $data[] = array(
  296. 'category_id' => $id,
  297. 'product_id' => (int)$productId,
  298. 'position' => (int)$position
  299. );
  300. }
  301. $adapter->insertMultiple($this->_categoryProductTable, $data);
  302. }
  303. /**
  304. * Update product positions in category
  305. */
  306. if (!empty($update)) {
  307. foreach ($update as $productId => $position) {
  308. $where = $adapter->quoteInto('category_id=? AND ', (int)$id)
  309. . $adapter->quoteInto('product_id=?', (int)$productId);
  310. $bind = array('position' => (int)$position);
  311. $adapter->update($this->_categoryProductTable, $bind, $where);
  312. }
  313. }
  314. if (!empty($insert) || !empty($delete)) {
  315. $productIds = array_unique(array_merge(array_keys($insert), array_keys($delete)));
  316. Mage::dispatchEvent('catalog_category_change_products', array(
  317. 'category' => $category,
  318. 'product_ids' => $productIds
  319. ));
  320. }
  321. if (!empty($insert) || !empty($update) || !empty($delete)) {
  322. $category->setIsChangedProductList(true);
  323. /**
  324. * Moved to index
  325. */
  326. //$categoryIds = explode('/', $category->getPath());
  327. //$this->refreshProductIndex($categoryIds);
  328. /**
  329. * Setting affected products to category for third party engine index refresh
  330. */
  331. $productIds = array_keys($insert + $delete + $update);
  332. $category->setAffectedProductIds($productIds);
  333. }
  334. return $this;
  335. }
  336. /**
  337. * Get positions of associated to category products
  338. *
  339. * @param Mage_Catalog_Model_Category $category
  340. * @return array
  341. */
  342. public function getProductsPosition($category)
  343. {
  344. $select = $this->_getWriteAdapter()->select()
  345. ->from($this->_categoryProductTable, array('product_id', 'position'))
  346. ->where('category_id=?', $category->getId());
  347. $positions = $this->_getWriteAdapter()->fetchPairs($select);
  348. return $positions;
  349. }
  350. /**
  351. * Get chlden categories count
  352. *
  353. * @param int $categoryId
  354. * @return int
  355. */
  356. public function getChildrenCount($categoryId)
  357. {
  358. $select = $this->_getReadAdapter()->select()
  359. ->from($this->getEntityTable(), 'children_count')
  360. ->where('entity_id=?', $categoryId);
  361. $child = $this->_getReadAdapter()->fetchOne($select);
  362. return $child;
  363. }
  364. /**
  365. * Check if category id exist
  366. *
  367. * @param int $id
  368. * @return bool
  369. */
  370. public function checkId($id)
  371. {
  372. $select = $this->_getReadAdapter()->select()
  373. ->from($this->getEntityTable(), 'entity_id')
  374. ->where('entity_id=?', $id);
  375. return $this->_getReadAdapter()->fetchOne($select);
  376. }
  377. /**
  378. * Check array of category identifiers
  379. *
  380. * @param array $ids
  381. * @return array
  382. */
  383. public function verifyIds(array $ids)
  384. {
  385. $validIds = array();
  386. $select = $this->_getWriteAdapter()->select()
  387. ->from($this->getEntityTable(), 'entity_id')
  388. ->where('entity_id IN(?)', $ids);
  389. $query = $this->_getWriteAdapter()->query($select);
  390. while ($row = $query->fetch()) {
  391. $validIds[] = $row['entity_id'];
  392. }
  393. return $validIds;
  394. }
  395. /**
  396. * Get count of active/not active children categories
  397. *
  398. * @param Mage_Catalog_Model_Category $category
  399. * @param bool $isActiveFlag
  400. * @return int
  401. */
  402. public function getChildrenAmount($category, $isActiveFlag = true)
  403. {
  404. $storeId = Mage::app()->getStore()->getId();
  405. $attributeId = $this->_getIsActiveAttributeId();
  406. $table = Mage::getSingleton('core/resource')->getTableName('catalog/category') . '_int';
  407. $select = $this->_getReadAdapter()->select()
  408. ->from(array('m'=>$this->getEntityTable()), array('COUNT(m.entity_id)'))
  409. ->joinLeft(
  410. array('d'=>$table),
  411. "d.attribute_id = '{$attributeId}' AND d.store_id = 0 AND d.entity_id = m.entity_id",
  412. array()
  413. )
  414. ->joinLeft(
  415. array('c'=>$table),
  416. "c.attribute_id = '{$attributeId}' AND c.store_id = '{$storeId}' AND c.entity_id = m.entity_id",
  417. array()
  418. )
  419. ->where('m.path like ?', $category->getPath() . '/%')
  420. ->where('(IF(c.value_id>0, c.value, d.value) = ?)', $isActiveFlag);
  421. return $this->_getReadAdapter()->fetchOne($select);
  422. }
  423. /**
  424. * Get "is_active" attribute identifier
  425. *
  426. * @return int
  427. */
  428. protected function _getIsActiveAttributeId()
  429. {
  430. if (is_null($this->_isActiveAttributeId)) {
  431. $select = $this->_getReadAdapter()->select()
  432. ->from(array('a'=>$this->getTable('eav/attribute')), array('attribute_id'))
  433. ->join(array('t'=>$this->getTable('eav/entity_type')), 'a.entity_type_id = t.entity_type_id')
  434. ->where('entity_type_code = ?', 'catalog_category')
  435. ->where('attribute_code = ?', 'is_active');
  436. $this->_isActiveAttributeId = $this->_getReadAdapter()->fetchOne($select);
  437. }
  438. return $this->_isActiveAttributeId;
  439. }
  440. public function findWhereAttributeIs($entityIdsFilter, $attribute, $expectedValue)
  441. {
  442. $select = $this->_getReadAdapter()->select()
  443. ->from($attribute->getBackend()->getTable(), array('entity_id'))
  444. ->where('attribute_id = ?', $attribute->getId())
  445. ->where('value = ?', $expectedValue)
  446. ->where('entity_id in (?)', $entityIdsFilter);
  447. return $this->_getReadAdapter()->fetchCol($select);
  448. }
  449. /**
  450. * Get products count in category
  451. *
  452. * @param unknown_type $category
  453. * @return unknown
  454. */
  455. public function getProductCount($category)
  456. {
  457. $productTable =Mage::getSingleton('core/resource')->getTableName('catalog/category_product');
  458. $select = $this->getReadConnection()->select();
  459. $select->from(
  460. array('main_table'=>$productTable),
  461. array(new Zend_Db_Expr('COUNT(main_table.product_id)'))
  462. )
  463. ->where('main_table.category_id = ?', $category->getId())
  464. ->group('main_table.category_id');
  465. $counts =$this->getReadConnection()->fetchOne($select);
  466. return intval($counts);
  467. }
  468. /**
  469. * Retrieve categories
  470. *
  471. * @param integer $parent
  472. * @param integer $recursionLevel
  473. * @param boolean|string $sorted
  474. * @param boolean $asCollection
  475. * @param boolean $toLoad
  476. * @return Varien_Data_Tree_Node_Collection|Mage_Catalog_Model_Resource_Eav_Mysql4_Category_Collection
  477. */
  478. public function getCategories($parent, $recursionLevel = 0, $sorted=false, $asCollection=false, $toLoad=true)
  479. {
  480. $tree = Mage::getResourceModel('catalog/category_tree');
  481. /** @var $tree Mage_Catalog_Model_Resource_Eav_Mysql4_Category_Tree */
  482. $nodes = $tree->loadNode($parent)
  483. ->loadChildren($recursionLevel)
  484. ->getChildren();
  485. $tree->addCollectionData(null, $sorted, $parent, $toLoad, true);
  486. if ($asCollection) {
  487. return $tree->getCollection();
  488. }
  489. return $nodes;
  490. }
  491. /**
  492. * Return parent categories of category
  493. *
  494. * @param Mage_Catalog_Model_Category $category
  495. * @return array
  496. */
  497. public function getParentCategories($category)
  498. {
  499. $pathIds = array_reverse(explode(',', $category->getPathInStore()));
  500. $categories = Mage::getResourceModel('catalog/category_collection')
  501. ->setStore(Mage::app()->getStore())
  502. ->addAttributeToSelect('name')
  503. ->addAttributeToSelect('url_key')
  504. ->addFieldToFilter('entity_id', array('in'=>$pathIds))
  505. ->addFieldToFilter('is_active', 1)
  506. ->load()
  507. ->getItems();
  508. return $categories;
  509. }
  510. /**
  511. * Return parent category of current category with own custom design settings
  512. *
  513. * @param Mage_Catalog_Model_Category $category
  514. * @return Mage_Catalog_Model_Category
  515. */
  516. public function getParentDesignCategory($category)
  517. {
  518. $pathIds = array_reverse($category->getPathIds());
  519. $collection = $category->getCollection()
  520. ->setStore(Mage::app()->getStore())
  521. ->addAttributeToSelect('custom_design')
  522. ->addAttributeToSelect('custom_design_from')
  523. ->addAttributeToSelect('custom_design_to')
  524. ->addAttributeToSelect('page_layout')
  525. ->addAttributeToSelect('custom_layout_update')
  526. ->addAttributeToSelect('custom_apply_to_products')
  527. ->addFieldToFilter('entity_id', array('in' => $pathIds))
  528. ->addFieldToFilter('custom_use_parent_settings', 0)
  529. ->addFieldToFilter('level', array('neq' => 0))
  530. ->setOrder('level', 'DESC')
  531. ->load();
  532. return $collection->getFirstItem();
  533. }
  534. /**
  535. * Return child categories
  536. *
  537. * @param Mage_Catalog_Model_Category $category
  538. * @return unknown
  539. */
  540. public function getChildrenCategories($category)
  541. {
  542. $collection = $category->getCollection();
  543. /* @var $collection Mage_Catalog_Model_Resource_Eav_Mysql4_Category_Collection */
  544. $collection->addAttributeToSelect('url_key')
  545. ->addAttributeToSelect('name')
  546. ->addAttributeToSelect('all_children')
  547. ->addAttributeToSelect('is_anchor')
  548. ->addAttributeToFilter('is_active', 1)
  549. ->addIdFilter($category->getChildren())
  550. ->setOrder('position', 'ASC')
  551. ->joinUrlRewrite()
  552. ->load();
  553. return $collection;
  554. }
  555. /**
  556. * Return children ids of category
  557. *
  558. * @param Mage_Catalog_Model_Category $category
  559. * @param boolean $recursive
  560. * @return array
  561. */
  562. public function getChildren($category, $recursive = true)
  563. {
  564. $attributeId = $this->_getIsActiveAttributeId();
  565. $select = $this->_getReadAdapter()->select()
  566. ->from(array('m' => $this->getEntityTable()), 'entity_id')
  567. ->joinLeft(
  568. array('d' => $this->getEntityTable() . '_int'),
  569. "d.attribute_id = '{$attributeId}' AND d.store_id = 0 AND d.entity_id = m.entity_id",
  570. array()
  571. )
  572. ->joinLeft(
  573. array('c' => $this->getEntityTable() . '_int'),
  574. "c.attribute_id = '{$attributeId}' AND c.store_id = '{$category->getStoreId()}' AND c.entity_id = m.entity_id",
  575. array()
  576. )
  577. ->where('(IF(c.value_id>0, c.value, d.value) = ?)', '1')
  578. ->where('path LIKE ?', "{$category->getPath()}/%");
  579. if (!$recursive) {
  580. $select->where('level <= ?', $category->getLevel() + 1);
  581. }
  582. $_categories = $this->_getReadAdapter()->fetchAll($select);
  583. $categoriesIds = array();
  584. foreach ($_categories as $_category) {
  585. $categoriesIds[] = $_category['entity_id'];
  586. }
  587. return $categoriesIds;
  588. }
  589. /**
  590. * Return all children ids of category (with category id)
  591. *
  592. * @param Mage_Catalog_Model_Category $category
  593. * @return array
  594. */
  595. public function getAllChildren($category)
  596. {
  597. $children = $this->getChildren($category);
  598. $myId = array($category->getId());
  599. $children = array_merge($myId, $children);
  600. return $children;
  601. }
  602. /**
  603. * Check is category in list of store categories
  604. *
  605. * @param Mage_Catalog_Model_Category $category
  606. * @return boolean
  607. */
  608. public function isInRootCategoryList($category)
  609. {
  610. $innerSelect = $this->_getReadAdapter()->select()
  611. ->from($this->getEntityTable(), new Zend_Db_Expr("CONCAT(path, '/%')"))
  612. ->where('entity_id = ?', Mage::app()->getStore()->getRootCategoryId());
  613. $select = $this->_getReadAdapter()->select()
  614. ->from($this->getEntityTable(), 'entity_id')
  615. ->where('entity_id = ?', $category->getId())
  616. ->where(new Zend_Db_Expr("path LIKE ({$innerSelect->__toString()})"));
  617. return (bool) $this->_getReadAdapter()->fetchOne($select);
  618. }
  619. /**
  620. * Check category is forbidden to delete.
  621. *
  622. * If category is root and assigned to store group return false
  623. *
  624. * @param integer $categoryId
  625. * @return boolean
  626. */
  627. public function isForbiddenToDelete($categoryId)
  628. {
  629. $select = $this->_getReadAdapter()->select()
  630. ->from($this->getTable('core/store_group'), array('group_id'))
  631. ->where('root_category_id = ?', $categoryId);
  632. if ($this->_getReadAdapter()->fetchOne($select)) {
  633. return true;
  634. }
  635. return false;
  636. }
  637. /**
  638. * Get category path value by its id
  639. *
  640. * @param int $categoryId
  641. * @return string
  642. */
  643. public function getCategoryPathById($categoryId)
  644. {
  645. $select = $this->getReadConnection()->select();
  646. $select->from($this->getEntityTable(), array('path'))
  647. ->where('entity_id = ?', $categoryId);
  648. return $this->getReadConnection()->fetchOne($select);
  649. }
  650. /**
  651. * Move category to another parent node
  652. *
  653. * @param Mage_Catalog_Model_Category $category
  654. * @param Mage_Catalog_Model_Category $newParent
  655. * @param null|int $afterCategoryId
  656. * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Category
  657. */
  658. public function changeParent(Mage_Catalog_Model_Category $category, Mage_Catalog_Model_Category $newParent, $afterCategoryId=null)
  659. {
  660. $childrenCount = $this->getChildrenCount($category->getId()) + 1;
  661. $table = $this->getEntityTable();
  662. $adapter = $this->_getWriteAdapter();
  663. $categoryId = $category->getId();
  664. /**
  665. * Decrease children count for all old category parent categories
  666. */
  667. $sql = "UPDATE {$table} SET children_count=children_count-{$childrenCount} WHERE entity_id IN(?)";
  668. $adapter->query($adapter->quoteInto($sql, $category->getParentIds()));
  669. /**
  670. * Increase children count for new category parents
  671. */
  672. $sql = "UPDATE {$table} SET children_count=children_count+{$childrenCount} WHERE entity_id IN(?)";
  673. $adapter->query($adapter->quoteInto($sql, $newParent->getPathIds()));
  674. $position = $this->_processPositions($category, $newParent, $afterCategoryId);
  675. $newPath = $newParent->getPath().'/'.$category->getId();
  676. $newLevel= $newParent->getLevel()+1;
  677. $levelDisposition = $newLevel - $category->getLevel();
  678. /**
  679. * Update children nodes path
  680. */
  681. $sql = "UPDATE {$table} SET
  682. `path` = REPLACE(`path`, '{$category->getPath()}/', '{$newPath}/'),
  683. `level` = `level` + {$levelDisposition}
  684. WHERE ". $adapter->quoteInto('path LIKE ?', $category->getPath().'/%');
  685. $adapter->query($sql);
  686. /**
  687. * Update moved category data
  688. */
  689. $data = array('path' => $newPath, 'level' => $newLevel,
  690. 'position'=>$position, 'parent_id'=>$newParent->getId());
  691. $adapter->update($table, $data, $adapter->quoteInto('entity_id=?', $category->getId()));
  692. // Update category object to new data
  693. $category->addData($data);
  694. return $this;
  695. }
  696. /**
  697. * Process positions of old parent category children and new parent category children.
  698. * Get position for moved category
  699. *
  700. * @param Mage_Catalog_Model_Category $category
  701. * @param Mage_Catalog_Model_Category $newParent
  702. * @param null|int $afterCategoryId
  703. * @return int
  704. */
  705. protected function _processPositions($category, $newParent, $afterCategoryId)
  706. {
  707. $table = $this->getEntityTable();
  708. $adapter = $this->_getWriteAdapter();
  709. $sql = "UPDATE {$table} SET `position`=`position`-1 WHERE "
  710. . $adapter->quoteInto('parent_id=? AND ', $category->getParentId())
  711. . $adapter->quoteInto('position>?', $category->getPosition());
  712. $adapter->query($sql);
  713. /**
  714. * Prepare position value
  715. */
  716. if ($afterCategoryId) {
  717. $sql = "SELECT `position` FROM {$table} WHERE entity_id=?";
  718. $position = $adapter->fetchOne($adapter->quoteInto($sql, $afterCategoryId));
  719. $sql = "UPDATE {$table} SET `position`=`position`+1 WHERE "
  720. . $adapter->quoteInto('parent_id=? AND ', $newParent->getId())
  721. . $adapter->quoteInto('position>?', $position);
  722. $adapter->query($sql);
  723. } elseif ($afterCategoryId !== null) {
  724. $position = 0;
  725. $sql = "UPDATE {$table} SET `position`=`position`+1 WHERE "
  726. . $adapter->quoteInto('parent_id=? AND ', $newParent->getId())
  727. . $adapter->quoteInto('position>?', $position);
  728. $adapter->query($sql);
  729. } else {
  730. $sql = "SELECT MIN(`position`) FROM {$table} WHERE parent_id=?";
  731. $position = $adapter->fetchOne($adapter->quoteInto($sql, $newParent->getId()));
  732. }
  733. $position+=1;
  734. return $position;
  735. }
  736. /**
  737. * @deprecated
  738. * @param Varien_Object $object
  739. * @return unknown
  740. */
  741. protected function _saveInStores(Varien_Object $object)
  742. {
  743. if (!$object->getMultistoreSaveFlag()) {
  744. $stores = $object->getStoreIds();
  745. foreach ($stores as $storeId) {
  746. if ($object->getStoreId() != $storeId) {
  747. $newObject = clone $object;
  748. $newObject->setStoreId($storeId)
  749. ->setMultistoreSaveFlag(true)
  750. ->save();
  751. }
  752. }
  753. }
  754. return $this;
  755. }
  756. /**
  757. * @deprecated
  758. */
  759. protected function _updateCategoryPath($category, $path)
  760. {
  761. return $this;
  762. if ($category->getNotUpdateDepends()) {
  763. return $this;
  764. }
  765. foreach ($path as $pathItem) {
  766. if ($pathItem->getId()>1 && $category->getId() != $pathItem->getId()) {
  767. $category = Mage::getModel('catalog/category')
  768. ->load($pathItem->getId())
  769. ->save();
  770. }
  771. }
  772. return $this;
  773. }
  774. /**
  775. * @deprecated since 1.1.7
  776. * @param Varien_Object $object
  777. * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Category
  778. */
  779. protected function _saveCountChidren($object)
  780. {
  781. $chidren = $object->getChildren();
  782. if (strlen($chidren)>0) {
  783. $chidrenCount = count(explode(',', $chidren));
  784. } else {
  785. $chidrenCount = 0;
  786. }
  787. $this->_getWriteAdapter()->update($this->getEntityTable(),
  788. array('children_count'=>$chidrenCount),
  789. $this->_getWriteAdapter()->quoteInto('entity_id=?', $object->getId())
  790. );
  791. return $this;
  792. }
  793. /**
  794. * Get store identifiers where category is presented
  795. *
  796. * @deprecated after 1.3.2.2 moved to model
  797. * @param Mage_Catalog_Model_Category $category
  798. * @return array
  799. */
  800. public function getStoreIds($category)
  801. {
  802. if (!$category->getId()) {
  803. return array();
  804. }
  805. $nodes = array();
  806. foreach ($category->getPathIds() as $id) {
  807. $nodes[] = $id;
  808. }
  809. $stores = array();
  810. $storeCollection = Mage::getModel('core/store')->getCollection()->loadByCategoryIds($nodes);
  811. foreach ($storeCollection as $store) {
  812. $stores[$store->getId()] = $store->getId();
  813. }
  814. $entityStoreId = $category->getStoreId();
  815. if (!in_array($entityStoreId, $stores)) {
  816. array_unshift($stores, $entityStoreId);
  817. }
  818. if (!in_array(0, $stores)) {
  819. array_unshift($stores, 0);
  820. }
  821. return $stores;
  822. }
  823. /**
  824. * Move category to another parent
  825. * @deprecated after 1.4.0.0-Alpha we are using changeParent method
  826. * @param int $categoryId
  827. * @param int $newParentId
  828. * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Category
  829. */
  830. public function move($categoryId, $newParentId)
  831. {
  832. $category = Mage::getModel('catalog/category')->load($categoryId);
  833. $oldParent = $category->getParentCategory();
  834. $newParent = Mage::getModel('catalog/category')->load($newParentId);
  835. $childrenCount = $this->getChildrenCount($category->getId()) + 1;
  836. // update children count of new parents
  837. $parentIds = explode('/', $newParent->getPath());
  838. $this->_getWriteAdapter()->update(
  839. $this->getEntityTable(),
  840. array('children_count' => new Zend_Db_Expr("`children_count` + {$childrenCount}")),
  841. $this->_getWriteAdapter()->quoteInto('entity_id IN (?)', $parentIds)
  842. );
  843. // update children count of old parents
  844. $parentIds = explode('/', $oldParent->getPath());
  845. $this->_getWriteAdapter()->update(
  846. $this->getEntityTable(),
  847. array('children_count' => new Zend_Db_Expr("`children_count` - {$childrenCount}")),
  848. $this->_getWriteAdapter()->quoteInto('entity_id IN (?)', $parentIds)
  849. );
  850. // update parent id
  851. $this->_getWriteAdapter()->query("UPDATE
  852. {$this->getEntityTable()} SET parent_id = {$newParent->getId()}
  853. WHERE entity_id = {$categoryId}");
  854. return $this;
  855. }
  856. /**
  857. * Rebuild associated products index
  858. *
  859. * @deprecated after 1.4.0.0-Alpha, functionality moved to Mage_Catalog_Model_Category_Indexer_Produxt
  860. * @param array $categoryIds
  861. * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Category
  862. */
  863. public function refreshProductIndex($categoryIds = array(), $productIds = array(), $storeIds = array())
  864. {
  865. /**
  866. * Prepare visibility and status attributes information
  867. */
  868. $statusAttribute = Mage::getSingleton('eav/config')->getAttribute('catalog_product', 'status');
  869. $visibilityAttribute = Mage::getSingleton('eav/config')->getAttribute('catalog_product', 'visibility');
  870. $statusAttributeId = $statusAttribute->getId();
  871. $visibilityAttributeId = $visibilityAttribute->getId();
  872. $statusTable = $statusAttribute->getBackend()->getTable();
  873. $visibilityTable = $visibilityAttribute->getBackend()->getTable();
  874. /**
  875. * Select categories data
  876. */
  877. $select = $this->_getReadAdapter()->select()
  878. ->from($this->getTable('catalog/category'))
  879. ->order('level')
  880. ->order('path');
  881. if (is_array($categoryIds) && !empty($categoryIds)) {
  882. $select->where('entity_id IN (?)', $categoryIds);
  883. } elseif (is_numeric($categoryIds)) {
  884. $select->where('entity_id=?', $categoryIds);
  885. }
  886. $categories = $this->_getWriteAdapter()->fetchAll($select);
  887. $storesCondition = '';
  888. if (!empty($storeIds)) {
  889. $storesCondition = $this->_getWriteAdapter()->quoteInto(
  890. ' AND s.store_id IN (?)', $storeIds
  891. );
  892. }
  893. /**
  894. * Get information about stores root categories
  895. */
  896. $stores = $this->_getWriteAdapter()->fetchAll("
  897. SELECT
  898. s.store_id, s.website_id, c.path AS root_path
  899. FROM
  900. {$this->getTable('core/store')} AS s,
  901. {$this->getTable('core/store_group')} AS sg,
  902. {$this->getTable('catalog/category')} AS c
  903. WHERE
  904. sg.group_id=s.group_id
  905. AND c.entity_id=sg.root_category_id
  906. {$storesCondition}
  907. ");
  908. $indexTable = $this->getTable('catalog/category_product_index');
  909. foreach ($stores as $storeData) {
  910. $storeId = $storeData['store_id'];
  911. $websiteId = $storeData['website_id'];
  912. $rootPath = $storeData['root_path'];
  913. $productCondition = '';
  914. if (!empty($productIds)) {
  915. $productCondition = $this->_getWriteAdapter()->quoteInto(
  916. ' AND product_id IN (?)', $productIds
  917. );
  918. }
  919. $insProductCondition = str_replace('product_id', 'cp.product_id', $productCondition);
  920. foreach ($categories as $category) {
  921. $categoryId = $category['entity_id'];
  922. $path = $category['path'];
  923. $this->_getWriteAdapter()->delete(
  924. $indexTable,
  925. 'category_id='.$categoryId. ' AND store_id='.$storeId.$productCondition
  926. );
  927. if (strpos($path.'/', $rootPath.'/') === false) {
  928. continue;
  929. }
  930. $query = "INSERT INTO {$indexTable}
  931. (`category_id`, `product_id`, `position`, `is_parent`, `store_id`, `visibility`)
  932. SELECT
  933. {$categoryId},
  934. cp.product_id,
  935. cp.position,
  936. MAX({$categoryId}=cp.category_id) as is_parent,
  937. {$storeId},
  938. IF(t_v.value_id>0, t_v.value, t_v_default.value)
  939. FROM
  940. {$this->getTable('catalog/category_product')} AS cp
  941. INNER JOIN {$this->getTable('catalog/product_website')} AS pw
  942. ON pw.product_id=cp.product_id AND pw.website_id={$websiteId}
  943. INNER JOIN {$visibilityTable} AS `t_v_default`
  944. ON (t_v_default.entity_id = cp.product_id)
  945. AND (t_v_default.attribute_id='{$visibilityAttributeId}')
  946. AND t_v_default.store_id=0
  947. LEFT JOIN {$visibilityTable} AS `t_v`
  948. ON (t_v.entity_id = cp.product_id)
  949. AND (t_v.attribute_id='{$visibilityAttributeId}')
  950. AND (t_v.store_id='{$storeId}')
  951. INNER JOIN {$statusTable} AS `t_s_default`
  952. ON (t_s_default.entity_id = cp.product_id)
  953. AND (t_s_default.attribute_id='{$statusAttributeId}')
  954. AND t_s_default.store_id=0
  955. LEFT JOIN {$statusTable} AS `t_s`
  956. ON (t_s.entity_id = cp.product_id)
  957. AND (t_s.attribute_id='{$statusAttributeId}')
  958. AND (t_s.store_id='{$storeId}')
  959. WHERE category_id IN(
  960. SELECT entity_id FROM {$this->getTable('catalog/category')}
  961. WHERE entity_id = {$category['entity_id']} OR path LIKE '{$path}/%')
  962. AND (IF(t_s.value_id>0, t_s.value, t_s_default.value)=".Mage_Catalog_Model_Product_Status::STATUS_ENABLED.")
  963. {$insProductCondition}
  964. GROUP BY product_id
  965. ORDER BY is_parent desc";
  966. $this->_getWriteAdapter()->query($query);
  967. }
  968. $this->_refreshRootCategoryProductIndex($productIds, array($storeId));
  969. }
  970. return $this;
  971. }
  972. /**
  973. * Refresh Category Product Index for Store Root Catgory
  974. *
  975. * @deprecated after 1.4.0.0-Alpha, functionality moved to Mage_Catalog_Model_Category_Indexer_Produxt
  976. * @param array|int $productIds
  977. * @param array|int $storeIds
  978. * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Category
  979. */
  980. protected function _refreshRootCategoryProductIndex($productIds = array(), $storeIds = array())
  981. {
  982. if (is_numeric($storeIds)) {
  983. $storeIds = array($storeIds);
  984. }
  985. elseif (!is_array($storeIds) || empty($storeIds)) {
  986. $storeIds = array();
  987. foreach (Mage::app()->getStores() as $store) {
  988. $storeIds[] = $store->getId();
  989. }
  990. }
  991. /**
  992. * Prepare visibility and status attributes information
  993. */
  994. $status = Mage::getSingleton('eav/config')->getAttribute('catalog_product', 'status');
  995. $visibility = Mage::getSingleton('eav/config')->getAttribute('catalog_product', 'visibility');
  996. $statusTable = $status->getBackend()->getTable();
  997. $visibilityTable = $visibility->getBackend()->getTable();
  998. $indexTable = $this->getTable('catalog/category_product_index');
  999. foreach ($storeIds as $storeId) {
  1000. $store = Mage::app()->getStore($storeId);
  1001. $categoryId = $store->getRootCategoryId();
  1002. $select = $this->_getWriteAdapter()->select()
  1003. ->from(array('e' => $this->getTable('catalog/product')), null)
  1004. ->joinLeft(
  1005. array('i' => $indexTable),
  1006. 'e.entity_id=i.product_id AND i.category_id=' . (int)$categoryId
  1007. . ' AND i.store_id=' . (int) $storeId,
  1008. array())
  1009. ->joinInner(
  1010. array('pw' => $this->getTable('catalog/product_website')),
  1011. 'e.entity_id=pw.product_id AND pw.website_id=' . (int)$store->getWebsiteId(),
  1012. array())
  1013. ->join(
  1014. array('t_v_default' => $visibilityTable),
  1015. 't_v_default.entity_id=e.entity_id'
  1016. . ' AND t_v_default.attribute_id=' . (int)$visibility->getAttributeId()
  1017. . ' AND t_v_default.store_id=0',
  1018. array())
  1019. ->joinLeft(
  1020. array('t_v' => $visibilityTable),
  1021. 't_v.entity_id=e.entity_id'
  1022. . ' AND t_v.attribute_id=' . (int)$visibility->getAttributeId()
  1023. . ' AND t_v.store_id='. (int)$storeId,
  1024. array())
  1025. ->join(
  1026. array('t_s_default' => $statusTable),
  1027. 't_s_default.entity_id=e.entity_id'
  1028. . ' AND t_s_default.attribute_id=' . (int)$status->getAttributeId()
  1029. . ' AND t_s_default.store_id=0',
  1030. array())
  1031. ->joinLeft(
  1032. array('t_s' => $statusTable),
  1033. 't_s.entity_id=e.entity_id'
  1034. . ' AND t_s.attribute_id=' . (int)$status->getAttributeId()
  1035. . ' AND t_s.store_id='. (int)$storeId,
  1036. array())
  1037. ->where('i.product_id IS NULL')
  1038. ->where('IF(t_s.value_id>0, t_s.value, t_s_default.value)=?', Mage_Catalog_Model_Product_Status::STATUS_ENABLED);
  1039. $select->columns(new Zend_Db_Expr($categoryId));
  1040. $select->columns('e.entity_id');
  1041. $select->columns(new Zend_Db_Expr(0));
  1042. $select->columns(new Zend_Db_Expr(0));
  1043. $select->columns(new Zend_Db_Expr($storeId));
  1044. $select->columns(new Zend_Db_Expr('IF(t_v.value_id>0, t_v.value, t_v_default.value)'));
  1045. if (!empty($productIds)) {
  1046. $select->where('e.entity_id IN(?)', $productIds);
  1047. }
  1048. $this->_getWriteAdapter()->query($select->insertFromSelect($indexTable));
  1049. }
  1050. return $this;
  1051. }
  1052. }