1: <?php
2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25:
26:
27:
28: 29: 30: 31: 32: 33: 34:
35: class Mage_Catalog_Model_Resource_Category_Tree extends Varien_Data_Tree_Dbp
36: {
37: const ID_FIELD = 'id';
38: const PATH_FIELD = 'path';
39: const ORDER_FIELD = 'order';
40: const LEVEL_FIELD = 'level';
41:
42: 43: 44: 45: 46:
47: protected $_collection;
48:
49: 50: 51: 52: 53:
54: protected $_isActiveAttributeId = null;
55:
56: 57: 58: 59: 60:
61: protected $_joinUrlRewriteIntoCollection = false;
62:
63: 64: 65: 66: 67:
68: protected $_inactiveCategoryIds = null;
69:
70: 71: 72: 73: 74:
75: protected $_storeId = null;
76:
77: 78: 79: 80:
81: public function __construct()
82: {
83: $resource = Mage::getSingleton('core/resource');
84:
85: parent::__construct(
86: $resource->getConnection('catalog_write'),
87: $resource->getTableName('catalog/category'),
88: array(
89: Varien_Data_Tree_Dbp::ID_FIELD => 'entity_id',
90: Varien_Data_Tree_Dbp::PATH_FIELD => 'path',
91: Varien_Data_Tree_Dbp::ORDER_FIELD => 'position',
92: Varien_Data_Tree_Dbp::LEVEL_FIELD => 'level',
93: )
94: );
95: }
96:
97: 98: 99: 100: 101: 102:
103: public function setStoreId($storeId)
104: {
105: $this->_storeId = (int) $storeId;
106: return $this;
107: }
108:
109: 110: 111: 112: 113:
114: public function getStoreId()
115: {
116: if ($this->_storeId === null) {
117: $this->_storeId = Mage::app()->getStore()->getId();
118: }
119: return $this->_storeId;
120: }
121:
122: 123: 124: 125: 126: 127: 128: 129: 130: 131:
132: public function addCollectionData($collection = null, $sorted = false, $exclude = array(), $toLoad = true,
133: $onlyActive = false)
134: {
135: if (is_null($collection)) {
136: $collection = $this->getCollection($sorted);
137: } else {
138: $this->setCollection($collection);
139: }
140:
141: if (!is_array($exclude)) {
142: $exclude = array($exclude);
143: }
144:
145: $nodeIds = array();
146: foreach ($this->getNodes() as $node) {
147: if (!in_array($node->getId(), $exclude)) {
148: $nodeIds[] = $node->getId();
149: }
150: }
151: $collection->addIdFilter($nodeIds);
152: if ($onlyActive) {
153:
154: $disabledIds = $this->_getDisabledIds($collection);
155: if ($disabledIds) {
156: $collection->addFieldToFilter('entity_id', array('nin' => $disabledIds));
157: }
158: $collection->addAttributeToFilter('is_active', 1);
159: $collection->addAttributeToFilter('include_in_menu', 1);
160: }
161:
162: if ($this->_joinUrlRewriteIntoCollection) {
163: $collection->joinUrlRewrite();
164: $this->_joinUrlRewriteIntoCollection = false;
165: }
166:
167: if ($toLoad) {
168: $collection->load();
169:
170: foreach ($collection as $category) {
171: if ($this->getNodeById($category->getId())) {
172: $this->getNodeById($category->getId())
173: ->addData($category->getData());
174: }
175: }
176:
177: foreach ($this->getNodes() as $node) {
178: if (!$collection->getItemById($node->getId()) && $node->getParent()) {
179: $this->removeNode($node);
180: }
181: }
182: }
183:
184: return $this;
185: }
186:
187: 188: 189: 190: 191: 192:
193: public function addInactiveCategoryIds($ids)
194: {
195: if (!is_array($this->_inactiveCategoryIds)) {
196: $this->_initInactiveCategoryIds();
197: }
198: $this->_inactiveCategoryIds = array_merge($ids, $this->_inactiveCategoryIds);
199: return $this;
200: }
201:
202: 203: 204: 205: 206:
207: protected function _initInactiveCategoryIds()
208: {
209: $this->_inactiveCategoryIds = array();
210: Mage::dispatchEvent('catalog_category_tree_init_inactive_category_ids', array('tree' => $this));
211: return $this;
212: }
213:
214: 215: 216: 217: 218:
219: public function getInactiveCategoryIds()
220: {
221: if (!is_array($this->_inactiveCategoryIds)) {
222: $this->_initInactiveCategoryIds();
223: }
224:
225: return $this->_inactiveCategoryIds;
226: }
227:
228: 229: 230: 231: 232: 233:
234: protected function _getDisabledIds($collection)
235: {
236: $storeId = Mage::app()->getStore()->getId();
237:
238: $this->_inactiveItems = $this->getInactiveCategoryIds();
239:
240:
241: $this->_inactiveItems = array_merge(
242: $this->_getInactiveItemIds($collection, $storeId),
243: $this->_inactiveItems
244: );
245:
246:
247: $allIds = $collection->getAllIds();
248: $disabledIds = array();
249:
250: foreach ($allIds as $id) {
251: $parents = $this->getNodeById($id)->getPath();
252: foreach ($parents as $parent) {
253: if (!$this->_getItemIsActive($parent->getId(), $storeId)){
254: $disabledIds[] = $id;
255: continue;
256: }
257: }
258: }
259: return $disabledIds;
260: }
261:
262: 263: 264: 265: 266:
267: protected function _getIsActiveAttributeId()
268: {
269: $resource = Mage::getSingleton('core/resource');
270: if (is_null($this->_isActiveAttributeId)) {
271: $bind = array(
272: 'entity_type_code' => Mage_Catalog_Model_Category::ENTITY,
273: 'attribute_code' => 'is_active'
274: );
275: $select = $this->_conn->select()
276: ->from(array('a'=>$resource->getTableName('eav/attribute')), array('attribute_id'))
277: ->join(array('t'=>$resource->getTableName('eav/entity_type')), 'a.entity_type_id = t.entity_type_id')
278: ->where('entity_type_code = :entity_type_code')
279: ->where('attribute_code = :attribute_code');
280:
281: $this->_isActiveAttributeId = $this->_conn->fetchOne($select, $bind);
282: }
283: return $this->_isActiveAttributeId;
284: }
285:
286: 287: 288: 289: 290: 291: 292:
293: protected function _getInactiveItemIds($collection, $storeId)
294: {
295: $filter = $collection->getAllIdsSql();
296: $attributeId = $this->_getIsActiveAttributeId();
297:
298: $conditionSql = $this->_conn->getCheckSql('c.value_id > 0', 'c.value', 'd.value');
299: $table = Mage::getSingleton('core/resource')->getTableName(array('catalog/category', 'int'));
300: $bind = array(
301: 'attribute_id' => $attributeId,
302: 'store_id' => $storeId,
303: 'zero_store_id'=> 0,
304: 'cond' => 0,
305:
306: );
307: $select = $this->_conn->select()
308: ->from(array('d'=>$table), array('d.entity_id'))
309: ->where('d.attribute_id = :attribute_id')
310: ->where('d.store_id = :zero_store_id')
311: ->where('d.entity_id IN (?)', new Zend_Db_Expr($filter))
312: ->joinLeft(
313: array('c'=>$table),
314: 'c.attribute_id = :attribute_id AND c.store_id = :store_id AND c.entity_id = d.entity_id',
315: array()
316: )
317: ->where($conditionSql . ' = :cond');
318:
319: return $this->_conn->fetchCol($select, $bind);
320: }
321:
322: 323: 324: 325: 326: 327:
328: protected function _getItemIsActive($id)
329: {
330: if (!in_array($id, $this->_inactiveItems)) {
331: return true;
332: }
333: return false;
334: }
335:
336: 337: 338: 339: 340: 341:
342: public function getCollection($sorted = false)
343: {
344: if (is_null($this->_collection)) {
345: $this->_collection = $this->_getDefaultCollection($sorted);
346: }
347: return $this->_collection;
348: }
349:
350: 351: 352: 353: 354: 355:
356: public function setCollection($collection)
357: {
358: if (!is_null($this->_collection)) {
359: destruct($this->_collection);
360: }
361: $this->_collection = $collection;
362: return $this;
363: }
364:
365: 366: 367: 368: 369: 370:
371: protected function _getDefaultCollection($sorted = false)
372: {
373: $this->_joinUrlRewriteIntoCollection = true;
374: $collection = Mage::getModel('catalog/category')->getCollection();
375:
376:
377: $attributes = Mage::getConfig()->getNode('frontend/category/collection/attributes');
378: if ($attributes) {
379: $attributes = $attributes->asArray();
380: $attributes = array_keys($attributes);
381: }
382: $collection->addAttributeToSelect($attributes);
383:
384: if ($sorted) {
385: if (is_string($sorted)) {
386:
387: $collection->addAttributeToSort($sorted);
388: } else {
389: $collection->addAttributeToSort('name');
390: }
391: }
392:
393: return $collection;
394: }
395:
396: 397: 398: 399: 400: 401: 402: 403:
404: protected function _beforeMove($category, $newParent, $prevNode)
405: {
406: Mage::dispatchEvent('catalog_category_tree_move_before', array(
407: 'category' => $category,
408: 'prev_parent' => $prevNode,
409: 'parent' => $newParent
410: ));
411:
412: return $this;
413: }
414:
415: 416: 417: 418: 419: 420: 421:
422: public function move($category, $newParent, $prevNode = null)
423: {
424: $this->_beforeMove($category, $newParent, $prevNode);
425: Mage::getResourceSingleton('catalog/category')->move($category->getId(), $newParent->getId());
426: parent::move($category, $newParent, $prevNode);
427:
428: $this->_afterMove($category, $newParent, $prevNode);
429: }
430:
431: 432: 433: 434: 435: 436: 437: 438:
439: protected function _afterMove($category, $newParent, $prevNode)
440: {
441: Mage::app()->cleanCache(array(Mage_Catalog_Model_Category::CACHE_TAG));
442:
443: Mage::dispatchEvent('catalog_category_tree_move_after', array(
444: 'category' => $category,
445: 'prev_node' => $prevNode,
446: 'parent' => $newParent
447: ));
448:
449: return $this;
450: }
451:
452: 453: 454: 455: 456: 457: 458: 459:
460: public function loadByIds($ids, $addCollectionData = true, $updateAnchorProductCount = true)
461: {
462: $levelField = $this->_conn->quoteIdentifier('level');
463: $pathField = $this->_conn->quoteIdentifier('path');
464:
465: if (empty($ids)) {
466: $select = $this->_conn->select()
467: ->from($this->_table, 'entity_id')
468: ->where($levelField . ' <= 2');
469: $ids = $this->_conn->fetchCol($select);
470: }
471: if (!is_array($ids)) {
472: $ids = array($ids);
473: }
474: foreach ($ids as $key => $id) {
475: $ids[$key] = (int)$id;
476: }
477:
478:
479: $select = $this->_conn->select()
480: ->from($this->_table, array('path', 'level'))
481: ->where('entity_id IN (?)', $ids);
482: $where = array($levelField . '=0' => true);
483:
484: foreach ($this->_conn->fetchAll($select) as $item) {
485: $pathIds = explode('/', $item['path']);
486: $level = (int)$item['level'];
487: while ($level > 0) {
488: $pathIds[count($pathIds) - 1] = '%';
489: $path = implode('/', $pathIds);
490: $where["$levelField=$level AND $pathField LIKE '$path'"] = true;
491: array_pop($pathIds);
492: $level--;
493: }
494: }
495: $where = array_keys($where);
496:
497:
498: if ($addCollectionData) {
499: $select = $this->_createCollectionDataSelect();
500: } else {
501: $select = clone $this->_select;
502: $select->order($this->_orderField . ' ' . Varien_Db_Select::SQL_ASC);
503: }
504: $select->where(implode(' OR ', $where));
505:
506:
507: $arrNodes = $this->_conn->fetchAll($select);
508: if (!$arrNodes) {
509: return false;
510: }
511: if ($updateAnchorProductCount) {
512: $this->_updateAnchorProductCount($arrNodes);
513: }
514: $childrenItems = array();
515: foreach ($arrNodes as $key => $nodeInfo) {
516: $pathToParent = explode('/', $nodeInfo[$this->_pathField]);
517: array_pop($pathToParent);
518: $pathToParent = implode('/', $pathToParent);
519: $childrenItems[$pathToParent][] = $nodeInfo;
520: }
521: $this->addChildNodes($childrenItems, '', null);
522: return $this;
523: }
524:
525: 526: 527: 528: 529: 530: 531: 532:
533: public function loadBreadcrumbsArray($path, $addCollectionData = true, $withRootNode = false)
534: {
535: $pathIds = explode('/', $path);
536: if (!$withRootNode) {
537: array_shift($pathIds);
538: }
539: $result = array();
540: if (!empty($pathIds)) {
541: if ($addCollectionData) {
542: $select = $this->_createCollectionDataSelect(false);
543: } else {
544: $select = clone $this->_select;
545: }
546: $select
547: ->where('e.entity_id IN(?)', $pathIds)
548: ->order($this->_conn->getLengthSql('e.path') . ' ' . Varien_Db_Select::SQL_ASC);
549: $result = $this->_conn->fetchAll($select);
550: $this->_updateAnchorProductCount($result);
551: }
552: return $result;
553: }
554:
555: 556: 557: 558: 559:
560: protected function _updateAnchorProductCount(&$data)
561: {
562: foreach ($data as $key => $row) {
563: if (0 === (int)$row['is_anchor']) {
564: $data[$key]['product_count'] = $row['self_product_count'];
565: }
566: }
567: }
568:
569: 570: 571: 572: 573: 574: 575: 576: 577: 578:
579: protected function _createCollectionDataSelect($sorted = true, $optionalAttributes = array())
580: {
581: $select = $this->_getDefaultCollection($sorted ? $this->_orderField : false)
582: ->getSelect();
583:
584: $attributes = array('name', 'is_active', 'is_anchor');
585: if ($optionalAttributes) {
586: $attributes = array_unique(array_merge($attributes, $optionalAttributes));
587: }
588: foreach ($attributes as $attributeCode) {
589:
590: $attribute = Mage::getResourceSingleton('catalog/category')->getAttribute($attributeCode);
591:
592: if (!$attribute->getBackend()->isStatic()) {
593: $tableDefault = sprintf('d_%s', $attributeCode);
594: $tableStore = sprintf('s_%s', $attributeCode);
595: $valueExpr = $this->_conn
596: ->getCheckSql("{$tableStore}.value_id > 0", "{$tableStore}.value", "{$tableDefault}.value");
597:
598: $select
599: ->joinLeft(
600: array($tableDefault => $attribute->getBackend()->getTable()),
601: sprintf('%1$s.entity_id=e.entity_id AND %1$s.attribute_id=%2$d'
602: . ' AND %1$s.entity_type_id=e.entity_type_id AND %1$s.store_id=%3$d',
603: $tableDefault, $attribute->getId(), Mage_Core_Model_App::ADMIN_STORE_ID),
604: array($attributeCode => 'value'))
605: ->joinLeft(
606: array($tableStore => $attribute->getBackend()->getTable()),
607: sprintf('%1$s.entity_id=e.entity_id AND %1$s.attribute_id=%2$d'
608: . ' AND %1$s.entity_type_id=e.entity_type_id AND %1$s.store_id=%3$d',
609: $tableStore, $attribute->getId(), $this->getStoreId()),
610: array($attributeCode => $valueExpr)
611: );
612: }
613: }
614:
615:
616: $categoriesTable = Mage::getSingleton('core/resource')->getTableName('catalog/category');
617: $categoriesProductsTable = Mage::getSingleton('core/resource')->getTableName('catalog/category_product');
618:
619: $subConcat = $this->_conn->getConcatSql(array('e.path', $this->_conn->quote('/%')));
620: $subSelect = $this->_conn->select()
621: ->from(array('see' => $categoriesTable), null)
622: ->joinLeft(
623: array('scp' => $categoriesProductsTable),
624: 'see.entity_id=scp.category_id',
625: array('COUNT(DISTINCT scp.product_id)'))
626: ->where('see.entity_id = e.entity_id')
627: ->orWhere('see.path LIKE ?', $subConcat);
628: $select->columns(array('product_count' => $subSelect));
629:
630: $subSelect = $this->_conn->select()
631: ->from(array('cp' => $categoriesProductsTable), 'COUNT(cp.product_id)')
632: ->where('cp.category_id = e.entity_id');
633:
634: $select->columns(array('self_product_count' => $subSelect));
635:
636: return $select;
637: }
638:
639: 640: 641: 642: 643: 644:
645: public function getExistingCategoryIdsBySpecifiedIds($ids)
646: {
647: if (empty($ids)) {
648: return array();
649: }
650: if (!is_array($ids)) {
651: $ids = array($ids);
652: }
653: $select = $this->_conn->select()
654: ->from($this->_table, array('entity_id'))
655: ->where('entity_id IN (?)', $ids);
656: return $this->_conn->fetchCol($select);
657: }
658: }
659: