PageRenderTime 260ms CodeModel.GetById 80ms app.highlight 101ms RepoModel.GetById 60ms app.codeStats 8ms

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

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