PageRenderTime 506ms CodeModel.GetById 302ms app.highlight 110ms RepoModel.GetById 81ms app.codeStats 1ms

/app/code/core/Mage/Eav/Model/Entity/Collection/Abstract.php

https://bitbucket.org/claudiu_marginean/magento-hg-mirror
PHP | 1336 lines | 800 code | 136 blank | 400 comment | 132 complexity | ff67cb0721d4409a1d4c2e5e70418c11 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_Eav
  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 * Entity/Attribute/Model - collection abstract
  29 *
  30 * @category   Mage
  31 * @package    Mage_Eav
  32 * @author      Magento Core Team <core@magentocommerce.com>
  33 */
  34abstract class Mage_Eav_Model_Entity_Collection_Abstract extends Varien_Data_Collection_Db
  35{
  36    /**
  37     * Array of items with item id key
  38     *
  39     * @var array
  40     */
  41    protected $_itemsById           = array();
  42
  43    /**
  44     * Entity static fields
  45     *
  46     * @var array
  47     */
  48    protected $_staticFields        = array();
  49
  50    /**
  51     * Entity object to define collection's attributes
  52     *
  53     * @var Mage_Eav_Model_Entity_Abstract
  54     */
  55    protected $_entity;
  56
  57    /**
  58     * Entity types to be fetched for objects in collection
  59     *
  60     * @var array
  61     */
  62    protected $_selectEntityTypes   = array();
  63
  64    /**
  65     * Attributes to be fetched for objects in collection
  66     *
  67     * @var array
  68     */
  69    protected $_selectAttributes=array();
  70
  71    /**
  72     * Attributes to be filtered order sorted by
  73     *
  74     * @var array
  75     */
  76    protected $_filterAttributes=array();
  77
  78    /**
  79     * Joined entities
  80     *
  81     * @var array
  82     */
  83    protected $_joinEntities = array();
  84
  85    /**
  86     * Joined attributes
  87     *
  88     * @var array
  89     */
  90    protected $_joinAttributes = array();
  91
  92    /**
  93     * Joined fields data
  94     *
  95     * @var array
  96     */
  97    protected $_joinFields = array();
  98
  99    /**
 100     * Collection constructor
 101     *
 102     * @param Mage_Core_Model_Mysql4_Abstract $resource
 103     */
 104    public function __construct($resource=null)
 105    {
 106        parent::__construct();
 107        $this->_construct();
 108        $this->setConnection($this->getEntity()->getReadConnection());
 109        $this->_prepareStaticFields();
 110        $this->_initSelect();
 111    }
 112
 113    /**
 114     * Initialize collection
 115     */
 116    protected function _construct()
 117    {
 118
 119    }
 120
 121    public function getTable($table)
 122    {
 123        return $this->getResource()->getTable($table);
 124    }
 125
 126    /**
 127     * Prepare static entity fields
 128     *
 129     * @return Mage_Eav_Model_Entity_Collection_Abstract
 130     */
 131    protected function _prepareStaticFields()
 132    {
 133        foreach ($this->getEntity()->getDefaultAttributes() as $field) {
 134            $this->_staticFields[$field] = $field;
 135        }
 136        return $this;
 137    }
 138
 139    protected function _initSelect()
 140    {
 141        $this->getSelect()->from(array('e'=>$this->getEntity()->getEntityTable()));
 142        if ($this->getEntity()->getTypeId()) {
 143            $this->addAttributeToFilter('entity_type_id', $this->getEntity()->getTypeId());
 144        }
 145        return $this;
 146    }
 147
 148    /**
 149     * Standard resource collection initalization
 150     *
 151     * @param string $model
 152     * @return Mage_Core_Model_Mysql4_Collection_Abstract
 153     */
 154    protected function _init($model, $entityModel=null)
 155    {
 156        $this->setItemObjectClass(Mage::getConfig()->getModelClassName($model));
 157        if (is_null($entityModel)) {
 158            $entityModel = $model;
 159        }
 160        $entity = Mage::getResourceSingleton($entityModel);
 161        $this->setEntity($entity);
 162        return $this;
 163    }
 164
 165    /**
 166     * Set entity to use for attributes
 167     *
 168     * @param Mage_Eav_Model_Entity_Abstract $entity
 169     * @return Mage_Eav_Model_Entity_Collection_Abstract
 170     */
 171    public function setEntity($entity)
 172    {
 173        if ($entity instanceof Mage_Eav_Model_Entity_Abstract) {
 174            $this->_entity = $entity;
 175        } elseif (is_string($entity) || $entity instanceof Mage_Core_Model_Config_Element) {
 176            $this->_entity = Mage::getModel('eav/entity')->setType($entity);
 177        } else {
 178            Mage::throwException(Mage::helper('eav')->__('Invalid entity supplied: %s.', print_r($entity,1)));
 179        }
 180        return $this;
 181    }
 182
 183    /**
 184     * Get collection's entity object
 185     *
 186     * @return Mage_Eav_Model_Entity_Abstract
 187     */
 188    public function getEntity()
 189    {
 190        if (empty($this->_entity)) {
 191            throw Mage::exception('Mage_Eav', Mage::helper('eav')->__('Entity is not initialized.'));
 192        }
 193        return $this->_entity;
 194    }
 195
 196    /**
 197     * Get resource instance
 198     *
 199     * @return Mage_Core_Model_Mysql4_Abstract
 200     */
 201    public function getResource()
 202    {
 203        return $this->getEntity();
 204    }
 205
 206    /**
 207     * Set template object for the collection
 208     *
 209     * @param   Varien_Object $object
 210     * @return  Mage_Eav_Model_Entity_Collection_Abstract
 211     */
 212    public function setObject($object=null)
 213    {
 214        if (is_object($object)) {
 215            $this->setItemObjectClass(get_class($object));
 216        }
 217        else {
 218            $this->setItemObjectClass($object);
 219        }
 220
 221        return $this;
 222    }
 223
 224
 225    /**
 226     * Add an object to the collection
 227     *
 228     * @param Varien_Object $object
 229     * @return Mage_Eav_Model_Entity_Collection_Abstract
 230     */
 231    public function addItem(Varien_Object $object)
 232    {
 233        if (get_class($object)!== $this->_itemObjectClass) {
 234            throw Mage::exception('Mage_Eav', Mage::helper('eav')->__('Attempt to add an invalid object.'));
 235        }
 236        return parent::addItem($object);
 237    }
 238
 239    /**
 240     * Retrieve entity attribute
 241     *
 242     * @param   string $attributeCode
 243     * @return  Mage_Eav_Model_Entity_Attribute_Abstract
 244     */
 245    public function getAttribute($attributeCode)
 246    {
 247        if (isset($this->_joinAttributes[$attributeCode])) {
 248            return $this->_joinAttributes[$attributeCode]['attribute'];
 249        } else {
 250            return $this->getEntity()->getAttribute($attributeCode);
 251        }
 252    }
 253
 254    /**
 255     * Add attribute filter to collection
 256     *
 257     * If $attribute is an array will add OR condition with following format:
 258     * array(
 259     *     array('attribute'=>'firstname', 'like'=>'test%'),
 260     *     array('attribute'=>'lastname', 'like'=>'test%'),
 261     * )
 262     *
 263     * @see self::_getConditionSql for $condition
 264     * @param Mage_Eav_Model_Entity_Attribute_Interface|integer|string|array $attribute
 265     * @param null|string|array $condition
 266     * @param string $operator
 267     * @return Mage_Eav_Model_Entity_Collection_Abstract
 268     */
 269    public function addAttributeToFilter($attribute, $condition=null, $joinType='inner')
 270    {
 271        if ($attribute===null) {
 272            $this->getSelect();
 273            return $this;
 274        }
 275
 276        if (is_numeric($attribute)) {
 277            $attribute = $this->getEntity()->getAttribute($attribute)->getAttributeCode();
 278        } else if ($attribute instanceof Mage_Eav_Model_Entity_Attribute_Interface) {
 279            $attribute = $attribute->getAttributeCode();
 280        }
 281
 282        if (is_array($attribute)) {
 283            $sqlArr = array();
 284            foreach ($attribute as $condition) {
 285                $sqlArr[] = $this->_getAttributeConditionSql($condition['attribute'], $condition, $joinType);
 286            }
 287            $conditionSql = '('.join(') OR (', $sqlArr).')';
 288        } elseif (is_string($attribute)) {
 289            if (is_null($condition)) {
 290                $condition = '';
 291            }
 292            $conditionSql = $this->_getAttributeConditionSql($attribute, $condition, $joinType);
 293        }
 294
 295        if (!empty($conditionSql)) {
 296            $this->getSelect()->where($conditionSql, null, Varien_Db_Select::TYPE_CONDITION);
 297        } else {
 298            Mage::throwException('Invalid attribute identifier for filter ('.get_class($attribute).')');
 299        }
 300
 301        return $this;
 302    }
 303
 304    /**
 305     * Wrapper for compatibility with Varien_Data_Collection_Db
 306     *
 307     * @param mixed $attribute
 308     * @param mixed $condition
 309     */
 310    public function addFieldToFilter($attribute, $condition=null)
 311    {
 312        return $this->addAttributeToFilter($attribute, $condition);
 313    }
 314
 315    /**
 316     * Add attribute to sort order
 317     *
 318     * @param string $attribute
 319     * @param string $dir
 320     * @return Mage_Eav_Model_Entity_Collection_Abstract
 321     */
 322    public function addAttributeToSort($attribute, $dir='asc')
 323    {
 324        if (isset($this->_joinFields[$attribute])) {
 325            $this->getSelect()->order($this->_getAttributeFieldName($attribute).' '.$dir);
 326            return $this;
 327        }
 328        if (isset($this->_staticFields[$attribute])) {
 329            $this->getSelect()->order("e.{$attribute} {$dir}");
 330        }
 331        if (isset($this->_joinAttributes[$attribute])) {
 332            $attrInstance = $this->_joinAttributes[$attribute]['attribute'];
 333            $entityField = $this->_getAttributeTableAlias($attribute).'.'.$attrInstance->getAttributeCode();
 334        } else {
 335            $attrInstance = $this->getEntity()->getAttribute($attribute);
 336            $entityField = 'e.'.$attribute;
 337        }
 338        if ($attrInstance) {
 339            if ($attrInstance->getBackend()->isStatic()) {
 340                $this->getSelect()->order($entityField.' '.$dir);
 341            } else {
 342                $this->_addAttributeJoin($attribute, 'left');
 343                if (isset($this->_joinAttributes[$attribute])) {
 344                    $this->getSelect()->order($attribute.' '.$dir);
 345                } else {
 346                    $this->getSelect()->order($this->_getAttributeTableAlias($attribute).'.value '.$dir);
 347                }
 348            }
 349        }
 350        return $this;
 351    }
 352
 353    /**
 354     * Add attribute to entities in collection
 355     *
 356     * If $attribute=='*' select all attributes
 357     *
 358     * @param   array|string|integer|Mage_Core_Model_Config_Element $attribute
 359     * @param   false|string $joinType flag for joining attribute
 360     * @return  Mage_Eav_Model_Entity_Collection_Abstract
 361     */
 362    public function addAttributeToSelect($attribute, $joinType=false)
 363    {
 364        if (is_array($attribute)) {
 365            Mage::getSingleton('eav/config')->loadCollectionAttributes($this->getEntity()->getType(), $attribute);
 366            foreach ($attribute as $a) {
 367                $this->addAttributeToSelect($a, $joinType);
 368            }
 369            return $this;
 370        }
 371        if ($joinType!==false && !$this->getEntity()->getAttribute($attribute)->isStatic()) {
 372            $this->_addAttributeJoin($attribute, $joinType);
 373        } elseif ('*'===$attribute) {
 374            $attributes = $this->getEntity()
 375                ->loadAllAttributes()
 376                ->getAttributesByCode();
 377            foreach ($attributes as $attrCode=>$attr) {
 378                $this->_selectAttributes[$attrCode] = $attr->getId();
 379            }
 380        } else {
 381            if (isset($this->_joinAttributes[$attribute])) {
 382                $attrInstance = $this->_joinAttributes[$attribute]['attribute'];
 383            } else {
 384                //$attrInstance = $this->getEntity()->getAttribute($attribute);
 385                $attrInstance = Mage::getSingleton('eav/config')
 386                    ->getCollectionAttribute($this->getEntity()->getType(), $attribute);
 387            }
 388            if (empty($attrInstance)) {
 389                throw Mage::exception('Mage_Eav',
 390                    Mage::helper('eav')->__('Invalid attribute requested: %s', (string)$attribute));
 391            }
 392            $this->_selectAttributes[$attrInstance->getAttributeCode()] = $attrInstance->getId();
 393        }
 394        return $this;
 395    }
 396
 397    public function addEntityTypeToSelect($entityType, $prefix)
 398    {
 399        $this->_selectEntityTypes[$entityType] = array(
 400            'prefix'=>$prefix,
 401        );
 402        return $this;
 403    }
 404
 405    /**
 406     * Add field to static
 407     *
 408     * @param string $field
 409     * @return Mage_Eav_Model_Entity_Collection_Abstract
 410     */
 411    public function addStaticField($field)
 412    {
 413        if (!isset($this->_staticFields[$field])) {
 414            $this->_staticFields[$field] = $field;
 415        }
 416        return $this;
 417    }
 418
 419    /**
 420     * Add attribute expression (SUM, COUNT, etc)
 421     *
 422     * Example: ('sub_total', 'SUM({{attribute}})', 'revenue')
 423     * Example: ('sub_total', 'SUM({{revenue}})', 'revenue')
 424     *
 425     * For some functions like SUM use groupByAttribute.
 426     *
 427     * @param string $alias
 428     * @param string $expression
 429     * @param string $attribute
 430     * @return Mage_Eav_Model_Entity_Collection_Abstract
 431     */
 432    public function addExpressionAttributeToSelect($alias, $expression, $attribute)
 433    {
 434        // validate alias
 435        if (isset($this->_joinFields[$alias])) {
 436            throw Mage::exception('Mage_Eav',
 437                Mage::helper('eav')->__('Joint field or attribute expression with this alias is already declared.'));
 438        }
 439        if(!is_array($attribute)) {
 440            $attribute = array($attribute);
 441        }
 442
 443        $fullExpression = $expression;
 444        // Replacing multiple attributes
 445        foreach($attribute as $attributeItem) {
 446            if (isset($this->_staticFields[$attributeItem])) {
 447                $attrField = sprintf('e.%s', $attributeItem);
 448            }
 449            else {
 450                $attributeInstance = $this->getAttribute($attributeItem);
 451
 452                if ($attributeInstance->getBackend()->isStatic()) {
 453                    $attrField = 'e.' . $attributeItem;
 454                } else {
 455                    $this->_addAttributeJoin($attributeItem, 'left');
 456                    $attrField = $this->_getAttributeFieldName($attributeItem);
 457                }
 458            }
 459
 460            $fullExpression = str_replace('{{attribute}}', $attrField, $fullExpression);
 461            $fullExpression = str_replace('{{' . $attributeItem . '}}', $attrField, $fullExpression);
 462        }
 463
 464        $this->getSelect()->columns(array($alias=>$fullExpression));
 465
 466        $this->_joinFields[$alias] = array(
 467            'table' => false,
 468            'field' => $fullExpression
 469        );
 470
 471        return $this;
 472    }
 473
 474
 475    /**
 476     * Groups results by specified attribute
 477     *
 478     * @param string|array $attribute
 479     */
 480    public function groupByAttribute($attribute)
 481    {
 482        if(is_array($attribute)) {
 483            foreach ($attribute as $attributeItem) {
 484                $this->groupByAttribute($attributeItem);
 485            }
 486        } else {
 487            if (isset($this->_joinFields[$attribute])) {
 488                $this->getSelect()->group($this->_getAttributeFieldName($attribute));
 489                return $this;
 490            }
 491
 492            if (isset($this->_staticFields[$attribute])) {
 493                $this->getSelect()->group(sprintf('e.%s', $attribute));
 494                return $this;
 495            }
 496
 497            if (isset($this->_joinAttributes[$attribute])) {
 498                $attrInstance = $this->_joinAttributes[$attribute]['attribute'];
 499                $entityField = $this->_getAttributeTableAlias($attribute).'.'.$attrInstance->getAttributeCode();
 500            } else {
 501                $attrInstance = $this->getEntity()->getAttribute($attribute);
 502                $entityField = 'e.'.$attribute;
 503            }
 504
 505            if ($attrInstance->getBackend()->isStatic()) {
 506                $this->getSelect()->group($entityField);
 507            } else {
 508                $this->_addAttributeJoin($attribute);
 509                $this->getSelect()->group($this->_getAttributeTableAlias($attribute).'.value');
 510            }
 511        }
 512
 513        return $this;
 514    }
 515
 516    /**
 517     * Add attribute from joined entity to select
 518     *
 519     * Examples:
 520     * ('billing_firstname', 'customer_address/firstname', 'default_billing')
 521     * ('billing_lastname', 'customer_address/lastname', 'default_billing')
 522     * ('shipping_lastname', 'customer_address/lastname', 'default_billing')
 523     * ('shipping_postalcode', 'customer_address/postalcode', 'default_shipping')
 524     * ('shipping_city', $cityAttribute, 'default_shipping')
 525     *
 526     * Developer is encouraged to use existing instances of attributes and entities
 527     * After first use of string entity name it will be cached in the collection
 528     *
 529     * @todo connect between joined attributes of same entity
 530     * @param string $alias alias for the joined attribute
 531     * @param string|Mage_Eav_Model_Entity_Attribute_Abstract $attribute
 532     * @param string $bind attribute of the main entity to link with joined $filter
 533     * @param string $filter primary key for the joined entity (entity_id default)
 534     * @param string $joinType inner|left
 535     * @return Mage_Eav_Model_Entity_Collection_Abstract
 536     */
 537    public function joinAttribute($alias, $attribute, $bind, $filter=null, $joinType='inner', $storeId=null)
 538    {
 539        // validate alias
 540        if (isset($this->_joinAttributes[$alias])) {
 541            throw Mage::exception('Mage_Eav',
 542                Mage::helper('eav')->__('Invalid alias, already exists in joint attributes.'));
 543        }
 544
 545        // validate bind attribute
 546        if (is_string($bind)) {
 547            $bindAttribute = $this->getAttribute($bind);
 548        }
 549
 550        if (!$bindAttribute || (!$bindAttribute->isStatic() && !$bindAttribute->getId())) {
 551            throw Mage::exception('Mage_Eav', Mage::helper('eav')->__('Invalid foreign key.'));
 552        }
 553
 554        // try to explode combined entity/attribute if supplied
 555        if (is_string($attribute)) {
 556            $attrArr = explode('/', $attribute);
 557            if (isset($attrArr[1])) {
 558                $entity = $attrArr[0];
 559                $attribute = $attrArr[1];
 560            }
 561        }
 562
 563        // validate entity
 564        if (empty($entity) && $attribute instanceof Mage_Eav_Model_Entity_Attribute_Abstract) {
 565            $entity = $attribute->getEntity();
 566        } elseif (is_string($entity)) {
 567            // retrieve cached entity if possible
 568            if (isset($this->_joinEntities[$entity])) {
 569                $entity = $this->_joinEntities[$entity];
 570            } else {
 571                $entity = Mage::getModel('eav/entity')->setType($attrArr[0]);
 572            }
 573        }
 574        if (!$entity || !$entity->getTypeId()) {
 575            throw Mage::exception('Mage_Eav', Mage::helper('eav')->__('Invalid entity type.'));
 576        }
 577
 578        // cache entity
 579        if (!isset($this->_joinEntities[$entity->getType()])) {
 580            $this->_joinEntities[$entity->getType()] = $entity;
 581        }
 582
 583        // validate attribute
 584        if (is_string($attribute)) {
 585            $attribute = $entity->getAttribute($attribute);
 586        }
 587        if (!$attribute) {
 588            throw Mage::exception('Mage_Eav', Mage::helper('eav')->__('Invalid attribute type.'));
 589        }
 590
 591        if (empty($filter)) {
 592            $filter = $entity->getEntityIdField();
 593        }
 594
 595        // add joined attribute
 596        $this->_joinAttributes[$alias] = array(
 597            'bind'          => $bind,
 598            'bindAttribute' => $bindAttribute,
 599            'attribute'     => $attribute,
 600            'filter'        => $filter,
 601            'store_id'      => $storeId,
 602        );
 603
 604        $this->_addAttributeJoin($alias, $joinType);
 605
 606        return $this;
 607    }
 608
 609    /**
 610     * Join regular table field and use an attribute as fk
 611     *
 612     * Examples:
 613     * ('country_name', 'directory/country_name', 'name', 'country_id=shipping_country',
 614     *      "{{table}}.language_code='en'", 'left')
 615     *
 616     * @param string $alias 'country_name'
 617     * @param string $table 'directory/country_name'
 618     * @param string $field 'name'
 619     * @param string $bind 'PK(country_id)=FK(shipping_country_id)'
 620     * @param string|array $cond "{{table}}.language_code='en'" OR array('language_code'=>'en')
 621     * @param string $joinType 'left'
 622     * @return Mage_Eav_Model_Entity_Collection_Abstract
 623     */
 624    public function joinField($alias, $table, $field, $bind, $cond=null, $joinType='inner')
 625    {
 626        // validate alias
 627        if (isset($this->_joinFields[$alias])) {
 628            throw Mage::exception('Mage_Eav',
 629                Mage::helper('eav')->__('Joined field with this alias is already declared.'));
 630        }
 631
 632        // validate table
 633        if (strpos($table, '/')!==false) {
 634            $table = Mage::getSingleton('core/resource')->getTableName($table);
 635        }
 636        $tableAlias = $this->_getAttributeTableAlias($alias);
 637
 638        // validate bind
 639        list($pk, $fk) = explode('=', $bind);
 640        $pk = $this->getSelect()->getAdapter()->quoteColumnAs(trim($pk), null);
 641        $bindCond = $tableAlias . '.' . $pk . '=' . $this->_getAttributeFieldName($fk);
 642
 643        // process join type
 644        switch ($joinType) {
 645            case 'left':
 646                $joinMethod = 'joinLeft';
 647                break;
 648
 649            default:
 650                $joinMethod = 'join';
 651        }
 652        $condArr = array($bindCond);
 653
 654        // add where condition if needed
 655        if (!is_null($cond)) {
 656            if (is_array($cond)) {
 657                foreach ($cond as $k=>$v) {
 658                    $condArr[] = $this->_getConditionSql($tableAlias.'.'.$k, $v);
 659                }
 660            } else {
 661                $condArr[] = str_replace('{{table}}', $tableAlias, $cond);
 662            }
 663        }
 664        $cond = '(' . join(') AND (', $condArr) . ')';
 665
 666        // join table
 667        $this->getSelect()->$joinMethod(array($tableAlias=>$table), $cond, ($field ? array($alias=>$field) : array()));
 668
 669        // save joined attribute
 670        $this->_joinFields[$alias] = array(
 671            'table' => $tableAlias,
 672            'field' => $field,
 673        );
 674
 675        return $this;
 676    }
 677
 678    /**
 679     * Join a table
 680     *
 681     * @param string|array $table
 682     * @param string $bind
 683     * @param string|array $fields
 684     * @param null|array $cond
 685     * @param string $joinType
 686     * @return Mage_Eav_Model_Entity_Collection_Abstract
 687     */
 688    public function joinTable($table, $bind, $fields=null, $cond=null, $joinType='inner')
 689    {
 690        $tableAlias = null;
 691        if (is_array($table)) {
 692            list($tableAlias, $tableName) = each($table);
 693        }
 694        else {
 695            $tableName = $table;
 696        }
 697
 698        // validate table
 699        if (strpos($tableName, '/') !== false) {
 700            $tableName = Mage::getSingleton('core/resource')->getTableName($tableName);
 701        }
 702        if (empty($tableAlias)) {
 703            $tableAlias = $tableName;
 704        }
 705
 706        // validate fields and aliases
 707        if (!$fields) {
 708            throw Mage::exception('Mage_Eav', Mage::helper('eav')->__('Invalid joint fields.'));
 709        }
 710        foreach ($fields as $alias=>$field) {
 711            if (isset($this->_joinFields[$alias])) {
 712                throw Mage::exception('Mage_Eav',
 713                    Mage::helper('eav')->__('A joint field with this alias (%s) is already declared.', $alias));
 714            }
 715            $this->_joinFields[$alias] = array(
 716                'table' => $tableAlias,
 717                'field' => $field,
 718            );
 719        }
 720
 721        // validate bind
 722        list($pk, $fk) = explode('=', $bind);
 723        $bindCond = $tableAlias . '.' . $pk . '=' . $this->_getAttributeFieldName($fk);
 724
 725        // process join type
 726        switch ($joinType) {
 727            case 'left':
 728                $joinMethod = 'joinLeft';
 729                break;
 730
 731            default:
 732                $joinMethod = 'join';
 733        }
 734        $condArr = array($bindCond);
 735
 736        // add where condition if needed
 737        if (!is_null($cond)) {
 738            if (is_array($cond)) {
 739                foreach ($cond as $k=>$v) {
 740                    $condArr[] = $this->_getConditionSql($tableAlias.'.'.$k, $v);
 741                }
 742            } else {
 743                $condArr[] = str_replace('{{table}}', $tableAlias, $cond);
 744            }
 745        }
 746        $cond = '('.join(') AND (', $condArr).')';
 747
 748// join table
 749        $this->getSelect()->$joinMethod(array($tableAlias => $tableName), $cond, $fields);
 750
 751        return $this;
 752    }
 753
 754    /**
 755     * Remove an attribute from selection list
 756     *
 757     * @param string $attribute
 758     * @return Mage_Eav_Model_Entity_Collection_Abstract
 759     */
 760    public function removeAttributeToSelect($attribute=null)
 761    {
 762        if (is_null($attribute)) {
 763            $this->_selectAttributes = array();
 764        } else {
 765            unset($this->_selectAttributes[$attribute]);
 766        }
 767        return $this;
 768    }
 769
 770    /**
 771     * Set collection page start and records to show
 772     *
 773     * @param integer $pageNum
 774     * @param integer $pageSize
 775     * @return Mage_Eav_Model_Entity_Collection_Abstract
 776     */
 777    public function setPage($pageNum, $pageSize)
 778    {
 779        $this->setCurPage($pageNum)
 780            ->setPageSize($pageSize);
 781        return $this;
 782    }
 783
 784    /**
 785     * Load collection data into object items
 786     *
 787     * @return Mage_Eav_Model_Entity_Collection_Abstract
 788     */
 789    public function load($printQuery = false, $logQuery = false)
 790    {
 791        if ($this->isLoaded()) {
 792            return $this;
 793        }
 794        Varien_Profiler::start('__EAV_COLLECTION_BEFORE_LOAD__');
 795        Mage::dispatchEvent('eav_collection_abstract_load_before', array('collection' => $this));
 796        $this->_beforeLoad();
 797        Varien_Profiler::stop('__EAV_COLLECTION_BEFORE_LOAD__');
 798
 799        $this->_renderFilters();
 800
 801        Varien_Profiler::start('__EAV_COLLECTION_LOAD_ENT__');
 802        $this->_loadEntities($printQuery, $logQuery);
 803        Varien_Profiler::stop('__EAV_COLLECTION_LOAD_ENT__');
 804        Varien_Profiler::start('__EAV_COLLECTION_LOAD_ATTR__');
 805        $this->_loadAttributes($printQuery, $logQuery);
 806        Varien_Profiler::stop('__EAV_COLLECTION_LOAD_ATTR__');
 807
 808        Varien_Profiler::start('__EAV_COLLECTION_ORIG_DATA__');
 809        foreach ($this->_items as $item) {
 810            $item->setOrigData();
 811        }
 812        Varien_Profiler::stop('__EAV_COLLECTION_ORIG_DATA__');
 813
 814        $this->_setIsLoaded();
 815        Varien_Profiler::start('__EAV_COLLECTION_AFTER_LOAD__');
 816        $this->_afterLoad();
 817        Varien_Profiler::stop('__EAV_COLLECTION_AFTER_LOAD__');
 818        return $this;
 819    }
 820
 821    /**
 822     * Clone and reset collection
 823     *
 824     * @return Mage_Eav_Model_Entity_Collection_Abstract
 825     */
 826    protected function _getAllIdsSelect($limit=null, $offset=null)
 827    {
 828        $idsSelect = clone $this->getSelect();
 829        $idsSelect->reset(Zend_Db_Select::ORDER);
 830        $idsSelect->reset(Zend_Db_Select::LIMIT_COUNT);
 831        $idsSelect->reset(Zend_Db_Select::LIMIT_OFFSET);
 832        $idsSelect->reset(Zend_Db_Select::COLUMNS);
 833        $idsSelect->columns('e.'.$this->getEntity()->getIdFieldName());
 834        $idsSelect->limit($limit, $offset);
 835        return $idsSelect;
 836    }
 837
 838    /**
 839     * Retrive all ids for collection
 840     *
 841     * @return array
 842     */
 843    public function getAllIds($limit=null, $offset=null)
 844    {
 845        return $this->getConnection()->fetchCol($this->_getAllIdsSelect($limit, $offset), $this->_bindParams);
 846    }
 847
 848    /**
 849     * Retrive all ids sql
 850     *
 851     * @return array
 852     */
 853    public function getAllIdsSql()
 854    {
 855        $idsSelect = clone $this->getSelect();
 856        $idsSelect->reset(Zend_Db_Select::ORDER);
 857        $idsSelect->reset(Zend_Db_Select::LIMIT_COUNT);
 858        $idsSelect->reset(Zend_Db_Select::LIMIT_OFFSET);
 859        $idsSelect->reset(Zend_Db_Select::COLUMNS);
 860        $idsSelect->reset(Zend_Db_Select::GROUP);
 861        $idsSelect->columns('e.'.$this->getEntity()->getIdFieldName());
 862        return $idsSelect;
 863    }
 864
 865    /**
 866     * Save all the entities in the collection
 867     *
 868     * @todo make batch save directly from collection
 869     */
 870    public function save()
 871    {
 872        foreach ($this->getItems() as $item) {
 873            $item->save();
 874        }
 875        return $this;
 876    }
 877
 878
 879    /**
 880     * Delete all the entities in the collection
 881     *
 882     * @todo make batch delete directly from collection
 883     */
 884    public function delete()
 885    {
 886        foreach ($this->getItems() as $k=>$item) {
 887            $this->getEntity()->delete($item);
 888            unset($this->_items[$k]);
 889        }
 890        return $this;
 891    }
 892
 893    /**
 894     * Import 2D array into collection as objects
 895     *
 896     * If the imported items already exist, update the data for existing objects
 897     *
 898     * @param array $arr
 899     * @return Mage_Eav_Model_Entity_Collection_Abstract
 900     */
 901    public function importFromArray($arr)
 902    {
 903        $entityIdField = $this->getEntity()->getEntityIdField();
 904        foreach ($arr as $row) {
 905            $entityId = $row[$entityIdField];
 906            if (!isset($this->_items[$entityId])) {
 907                $this->_items[$entityId] = $this->getNewEmptyItem();
 908                $this->_items[$entityId]->setData($row);
 909            }  else {
 910                $this->_items[$entityId]->addData($row);
 911            }
 912        }
 913        return $this;
 914    }
 915
 916    /**
 917     * Get collection data as a 2D array
 918     *
 919     * @return array
 920     */
 921    public function exportToArray()
 922    {
 923        $result = array();
 924        $entityIdField = $this->getEntity()->getEntityIdField();
 925        foreach ($this->getItems() as $item) {
 926            $result[$item->getData($entityIdField)] = $item->getData();
 927        }
 928        return $result;
 929    }
 930
 931
 932    public function getRowIdFieldName()
 933    {
 934        if (is_null($this->_idFieldName)) {
 935            $this->_setIdFieldName($this->getEntity()->getIdFieldName());
 936        }
 937        return $this->getIdFieldName();
 938    }
 939
 940    public function setRowIdFieldName($fieldName)
 941    {
 942        return $this->_setIdFieldName($fieldName);
 943    }
 944
 945    /**
 946     * Load entities records into items
 947     *
 948     * @return Mage_Eav_Model_Entity_Collection_Abstract
 949     */
 950    public function _loadEntities($printQuery = false, $logQuery = false)
 951    {
 952        $entity = $this->getEntity();
 953//        $entityIdField = $entity->getEntityIdField();
 954
 955        if ($this->_pageSize) {
 956            $this->getSelect()->limitPage($this->getCurPage(), $this->_pageSize);
 957        }
 958
 959        $this->printLogQuery($printQuery, $logQuery);
 960
 961        try {
 962            $rows = $this->_fetchAll($this->getSelect());
 963        } catch (Exception $e) {
 964            Mage::printException($e, $this->getSelect());
 965            $this->printLogQuery(true, true, $this->getSelect());
 966            throw $e;
 967        }
 968
 969        foreach ($rows as $v) {
 970            $object = $this->getNewEmptyItem()
 971                ->setData($v);
 972            $this->addItem($object);
 973            if (isset($this->_itemsById[$object->getId()])) {
 974                $this->_itemsById[$object->getId()][] = $object;
 975            }
 976            else {
 977                $this->_itemsById[$object->getId()] = array($object);
 978            }
 979        }
 980        return $this;
 981    }
 982
 983    /**
 984     * Load attributes into loaded entities
 985     *
 986     * @return Mage_Eav_Model_Entity_Collection_Abstract
 987     */
 988    public function _loadAttributes($printQuery = false, $logQuery = false)
 989    {
 990        if (empty($this->_items) || empty($this->_itemsById) || empty($this->_selectAttributes)) {
 991            return $this;
 992        }
 993
 994        $entity = $this->getEntity();
 995        $entityIdField = $entity->getEntityIdField();
 996
 997        $tableAttributes = array();
 998        foreach ($this->_selectAttributes as $attributeCode => $attributeId) {
 999            if (!$attributeId) {
1000                continue;
1001            }
1002            $attribute = Mage::getSingleton('eav/config')->getCollectionAttribute($entity->getType(), $attributeCode);
1003            if ($attribute && !$attribute->isStatic()) {
1004                $tableAttributes[$attribute->getBackendTable()][] = $attributeId;
1005            }
1006        }
1007
1008        $selects = array();
1009        foreach ($tableAttributes as $table=>$attributes) {
1010            $selects[] = $this->_getLoadAttributesSelect($table, $attributes);
1011        }
1012        if (!empty($selects)) {
1013            try {
1014                $select = implode(' UNION ', $selects);
1015                $values = $this->_fetchAll($select);
1016            } catch (Exception $e) {
1017                Mage::printException($e, $select);
1018                $this->printLogQuery(true, true, $select);
1019                throw $e;
1020            }
1021
1022            foreach ($values as $value) {
1023                $this->_setItemAttributeValue($value);
1024            }
1025        }
1026        return $this;
1027    }
1028
1029    /**
1030     * Retrieve attributes load select
1031     *
1032     * @param   string $table
1033     * @return  Mage_Eav_Model_Entity_Collection_Abstract
1034     */
1035    protected function _getLoadAttributesSelect($table, $attributeIds=array())
1036    {
1037        if (empty($attributeIds)) {
1038            $attributeIds = $this->_selectAttributes;
1039        }
1040        $entityIdField = $this->getEntity()->getEntityIdField();
1041        $select = $this->getConnection()->select()
1042            ->from($table, array($entityIdField, 'attribute_id', 'value'))
1043            ->where('entity_type_id=?', $this->getEntity()->getTypeId())
1044            ->where("$entityIdField in (?)", array_keys($this->_itemsById))
1045            ->where('attribute_id in (?)', $attributeIds);
1046        return $select;
1047    }
1048
1049    /**
1050     * Initialize entity ubject property value
1051     *
1052     * $valueInfo is _getLoadAttributesSelect fetch result row
1053     *
1054     * @param   array $valueInfo
1055     * @return  Mage_Eav_Model_Entity_Collection_Abstract
1056     */
1057    protected function _setItemAttributeValue($valueInfo)
1058    {
1059        $entityIdField  = $this->getEntity()->getEntityIdField();
1060        $entityId       = $valueInfo[$entityIdField];
1061        if (!isset($this->_itemsById[$entityId])) {
1062            Mage::throwException('Mage_Eav',
1063                Mage::helper('eav')->__('Data integrity: No header row found for attribute.')
1064            );
1065        }
1066        $attributeCode = array_search($valueInfo['attribute_id'], $this->_selectAttributes);
1067        if (!$attributeCode) {
1068            $attribute = Mage::getSingleton('eav/config')->getCollectionAttribute(
1069                $this->getEntity()->getType(),
1070                $valueInfo['attribute_id']
1071            );
1072            $attributeCode = $attribute->getAttributeCode();
1073        }
1074
1075        foreach ($this->_itemsById[$entityId] as $object) {
1076            $object->setData($attributeCode, $valueInfo['value']);
1077        }
1078        return $this;
1079    }
1080
1081    /**
1082     * Get alias for attribute value table
1083     *
1084     * @param string $attributeCode
1085     * @return string
1086     */
1087    protected function _getAttributeTableAlias($attributeCode)
1088    {
1089        return '_table_'.$attributeCode;
1090    }
1091
1092    protected function _getAttributeFieldName($attributeCode)
1093    {
1094        if (isset($this->_joinAttributes[$attributeCode]['condition_alias'])) {
1095            return $this->_joinAttributes[$attributeCode]['condition_alias'];
1096        }
1097        if (isset($this->_staticFields[$attributeCode])) {
1098            return sprintf('e.%s', $attributeCode);
1099        }
1100        if (isset($this->_joinFields[$attributeCode])) {
1101            $attr = $this->_joinFields[$attributeCode];
1102            return $attr['table'] ? $attr['table'] .'.'.$attr['field'] : $attr['field'];
1103        }
1104
1105        $attribute = $this->getAttribute($attributeCode);
1106        if (!$attribute) {
1107            throw Mage::exception('Mage_Eav', Mage::helper('eav')->__('Invalid attribute name: %s.', $attributeCode));
1108        }
1109
1110        if ($attribute->isStatic()) {
1111            if (isset($this->_joinAttributes[$attributeCode])) {
1112                $fieldName = $this->_getAttributeTableAlias($attributeCode).'.'.$attributeCode;
1113            } else {
1114                $fieldName = 'e.'.$attributeCode;
1115            }
1116        } else {
1117            $fieldName = $this->_getAttributeTableAlias($attributeCode).'.value';
1118        }
1119        return $fieldName;
1120    }
1121
1122    /**
1123     * Add attribute value table to the join if it wasn't added previously
1124     *
1125     * @param   string $attributeCode
1126     * @param   string $joinType inner|left
1127     * @return  Mage_Eav_Model_Entity_Collection_Abstract
1128     */
1129    protected function _addAttributeJoin($attributeCode, $joinType='inner')
1130    {
1131        if (!empty($this->_filterAttributes[$attributeCode])) {
1132            return $this;
1133        }
1134
1135        $adapter = $this->getConnection();
1136
1137        $attrTable = $this->_getAttributeTableAlias($attributeCode);
1138        if (isset($this->_joinAttributes[$attributeCode])) {
1139            $attribute      = $this->_joinAttributes[$attributeCode]['attribute'];
1140            $entity         = $attribute->getEntity();
1141            $entityIdField  = $entity->getEntityIdField();
1142            $fkName         = $this->_joinAttributes[$attributeCode]['bind'];
1143            $fkAttribute    = $this->_joinAttributes[$attributeCode]['bindAttribute'];
1144            $fkTable        = $this->_getAttributeTableAlias($fkName);
1145
1146            if ($fkAttribute->getBackend()->isStatic()) {
1147                if (isset($this->_joinAttributes[$fkName])) {
1148                    $fk = $fkTable.".".$fkAttribute->getAttributeCode();
1149                } else {
1150                    $fk = "e.".$fkAttribute->getAttributeCode();
1151                }
1152            } else {
1153                $this->_addAttributeJoin($fkAttribute->getAttributeCode(), $joinType);
1154                $fk = "$fkTable.value";
1155            }
1156            $pk = $attrTable.'.'.$this->_joinAttributes[$attributeCode]['filter'];
1157        } else {
1158            $entity         = $this->getEntity();
1159            $entityIdField  = $entity->getEntityIdField();
1160            $attribute      = $entity->getAttribute($attributeCode);
1161            $fk             = "e.$entityIdField";
1162            $pk             = "$attrTable.$entityIdField";
1163        }
1164
1165        if (!$attribute) {
1166            throw Mage::exception('Mage_Eav', Mage::helper('eav')->__('Invalid attribute name: %s.', $attributeCode));
1167        }
1168
1169        if ($attribute->getBackend()->isStatic()) {
1170            $attrFieldName = "$attrTable.".$attribute->getAttributeCode();
1171        } else {
1172            $attrFieldName = "$attrTable.value";
1173        }
1174
1175        $fk = $adapter->quoteColumnAs($fk, null);
1176        $pk = $adapter->quoteColumnAs($pk, null);
1177
1178        $condArr = array("$pk = $fk");
1179        if (!$attribute->getBackend()->isStatic()) {
1180            $condArr[] = $this->getConnection()->quoteInto(
1181                $adapter->quoteColumnAs("$attrTable.attribute_id", null) . ' = ?', $attribute->getId());
1182        }
1183
1184        /**
1185         * process join type
1186         */
1187        $joinMethod = ($joinType == 'left') ? 'joinLeft' : 'join';
1188
1189        $this->_joinAttributeToSelect($joinMethod, $attribute, $attrTable, $condArr, $attributeCode, $attrFieldName);
1190
1191        $this->removeAttributeToSelect($attributeCode);
1192        $this->_filterAttributes[$attributeCode] = $attribute->getId();
1193
1194        /**
1195         * Fix double join for using same as filter
1196         */
1197        $this->_joinFields[$attributeCode] = array(
1198            'table' => '',
1199            'field' => $attrFieldName,
1200        );
1201
1202        return $this;
1203    }
1204
1205    /**
1206     * Adding join statement to collection select instance
1207     *
1208     * @param   string $method
1209     * @param   object $attribute
1210     * @param   string $tableAlias
1211     * @param   array $condition
1212     * @param   string $fieldCode
1213     * @param   string $fieldAlias
1214     * @return  Mage_Eav_Model_Entity_Collection_Abstract
1215     */
1216    protected function _joinAttributeToSelect($method, $attribute, $tableAlias, $condition, $fieldCode, $fieldAlias)
1217    {
1218        $this->getSelect()->$method(
1219            array($tableAlias => $attribute->getBackend()->getTable()),
1220            '('.join(') AND (', $condition).')',
1221            array($fieldCode=>$fieldAlias)
1222        );
1223        return $this;
1224    }
1225
1226    /**
1227     * Get condition sql for the attribute
1228     *
1229     * @see self::_getConditionSql
1230     * @param string $attribute
1231     * @param mixed $condition
1232     * @param string $joinType
1233     * @return string
1234     */
1235    protected function _getAttributeConditionSql($attribute, $condition, $joinType='inner')
1236    {
1237        if (isset($this->_joinFields[$attribute])) {
1238            return $this->_getConditionSql($this->_getAttributeFieldName($attribute), $condition);
1239        }
1240        if (isset($this->_staticFields[$attribute])) {
1241            return $this->_getConditionSql(sprintf('e.%s', $attribute), $condition);
1242        }
1243        // process linked attribute
1244        if (isset($this->_joinAttributes[$attribute])) {
1245            $entity = $this->getAttribute($attribute)->getEntity();
1246            $entityTable = $entity->getEntityTable();
1247        } else {
1248            $entity = $this->getEntity();
1249            $entityTable = 'e';
1250        }
1251
1252        if ($entity->isAttributeStatic($attribute)) {
1253            $conditionSql = $this->_getConditionSql('e.'.$attribute, $condition);
1254        } else {
1255            $this->_addAttributeJoin($attribute, $joinType);
1256            if (isset($this->_joinAttributes[$attribute]['condition_alias'])) {
1257                $field = $this->_joinAttributes[$attribute]['condition_alias'];
1258            }
1259            else {
1260                $field = $this->_getAttributeTableAlias($attribute).'.value';
1261            }
1262            $conditionSql = $this->_getConditionSql($field, $condition);
1263        }
1264        return $conditionSql;
1265    }
1266
1267    /**
1268     * Set sorting order
1269     *
1270     * $attribute can also be an array of attributes
1271     *
1272     * @param string|array $attribute
1273     * @param string $dir
1274     * @return Mage_Eav_Model_Entity_Collection_Abstract
1275     */
1276    public function setOrder($attribute, $dir='desc')
1277    {
1278        if (is_array($attribute)) {
1279            foreach ($attribute as $attr) {
1280                $this->addAttributeToSort($attr, $dir);
1281            }
1282        } else {
1283            $this->addAttributeToSort($attribute, $dir);
1284        }
1285        return $this;
1286    }
1287
1288
1289    public function toArray($arrAttributes = array())
1290    {
1291        $arr = array();
1292        foreach ($this->_items as $k=>$item) {
1293            $arr[$k] = $item->toArray($arrAttributes);
1294        }
1295        return $arr;
1296    }
1297
1298    protected function _beforeLoad()
1299    {
1300        return $this;
1301    }
1302
1303    protected function _afterLoad()
1304    {
1305        return $this;
1306    }
1307
1308    /**
1309     * Reset collection
1310     *
1311     * @return Mage_Eav_Model_Entity_Collection_Abstract
1312     */
1313    protected function _reset()
1314    {
1315        parent::_reset();
1316
1317        $this->_selectEntityTypes = array();
1318        $this->_selectAttributes = array();
1319        $this->_filterAttributes = array();
1320        $this->_joinEntities = array();
1321        $this->_joinAttributes = array();
1322        $this->_joinFields = array();
1323
1324        return $this;
1325    }
1326
1327    /**
1328     * Returns already loaded element ids
1329     *
1330     * return array
1331     */
1332    public function getLoadedIds()
1333    {
1334        return array_keys($this->_items);
1335    }
1336}