PageRenderTime 327ms CodeModel.GetById 151ms app.highlight 105ms RepoModel.GetById 56ms app.codeStats 1ms

/app/code/core/Mage/ImportExport/Model/Import/Entity/Product.php

https://bitbucket.org/claudiu_marginean/magento-hg-mirror
PHP | 1472 lines | 1025 code | 104 blank | 343 comment | 187 complexity | ab7c1c659a000bea23ca4f2b94447a6e MD5 | raw file

Large files files are truncated, but you can click here to view the full 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_ImportExport
  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 * Import entity product model
  29 *
  30 * @category    Mage
  31 * @package     Mage_ImportExport
  32 * @author      Magento Core Team <core@magentocommerce.com>
  33 */
  34class Mage_ImportExport_Model_Import_Entity_Product extends Mage_ImportExport_Model_Import_Entity_Abstract
  35{
  36    const CONFIG_KEY_PRODUCT_TYPES = 'global/importexport/import_product_types';
  37
  38    /**
  39     * Size of bunch - part of products to save in one step.
  40     */
  41    const BUNCH_SIZE = 20;
  42
  43    /**
  44     * Value that means all entities (e.g. websites, groups etc.)
  45     */
  46    const VALUE_ALL = 'all';
  47
  48    /**
  49     * Data row scopes.
  50     */
  51    const SCOPE_DEFAULT = 1;
  52    const SCOPE_WEBSITE = 2;
  53    const SCOPE_STORE   = 0;
  54    const SCOPE_NULL    = -1;
  55
  56    /**
  57     * Permanent column names.
  58     *
  59     * Names that begins with underscore is not an attribute. This name convention is for
  60     * to avoid interference with same attribute name.
  61     */
  62    const COL_STORE    = '_store';
  63    const COL_ATTR_SET = '_attribute_set';
  64    const COL_TYPE     = '_type';
  65    const COL_CATEGORY = '_category';
  66    const COL_SKU      = 'sku';
  67
  68    /**
  69     * Error codes.
  70     */
  71    const ERROR_INVALID_SCOPE            = 'invalidScope';
  72    const ERROR_INVALID_WEBSITE          = 'invalidWebsite';
  73    const ERROR_INVALID_STORE            = 'invalidStore';
  74    const ERROR_INVALID_ATTR_SET         = 'invalidAttrSet';
  75    const ERROR_INVALID_TYPE             = 'invalidType';
  76    const ERROR_INVALID_CATEGORY         = 'invalidCategory';
  77    const ERROR_VALUE_IS_REQUIRED        = 'isRequired';
  78    const ERROR_TYPE_CHANGED             = 'typeChanged';
  79    const ERROR_SKU_IS_EMPTY             = 'skuEmpty';
  80    const ERROR_NO_DEFAULT_ROW           = 'noDefaultRow';
  81    const ERROR_CHANGE_TYPE              = 'changeProductType';
  82    const ERROR_DUPLICATE_SCOPE          = 'duplicateScope';
  83    const ERROR_DUPLICATE_SKU            = 'duplicateSKU';
  84    const ERROR_CHANGE_ATTR_SET          = 'changeAttrSet';
  85    const ERROR_TYPE_UNSUPPORTED         = 'productTypeUnsupported';
  86    const ERROR_ROW_IS_ORPHAN            = 'rowIsOrphan';
  87    const ERROR_INVALID_TIER_PRICE_QTY   = 'invalidTierPriceOrQty';
  88    const ERROR_INVALID_TIER_PRICE_SITE  = 'tierPriceWebsiteInvalid';
  89    const ERROR_INVALID_TIER_PRICE_GROUP = 'tierPriceGroupInvalid';
  90    const ERROR_TIER_DATA_INCOMPLETE     = 'tierPriceDataIsIncomplete';
  91    const ERROR_SKU_NOT_FOUND_FOR_DELETE = 'skuNotFoundToDelete';
  92
  93    /**
  94     * Pairs of attribute set ID-to-name.
  95     *
  96     * @var array
  97     */
  98    protected $_attrSetIdToName = array();
  99
 100    /**
 101     * Pairs of attribute set name-to-ID.
 102     *
 103     * @var array
 104     */
 105    protected $_attrSetNameToId = array();
 106
 107    /**
 108     * Categories text-path to ID hash.
 109     *
 110     * @var array
 111     */
 112    protected $_categories = array();
 113
 114    /**
 115     * Customer groups ID-to-name.
 116     *
 117     * @var array
 118     */
 119    protected $_customerGroups = array();
 120
 121    /**
 122     * Attributes with index (not label) value.
 123     *
 124     * @var array
 125     */
 126    protected $_indexValueAttributes = array(
 127        'status',
 128        'tax_class_id',
 129        'visibility',
 130        'enable_googlecheckout',
 131        'gift_message_available',
 132        'custom_design'
 133    );
 134
 135    /**
 136     * Links attribute name-to-link type ID.
 137     *
 138     * @var array
 139     */
 140    protected $_linkNameToId = array(
 141        '_links_related_'   => Mage_Catalog_Model_Product_Link::LINK_TYPE_RELATED,
 142        '_links_crosssell_' => Mage_Catalog_Model_Product_Link::LINK_TYPE_CROSSSELL,
 143        '_links_upsell_'    => Mage_Catalog_Model_Product_Link::LINK_TYPE_UPSELL
 144    );
 145
 146    /**
 147     * Validation failure message template definitions
 148     *
 149     * @var array
 150     */
 151    protected $_messageTemplates = array(
 152        self::ERROR_INVALID_SCOPE            => 'Invalid value in Scope column',
 153        self::ERROR_INVALID_WEBSITE          => 'Invalid value in Website column (website does not exists?)',
 154        self::ERROR_INVALID_STORE            => 'Invalid value in Store column (store does not exists?)',
 155        self::ERROR_INVALID_ATTR_SET         => 'Invalid value for Attribute Set column (set does not exists?)',
 156        self::ERROR_INVALID_TYPE             => 'Product Type is invalid or not supported',
 157        self::ERROR_INVALID_CATEGORY         => 'Category does not exists',
 158        self::ERROR_VALUE_IS_REQUIRED        => "Required attribute '%s' has an empty value",
 159        self::ERROR_TYPE_CHANGED             => 'Trying to change type of existing products',
 160        self::ERROR_SKU_IS_EMPTY             => 'SKU is empty',
 161        self::ERROR_NO_DEFAULT_ROW           => 'Default values row does not exists',
 162        self::ERROR_CHANGE_TYPE              => 'Product type change is not allowed',
 163        self::ERROR_DUPLICATE_SCOPE          => 'Duplicate scope',
 164        self::ERROR_DUPLICATE_SKU            => 'Duplicate SKU',
 165        self::ERROR_CHANGE_ATTR_SET          => 'Product attribute set change is not allowed',
 166        self::ERROR_TYPE_UNSUPPORTED         => 'Product type is not supported',
 167        self::ERROR_ROW_IS_ORPHAN            => 'Orphan rows that will be skipped due default row errors',
 168        self::ERROR_INVALID_TIER_PRICE_QTY   => 'Tier Price data price or quantity value is invalid',
 169        self::ERROR_INVALID_TIER_PRICE_SITE  => 'Tier Price data website is invalid',
 170        self::ERROR_INVALID_TIER_PRICE_GROUP => 'Tier Price customer group ID is invalid',
 171        self::ERROR_TIER_DATA_INCOMPLETE     => 'Tier Price data is incomplete',
 172        self::ERROR_SKU_NOT_FOUND_FOR_DELETE => 'Product with specified SKU not found'
 173    );
 174
 175    /**
 176     * Dry-runned products information from import file.
 177     *
 178     * [SKU] => array(
 179     *     'type_id'        => (string) product type
 180     *     'attr_set_id'    => (int) product attribute set ID
 181     *     'entity_id'      => (int) product ID (value for new products will be set after entity save)
 182     *     'attr_set_code'  => (string) attribute set code
 183     * )
 184     *
 185     * @var array
 186     */
 187    protected $_newSku = array();
 188
 189    /**
 190     * Existing products SKU-related information in form of array:
 191     *
 192     * [SKU] => array(
 193     *     'type_id'        => (string) product type
 194     *     'attr_set_id'    => (int) product attribute set ID
 195     *     'entity_id'      => (int) product ID
 196     *     'supported_type' => (boolean) is product type supported by current version of import module
 197     * )
 198     *
 199     * @var array
 200     */
 201    protected $_oldSku = array();
 202
 203    /**
 204     * Column names that holds values with particular meaning.
 205     *
 206     * @var array
 207     */
 208    protected $_particularAttributes = array(
 209        '_store', '_attribute_set', '_type', '_category', '_product_websites', '_tier_price_website',
 210        '_tier_price_customer_group', '_tier_price_qty', '_tier_price_price', '_links_related_sku',
 211        '_links_related_position', '_links_crosssell_sku', '_links_crosssell_position', '_links_upsell_sku',
 212        '_links_upsell_position', '_custom_option_store', '_custom_option_type', '_custom_option_title',
 213        '_custom_option_is_required', '_custom_option_price', '_custom_option_sku', '_custom_option_max_characters',
 214        '_custom_option_sort_order', '_custom_option_file_extension', '_custom_option_image_size_x',
 215        '_custom_option_image_size_y', '_custom_option_row_title', '_custom_option_row_price',
 216        '_custom_option_row_sku', '_custom_option_row_sort'
 217    );
 218
 219    /**
 220     * Permanent entity columns.
 221     *
 222     * @var array
 223     */
 224    protected $_permanentAttributes = array(self::COL_SKU);
 225
 226    /**
 227     * Array of supported product types as keys with appropriate model object as value.
 228     *
 229     * @var array
 230     */
 231    protected $_productTypeModels = array();
 232
 233    /**
 234     * All stores code-ID pairs.
 235     *
 236     * @var array
 237     */
 238    protected $_storeCodeToId = array();
 239
 240    /**
 241     * Store ID to its website stores IDs.
 242     *
 243     * @var array
 244     */
 245    protected $_storeIdToWebsiteStoreIds = array();
 246
 247    /**
 248     * Website code-to-ID
 249     *
 250     * @var array
 251     */
 252    protected $_websiteCodeToId = array();
 253
 254    /**
 255     * Website code to store code-to-ID pairs which it consists.
 256     *
 257     * @var array
 258     */
 259    protected $_websiteCodeToStoreIds = array();
 260
 261    /**
 262     * Constructor.
 263     *
 264     * @return void
 265     */
 266    public function __construct()
 267    {
 268        parent::__construct();
 269
 270        $this->_initWebsites()
 271            ->_initStores()
 272            ->_initAttributeSets()
 273            ->_initTypeModels()
 274            ->_initCategories()
 275            ->_initSkus()
 276            ->_initCustomerGroups();
 277    }
 278
 279    /**
 280     * Delete products.
 281     *
 282     * @return Mage_ImportExport_Model_Import_Entity_Product
 283     */
 284    protected function _deleteProducts()
 285    {
 286        $productEntityTable = Mage::getModel('importexport/import_proxy_product_resource')->getEntityTable();
 287
 288        while ($bunch = $this->_dataSourceModel->getNextBunch()) {
 289            $idToDelete = array();
 290
 291            foreach ($bunch as $rowNum => $rowData) {
 292                if ($this->validateRow($rowData, $rowNum) && self::SCOPE_DEFAULT == $this->getRowScope($rowData)) {
 293                    $idToDelete[] = $this->_oldSku[$rowData[self::COL_SKU]]['entity_id'];
 294                }
 295            }
 296            if ($idToDelete) {
 297                $this->_connection->query(
 298                    $this->_connection->quoteInto(
 299                        "DELETE FROM `{$productEntityTable}` WHERE `entity_id` IN (?)", $idToDelete
 300                    )
 301                );
 302            }
 303        }
 304        return $this;
 305    }
 306
 307    /**
 308     * Create Product entity from raw data.
 309     *
 310     * @throws Exception
 311     * @return bool Result of operation.
 312     */
 313    protected function _importData()
 314    {
 315        if (Mage_ImportExport_Model_Import::BEHAVIOR_DELETE == $this->getBehavior()) {
 316            $this->_deleteProducts();
 317        } else {
 318            $this->_saveProducts();
 319            $this->_saveStockItem();
 320            $this->_saveLinks();
 321            $this->_saveCustomOptions();
 322            foreach ($this->_productTypeModels as $productType => $productTypeModel) {
 323                $productTypeModel->saveData();
 324            }
 325        }
 326        return true;
 327    }
 328
 329    /**
 330     * Initialize attribute sets code-to-id pairs.
 331     *
 332     * @return Mage_ImportExport_Model_Import_Entity_Product
 333     */
 334    protected function _initAttributeSets()
 335    {
 336        foreach (Mage::getResourceModel('eav/entity_attribute_set_collection')
 337                ->setEntityTypeFilter($this->_entityTypeId) as $attributeSet) {
 338            $this->_attrSetNameToId[$attributeSet->getAttributeSetName()] = $attributeSet->getId();
 339            $this->_attrSetIdToName[$attributeSet->getId()] = $attributeSet->getAttributeSetName();
 340        }
 341        return $this;
 342    }
 343
 344    /**
 345     * Initialize categories text-path to ID hash.
 346     *
 347     * @return Mage_ImportExport_Model_Import_Entity_Product
 348     */
 349    protected function _initCategories()
 350    {
 351        $collection = Mage::getResourceModel('catalog/category_collection')->addNameToResult();
 352        /* @var $collection Mage_Catalog_Model_Resource_Eav_Mysql4_Category_Collection */
 353        foreach ($collection as $category) {
 354            $structure = explode('/', $category->getPath());
 355            $pathSize  = count($structure);
 356            if ($pathSize > 2) {
 357                $path = array();
 358                for ($i = 2; $i < $pathSize; $i++) {
 359                    $path[] = $collection->getItemById($structure[$i])->getName();
 360                }
 361                $this->_categories[implode('/', $path)] = $category->getId();
 362            }
 363        }
 364        return $this;
 365    }
 366
 367    /**
 368     * Initialize customer groups.
 369     *
 370     * @return Mage_ImportExport_Model_Import_Entity_Product
 371     */
 372    protected function _initCustomerGroups()
 373    {
 374        foreach (Mage::getResourceModel('customer/group_collection') as $customerGroup) {
 375            $this->_customerGroups[$customerGroup->getId()] = true;
 376        }
 377        return $this;
 378    }
 379
 380    /**
 381     * Initialize existent product SKUs.
 382     *
 383     * @return Mage_ImportExport_Model_Import_Entity_Product
 384     */
 385    protected function _initSkus()
 386    {
 387        foreach (Mage::getResourceModel('catalog/product_collection') as $product) {
 388            $typeId = $product->getTypeId();
 389            $sku = $product->getSku();
 390            $this->_oldSku[$sku] = array(
 391                'type_id'        => $typeId,
 392                'attr_set_id'    => $product->getAttributeSetId(),
 393                'entity_id'      => $product->getId(),
 394                'supported_type' => isset($this->_productTypeModels[$typeId])
 395            );
 396        }
 397        return $this;
 398    }
 399
 400    /**
 401     * Initialize stores hash.
 402     *
 403     * @return Mage_ImportExport_Model_Import_Entity_Product
 404     */
 405    protected function _initStores()
 406    {
 407        foreach (Mage::app()->getStores() as $store) {
 408            $this->_storeCodeToId[$store->getCode()] = $store->getId();
 409            $this->_storeIdToWebsiteStoreIds[$store->getId()] = $store->getWebsite()->getStoreIds();
 410        }
 411        return $this;
 412    }
 413
 414    /**
 415     * Initialize product type models.
 416     *
 417     * @throws Exception
 418     * @return Mage_ImportExport_Model_Import_Entity_Product
 419     */
 420    protected function _initTypeModels()
 421    {
 422        $config = Mage::getConfig()->getNode(self::CONFIG_KEY_PRODUCT_TYPES)->asCanonicalArray();
 423        foreach ($config as $type => $typeModel) {
 424            if (!($model = Mage::getModel($typeModel, array($this, $type)))) {
 425                Mage::throwException("Entity type model '{$typeModel}' is not found");
 426            }
 427            if (! $model instanceof Mage_ImportExport_Model_Import_Entity_Product_Type_Abstract) {
 428                Mage::throwException(
 429                    Mage::helper('importexport')->__('Entity type model must be an instance of Mage_ImportExport_Model_Import_Entity_Product_Type_Abstract')
 430                );
 431            }
 432            if ($model->isSuitable()) {
 433                $this->_productTypeModels[$type] = $model;
 434            }
 435            $this->_particularAttributes = array_merge(
 436                $this->_particularAttributes,
 437                $model->getParticularAttributes()
 438            );
 439        }
 440        // remove doubles
 441        $this->_particularAttributes = array_unique($this->_particularAttributes);
 442
 443        return $this;
 444    }
 445
 446    /**
 447     * Initialize website values.
 448     *
 449     * @return Mage_ImportExport_Model_Import_Entity_Product
 450     */
 451    protected function _initWebsites()
 452    {
 453        /** @var $website Mage_Core_Model_Website */
 454        foreach (Mage::app()->getWebsites() as $website) {
 455            $this->_websiteCodeToId[$website->getCode()] = $website->getId();
 456            $this->_websiteCodeToStoreIds[$website->getCode()] = array_flip($website->getStoreCodes());
 457        }
 458        return $this;
 459    }
 460
 461    /**
 462     * Check product category validity.
 463     *
 464     * @param array $rowData
 465     * @param int $rowNum
 466     * @return bool
 467     */
 468    protected function _isProductCategoryValid(array $rowData, $rowNum)
 469    {
 470        if (!empty($rowData[self::COL_CATEGORY]) && !isset($this->_categories[$rowData[self::COL_CATEGORY]])) {
 471            $this->addRowError(self::ERROR_INVALID_CATEGORY, $rowNum);
 472            return false;
 473        }
 474        return true;
 475    }
 476
 477    /**
 478     * Check product website belonging.
 479     *
 480     * @param array $rowData
 481     * @param int $rowNum
 482     * @return bool
 483     */
 484    protected function _isProductWebsiteValid(array $rowData, $rowNum)
 485    {
 486        if (!empty($rowData['_product_websites']) && !isset($this->_websiteCodeToId[$rowData['_product_websites']])) {
 487            $this->addRowError(self::ERROR_INVALID_WEBSITE, $rowNum);
 488            return false;
 489        }
 490        return true;
 491    }
 492
 493    /**
 494     * Set valid attribute set and product type to rows with all scopes
 495     * to ensure that existing products doesn't changed.
 496     *
 497     * @param array $rowData
 498     * @return array
 499     */
 500    protected function _prepareRowForDb(array $rowData)
 501    {
 502        static $lastSku  = null;
 503
 504        if (Mage_ImportExport_Model_Import::BEHAVIOR_DELETE == $this->getBehavior()) {
 505            return $rowData;
 506        }
 507        if (self::SCOPE_DEFAULT == $this->getRowScope($rowData)) {
 508            $lastSku = $rowData[self::COL_SKU];
 509        }
 510        if (isset($this->_oldSku[$lastSku])) {
 511            $rowData[self::COL_ATTR_SET] = $this->_newSku[$lastSku]['attr_set_code'];
 512            $rowData[self::COL_TYPE]     = $this->_newSku[$lastSku]['type_id'];
 513        }
 514
 515        return $rowData;
 516    }
 517
 518    /**
 519     * Check tier orice data validity.
 520     *
 521     * @param array $rowData
 522     * @param int $rowNum
 523     * @return bool
 524     */
 525    protected function _isTierPriceValid(array $rowData, $rowNum)
 526    {
 527        if ((isset($rowData['_tier_price_website']) && strlen($rowData['_tier_price_website']))
 528                || (isset($rowData['_tier_price_customer_group']) && strlen($rowData['_tier_price_customer_group']))
 529                || (isset($rowData['_tier_price_qty']) && strlen($rowData['_tier_price_qty']))
 530                || (isset($rowData['_tier_price_price']) && strlen($rowData['_tier_price_price']))
 531        ) {
 532            if (!isset($rowData['_tier_price_website']) || !isset($rowData['_tier_price_customer_group'])
 533                    || !isset($rowData['_tier_price_qty']) || !isset($rowData['_tier_price_price'])
 534                    || !strlen($rowData['_tier_price_website']) || !strlen($rowData['_tier_price_customer_group'])
 535                    || !strlen($rowData['_tier_price_qty']) || !strlen($rowData['_tier_price_price'])
 536            ) {
 537                $this->addRowError(self::ERROR_TIER_DATA_INCOMPLETE, $rowNum);
 538                return false;
 539            } elseif ($rowData['_tier_price_website'] != self::VALUE_ALL
 540                    && !isset($this->_websiteCodeToId[$rowData['_tier_price_website']])) {
 541                $this->addRowError(self::ERROR_INVALID_TIER_PRICE_SITE, $rowNum);
 542                return false;
 543            } elseif ($rowData['_tier_price_customer_group'] != self::VALUE_ALL
 544                    && !isset($this->_customerGroups[$rowData['_tier_price_customer_group']])) {
 545                $this->addRowError(self::ERROR_INVALID_TIER_PRICE_GROUP, $rowNum);
 546                return false;
 547            } elseif ($rowData['_tier_price_qty'] <= 0 || $rowData['_tier_price_price'] <= 0) {
 548                $this->addRowError(self::ERROR_INVALID_TIER_PRICE_QTY, $rowNum);
 549                return false;
 550            }
 551        }
 552        return true;
 553    }
 554
 555    /**
 556     * Custom options save.
 557     *
 558     * @return Mage_ImportExport_Model_Import_Entity_Product
 559     */
 560    protected function _saveCustomOptions()
 561    {
 562        $productTable   = Mage::getSingleton('core/resource')->getTableName('catalog/product');
 563        $optionTable    = Mage::getSingleton('core/resource')->getTableName('catalog/product_option');
 564        $priceTable     = Mage::getSingleton('core/resource')->getTableName('catalog/product_option_price');
 565        $titleTable     = Mage::getSingleton('core/resource')->getTableName('catalog/product_option_title');
 566        $typePriceTable = Mage::getSingleton('core/resource')->getTableName('catalog/product_option_type_price');
 567        $typeTitleTable = Mage::getSingleton('core/resource')->getTableName('catalog/product_option_type_title');
 568        $typeValueTable = Mage::getSingleton('core/resource')->getTableName('catalog/product_option_type_value');
 569        $nextOptionId   = $this->getNextAutoincrement($optionTable);
 570        $nextValueId    = $this->getNextAutoincrement($typeValueTable);
 571        $priceIsGlobal  = Mage::helper('catalog')->isPriceGlobal();
 572        $type           = null;
 573        $typeSpecific   = array(
 574            'date'      => array('price', 'sku'),
 575            'date_time' => array('price', 'sku'),
 576            'time'      => array('price', 'sku'),
 577            'field'     => array('price', 'sku', 'max_characters'),
 578            'area'      => array('price', 'sku', 'max_characters'),
 579            //'file'      => array('price', 'sku', 'file_extension', 'image_size_x', 'image_size_y'),
 580            'drop_down' => true,
 581            'radio'     => true,
 582            'checkbox'  => true,
 583            'multiple'  => true
 584        );
 585
 586        while ($bunch = $this->_dataSourceModel->getNextBunch()) {
 587            $customOptions = array(
 588                'product_id'    => array(),
 589                $optionTable    => array(),
 590                $priceTable     => array(),
 591                $titleTable     => array(),
 592                $typePriceTable => array(),
 593                $typeTitleTable => array(),
 594                $typeValueTable => array()
 595            );
 596
 597            foreach ($bunch as $rowNum => $rowData) {
 598                if (!$this->isRowAllowedToImport($rowData, $rowNum)) {
 599                    continue;
 600                }
 601                if (self::SCOPE_DEFAULT == $this->getRowScope($rowData)) {
 602                    $productId = $this->_newSku[$rowData[self::COL_SKU]]['entity_id'];
 603                } elseif (!isset($productId)) {
 604                    continue;
 605                }
 606                if (!empty($rowData['_custom_option_store'])) {
 607                    if (!isset($this->_storeCodeToId[$rowData['_custom_option_store']])) {
 608                        continue;
 609                    }
 610                    $storeId = $this->_storeCodeToId[$rowData['_custom_option_store']];
 611                } else {
 612                    $storeId = Mage_Catalog_Model_Abstract::DEFAULT_STORE_ID;
 613                }
 614                if (!empty($rowData['_custom_option_type'])) { // get CO type if its specified
 615                    if (!isset($typeSpecific[$rowData['_custom_option_type']])) {
 616                        $type = null;
 617                        continue;
 618                    }
 619                    $type = $rowData['_custom_option_type'];
 620                    $rowIsMain = true;
 621                } else {
 622                    if (null === $type) {
 623                        continue;
 624                    }
 625                    $rowIsMain = false;
 626                }
 627                if (!isset($customOptions['product_id'][$productId])) { // for update product entity table
 628                    $customOptions['product_id'][$productId] = array(
 629                        'entity_id'        => $productId,
 630                        'has_options'      => 0,
 631                        'required_options' => 0,
 632                        'updated_at'       => now()
 633                    );
 634                }
 635                if ($rowIsMain) {
 636                    $solidParams = array(
 637                        'option_id'      => $nextOptionId,
 638                        'sku'            => '',
 639                        'max_characters' => 0,
 640                        'file_extension' => null,
 641                        'image_size_x'   => 0,
 642                        'image_size_y'   => 0,
 643                        'product_id'     => $productId,
 644                        'type'           => $type,
 645                        'is_require'     => empty($rowData['_custom_option_is_required']) ? 0 : 1,
 646                        'sort_order'     => empty($rowData['_custom_option_sort_order'])
 647                                            ? 0 : abs($rowData['_custom_option_sort_order'])
 648                    );
 649
 650                    if (true !== $typeSpecific[$type]) { // simple option may have optional params
 651                        $priceTableRow = array(
 652                            'option_id'  => $nextOptionId,
 653                            'store_id'   => Mage_Catalog_Model_Abstract::DEFAULT_STORE_ID,
 654                            'price'      => 0,
 655                            'price_type' => 'fixed'
 656                        );
 657
 658                        foreach ($typeSpecific[$type] as $paramSuffix) {
 659                            if (isset($rowData['_custom_option_' . $paramSuffix])) {
 660                                $data = $rowData['_custom_option_' . $paramSuffix];
 661
 662                                if (array_key_exists($paramSuffix, $solidParams)) {
 663                                    $solidParams[$paramSuffix] = $data;
 664                                } elseif ('price' == $paramSuffix) {
 665                                    if ('%' == substr($data, -1)) {
 666                                        $priceTableRow['price_type'] = 'percent';
 667                                    }
 668                                    $priceTableRow['price'] = (float) rtrim($data, '%');
 669                                }
 670                            }
 671                        }
 672                        $customOptions[$priceTable][] = $priceTableRow;
 673                    }
 674                    $customOptions[$optionTable][] = $solidParams;
 675                    $customOptions['product_id'][$productId]['has_options'] = 1;
 676
 677                    if (!empty($rowData['_custom_option_is_required'])) {
 678                        $customOptions['product_id'][$productId]['required_options'] = 1;
 679                    }
 680                    $prevOptionId = $nextOptionId++; // increment option id, but preserve value for $typeValueTable
 681                }
 682                if ($typeSpecific[$type] === true && !empty($rowData['_custom_option_row_title'])
 683                        && empty($rowData['_custom_option_store'])) {
 684                    // complex CO option row
 685                    $customOptions[$typeValueTable][$prevOptionId][] = array(
 686                        'option_type_id' => $nextValueId,
 687                        'sort_order'     => empty($rowData['_custom_option_row_sort'])
 688                                            ? 0 : abs($rowData['_custom_option_row_sort']),
 689                        'sku'            => !empty($rowData['_custom_option_row_sku'])
 690                                            ? $rowData['_custom_option_row_sku'] : ''
 691                    );
 692                    if (!isset($customOptions[$typeTitleTable][$nextValueId][0])) { // ensure default title is set
 693                        $customOptions[$typeTitleTable][$nextValueId][0] = $rowData['_custom_option_row_title'];
 694                    }
 695                    $customOptions[$typeTitleTable][$nextValueId][$storeId] = $rowData['_custom_option_row_title'];
 696
 697                    if (!empty($rowData['_custom_option_row_price'])) {
 698                        $typePriceRow = array(
 699                            'price'      => (float) rtrim($rowData['_custom_option_row_price'], '%'),
 700                            'price_type' => 'fixed'
 701                        );
 702                        if ('%' == substr($rowData['_custom_option_row_price'], -1)) {
 703                            $typePriceRow['price_type'] = 'percent';
 704                        }
 705                        if ($priceIsGlobal) {
 706                            $customOptions[$typePriceTable][$nextValueId][0] = $typePriceRow;
 707                        } else {
 708                            // ensure default price is set
 709                            if (!isset($customOptions[$typePriceTable][$nextValueId][0])) {
 710                                $customOptions[$typePriceTable][$nextValueId][0] = $typePriceRow;
 711                            }
 712                            $customOptions[$typePriceTable][$nextValueId][$storeId] = $typePriceRow;
 713                        }
 714                    }
 715                    $nextValueId++;
 716                }
 717                if (!empty($rowData['_custom_option_title'])) {
 718                    if (!isset($customOptions[$titleTable][$prevOptionId][0])) { // ensure default title is set
 719                        $customOptions[$titleTable][$prevOptionId][0] = $rowData['_custom_option_title'];
 720                    }
 721                    $customOptions[$titleTable][$prevOptionId][$storeId] = $rowData['_custom_option_title'];
 722                }
 723            }
 724            if ($this->getBehavior() != Mage_ImportExport_Model_Import::BEHAVIOR_APPEND) { // remove old data?
 725                $this->_connection->delete(
 726                    $optionTable,
 727                    $this->_connection->quoteInto('product_id IN (?)', array_keys($customOptions['product_id']))
 728                );
 729            }
 730            // if complex options does not contain values - ignore them
 731            foreach ($customOptions[$optionTable] as $key => $optionData) {
 732                if ($typeSpecific[$optionData['type']] === true
 733                        && !isset($customOptions[$typeValueTable][$optionData['option_id']])) {
 734                    unset($customOptions[$optionTable][$key], $customOptions[$titleTable][$optionData['option_id']]);
 735                }
 736            }
 737            if ($customOptions[$optionTable]) {
 738                $this->_connection->insertMultiple($optionTable, $customOptions[$optionTable]);
 739            } else {
 740                continue; // nothing to save
 741            }
 742            $titleRows = array();
 743
 744            foreach ($customOptions[$titleTable] as $optionId => $storeInfo) {
 745                foreach ($storeInfo as $storeId => $title) {
 746                    $titleRows[] = array('option_id' => $optionId, 'store_id' => $storeId, 'title' => $title);
 747                }
 748            }
 749            if ($titleRows) {
 750                $this->_connection->insertOnDuplicate($titleTable, $titleRows, array('title'));
 751            }
 752            if ($customOptions[$priceTable]) {
 753                $this->_connection->insertOnDuplicate(
 754                    $priceTable,
 755                    $customOptions[$priceTable],
 756                    array('price', 'price_type')
 757                );
 758            }
 759            $typeValueRows = array();
 760
 761            foreach ($customOptions[$typeValueTable] as $optionId => $optionInfo) {
 762                foreach ($optionInfo as $row) {
 763                    $row['option_id'] = $optionId;
 764                    $typeValueRows[]  = $row;
 765                }
 766            }
 767            if ($typeValueRows) {
 768                $this->_connection->insertMultiple($typeValueTable, $typeValueRows);
 769            }
 770            $optionTypePriceRows = array();
 771            $optionTypeTitleRows = array();
 772
 773            foreach ($customOptions[$typePriceTable] as $optionTypeId => $storesData) {
 774                foreach ($storesData as $storeId => $row) {
 775                    $row['option_type_id'] = $optionTypeId;
 776                    $row['store_id']       = $storeId;
 777                    $optionTypePriceRows[] = $row;
 778                }
 779            }
 780            foreach ($customOptions[$typeTitleTable] as $optionTypeId => $storesData) {
 781                foreach ($storesData as $storeId => $title) {
 782                    $optionTypeTitleRows[] = array(
 783                        'option_type_id' => $optionTypeId,
 784                        'store_id'       => $storeId,
 785                        'title'          => $title
 786                    );
 787                }
 788            }
 789            if ($optionTypePriceRows) {
 790                $this->_connection->insertOnDuplicate(
 791                    $typePriceTable,
 792                    $optionTypePriceRows,
 793                    array('price', 'price_type')
 794                );
 795            }
 796            if ($optionTypeTitleRows) {
 797                $this->_connection->insertOnDuplicate($typeTitleTable, $optionTypeTitleRows, array('title'));
 798            }
 799            if ($customOptions['product_id']) { // update product entity table to show that product has options
 800                $this->_connection->insertOnDuplicate(
 801                    $productTable,
 802                    $customOptions['product_id'],
 803                    array('has_options', 'required_options', 'updated_at')
 804                );
 805            }
 806        }
 807        return $this;
 808    }
 809
 810    /**
 811     * Gather and save information about product links.
 812     * Must be called after ALL products saving done.
 813     *
 814     * @return Mage_ImportExport_Model_Import_Entity_Product
 815     */
 816    protected function _saveLinks()
 817    {
 818        $resource       = Mage::getResourceModel('catalog/product_link');
 819        $mainTable      = $resource->getMainTable();
 820        $positionAttrId = array();
 821        $nextLinkId     = $this->getNextAutoincrement($mainTable);
 822
 823        // pre-load 'position' attributes ID for each link type once
 824        foreach ($this->_linkNameToId as $linkName => $linkId) {
 825            $select = $this->_connection->select()
 826                ->from(
 827                    $resource->getTable('catalog/product_link_attribute'),
 828                    array('id' => 'product_link_attribute_id')
 829                )
 830                ->where('link_type_id = ? AND product_link_attribute_code = "position"', $linkId);
 831            $positionAttrId[$linkId] = $this->_connection->fetchOne($select);
 832        }
 833        while ($bunch = $this->_dataSourceModel->getNextBunch()) {
 834            $productIds   = array();
 835            $linkRows     = array();
 836            $positionRows = array();
 837
 838            foreach ($bunch as $rowNum => $rowData) {
 839                if (!$this->isRowAllowedToImport($rowData, $rowNum)) {
 840                    continue;
 841                }
 842                if (self::SCOPE_DEFAULT == $this->getRowScope($rowData)) {
 843                    $sku = $rowData[self::COL_SKU];
 844                }
 845                foreach ($this->_linkNameToId as $linkName => $linkId) {
 846                    if (isset($rowData[$linkName . 'sku'])) {
 847                        $productId    = $this->_newSku[$sku]['entity_id'];
 848                        $productIds[] = $productId;
 849                        $linkedSku    = $rowData[$linkName . 'sku'];
 850
 851                        if ((isset($this->_newSku[$linkedSku]) || isset($this->_oldSku[$linkedSku]))
 852                                && $linkedSku != $sku) {
 853                            if (isset($this->_newSku[$linkedSku])) {
 854                                $linkedId = $this->_newSku[$linkedSku]['entity_id'];
 855                            } else {
 856                                $linkedId = $this->_oldSku[$linkedSku]['entity_id'];
 857                            }
 858                            $linkKey = "{$productId}-{$linkedId}-{$linkId}";
 859
 860                            if (!isset($linkRows[$linkKey])) {
 861                                $linkRows[$linkKey] = array(
 862                                    'link_id'           => $nextLinkId,
 863                                    'product_id'        => $productId,
 864                                    'linked_product_id' => $linkedId,
 865                                    'link_type_id'      => $linkId
 866                                );
 867                                if (!empty($rowData[$linkName . 'position'])) {
 868                                    $positionRows[] = array(
 869                                        'link_id'                   => $nextLinkId,
 870                                        'product_link_attribute_id' => $positionAttrId[$linkId],
 871                                        'value'                     => $rowData[$linkName . 'position']
 872                                    );
 873                                }
 874                                $nextLinkId++;
 875                            }
 876                        }
 877                    }
 878                }
 879            }
 880            if (Mage_ImportExport_Model_Import::BEHAVIOR_APPEND != $this->getBehavior() && $productIds) {
 881                $this->_connection->delete(
 882                    $mainTable,
 883                    $this->_connection->quoteInto('product_id IN (?)', array_keys($productIds))
 884                );
 885            }
 886            if ($linkRows) {
 887                $this->_connection->insertOnDuplicate(
 888                    $mainTable,
 889                    $linkRows,
 890                    array('link_id')
 891                );
 892            }
 893            if ($positionRows) { // process linked product positions
 894                $this->_connection->insertOnDuplicate(
 895                    $resource->getAttributeTypeTable('int'),
 896                    $positionRows,
 897                    array('value')
 898                );
 899            }
 900        }
 901        return $this;
 902    }
 903
 904    /**
 905     * Save product attributes.
 906     *
 907     * @param array $attributesData
 908     * @return Mage_ImportExport_Model_Import_Entity_Product
 909     */
 910    protected function _saveProductAttributes(array $attributesData)
 911    {
 912        foreach ($attributesData as $tableName => $skuData) {
 913            $tableData = array();
 914
 915            foreach ($skuData as $sku => $attributes) {
 916                $productId = $this->_newSku[$sku]['entity_id'];
 917
 918                foreach ($attributes as $attributeId => $storeValues) {
 919                    foreach ($storeValues as $storeId => $storeValue) {
 920                        $tableData[] = array(
 921                            'entity_id'      => $productId,
 922                            'entity_type_id' => $this->_entityTypeId,
 923                            'attribute_id'   => $attributeId,
 924                            'store_id'       => $storeId,
 925                            'value'          => $storeValue
 926                        );
 927                    }
 928                }
 929            }
 930            $this->_connection->insertOnDuplicate($tableName, $tableData, array('value'));
 931        }
 932        return $this;
 933    }
 934
 935    /**
 936     * Save product categories.
 937     *
 938     * @param array $categoriesData
 939     * @return Mage_ImportExport_Model_Import_Entity_Product
 940     */
 941    protected function _saveProductCategories(array $categoriesData)
 942    {
 943        static $tableName = null;
 944
 945        if (!$tableName) {
 946            $tableName = Mage::getModel('importexport/import_proxy_product_resource')->getProductCategoryTable();
 947        }
 948        if ($categoriesData) {
 949            $categoriesIn = array();
 950            $delProductId = array();
 951
 952            foreach ($categoriesData as $delSku => $categories) {
 953                $productId      = $this->_newSku[$delSku]['entity_id'];
 954                $delProductId[] = $productId;
 955
 956                foreach (array_keys($categories) as $categoryId) {
 957                    $categoriesIn[] = array('product_id' => $productId, 'category_id' => $categoryId, 'position' => 1);
 958                }
 959            }
 960            if (Mage_ImportExport_Model_Import::BEHAVIOR_APPEND != $this->getBehavior()) {
 961                $this->_connection->delete(
 962                    $tableName,
 963                    $this->_connection->quoteInto('product_id IN (?)', $delProductId)
 964                );
 965            }
 966            if ($categoriesIn) {
 967                $this->_connection->insertOnDuplicate($tableName, $categoriesIn, array('position'));
 968            }
 969        }
 970        return $this;
 971    }
 972
 973    /**
 974     * Update and insert data in entity table.
 975     *
 976     * @param array $entityRowsIn Row for insert
 977     * @param array $entityRowsUp Row for update
 978     * @return Mage_ImportExport_Model_Import_Entity_Product
 979     */
 980    protected function _saveProductEntity(array $entityRowsIn, array $entityRowsUp)
 981    {
 982        static $entityTable = null;
 983
 984        if (!$entityTable) {
 985            $entityTable = Mage::getModel('importexport/import_proxy_product_resource')->getEntityTable();
 986        }
 987        if ($entityRowsUp) {
 988            $this->_connection->insertOnDuplicate(
 989                $entityTable,
 990                $entityRowsUp,
 991                array('updated_at')
 992            );
 993        }
 994        if ($entityRowsIn) {
 995            $this->_connection->insertMultiple($entityTable, $entityRowsIn);
 996
 997            $newProducts = $this->_connection->fetchPairs($this->_connection->select()
 998                ->from($entityTable, array('sku', 'entity_id'))
 999                ->where('sku IN (?)', array_keys($entityRowsIn))
1000            );
1001            foreach ($newProducts as $sku => $newId) { // fill up entity_id for new products
1002                $this->_newSku[$sku]['entity_id'] = $newId;
1003            }
1004        }
1005        return $this;
1006    }
1007
1008    /**
1009     * Gather and save information about product entities.
1010     *
1011     * @return Mage_ImportExport_Model_Import_Entity_Product
1012     */
1013    protected function _saveProducts()
1014    {
1015        /** @var $resource Mage_ImportExport_Model_Import_Proxy_Product_Resource */
1016        $resource       = Mage::getModel('importexport/import_proxy_product_resource');
1017        $priceIsGlobal  = Mage::helper('catalog')->isPriceGlobal();
1018        $strftimeFormat = Varien_Date::convertZendToStrftime(Varien_Date::DATETIME_INTERNAL_FORMAT, true, true);
1019        $productLimit   = null;
1020        $productsQty    = null;
1021
1022        while ($bunch = $this->_dataSourceModel->getNextBunch()) {
1023            $entityRowsIn = array();
1024            $entityRowsUp = array();
1025            $attributes   = array();
1026            $websites     = array();
1027            $categories   = array();
1028            $tierPrices   = array();
1029
1030            foreach ($bunch as $rowNum => $rowData) {
1031                if (!$this->validateRow($rowData, $rowNum)) {
1032                    continue;
1033                }
1034                $rowScope = $this->getRowScope($rowData);
1035
1036                if (self::SCOPE_DEFAULT == $rowScope) {
1037                    $rowSku = $rowData[self::COL_SKU];
1038
1039                    // 1. Entity phase
1040                    if (isset($this->_oldSku[$rowSku])) { // existing row
1041                        $entityRowsUp[] = array(
1042                            'updated_at' => now(),
1043                            'entity_id'  => $this->_oldSku[$rowSku]['entity_id']
1044                        );
1045                    } else { // new row
1046                        if (!$productLimit || $productsQty < $productLimit) {
1047                            $entityRowsIn[$rowSku] = array(
1048                                'entity_type_id'   => $this->_entityTypeId,
1049                                'attribute_set_id' => $this->_newSku[$rowSku]['attr_set_id'],
1050                                'type_id'          => $this->_newSku[$rowSku]['type_id'],
1051                                'sku'              => $rowSku,
1052                                'created_at'       => now(),
1053                                'updated_at'       => now()
1054                            );
1055                            $productsQty++;
1056                        } else {
1057                            $rowSku = null; // sign for child rows to be skipped
1058                            $this->_rowsToSkip[$rowNum] = true;
1059                            continue;
1060                        }
1061                    }
1062                } elseif (null === $rowSku) {
1063                    $this->_rowsToSkip[$rowNum] = true;
1064                    continue; // skip rows when SKU is NULL
1065                } elseif (self::SCOPE_STORE == $rowScope) { // set necessary data from SCOPE_DEFAULT row
1066                    $rowData[self::COL_TYPE]     = $this->_newSku[$rowSku]['type_id'];
1067                    $rowData['attribute_set_id'] = $this->_newSku[$rowSku]['attr_set_id'];
1068                    $rowData[self::COL_ATTR_SET] = $this->_newSku[$rowSku]['attr_set_code'];
1069                }
1070                if (!empty($rowData['_product_websites'])) { // 2. Product-to-Website phase
1071                    $websites[$rowSku][$this->_websiteCodeToId[$rowData['_product_websites']]] = true;
1072                }
1073                if (!empty($rowData[self::COL_CATEGORY])) { // 3. Categories phase
1074                    $categories[$rowSku][$this->_categories[$rowData[self::COL_CATEGORY]]] = true;
1075                }
1076                if (!empty($rowData['_tier_price_website'])) { // 4. Tier prices phase
1077                    $tierPrices[$rowSku][] = array(
1078                        'all_groups'        => $rowData['_tier_price_customer_group'] == self::VALUE_ALL,
1079                        'customer_group_id' => $rowData['_tier_price_customer_group'] == self::VALUE_ALL ?
1080                                               0 : $rowData['_tier_price_customer_group'],
1081                        'qty'               => $rowData['_tier_price_qty'],
1082                        'value'             => $rowData['_tier_price_price'],
1083                        'website_id'        => self::VALUE_ALL == $rowData['_tier_price_website'] || $priceIsGlobal ?
1084                                               0 : $this->_websiteCodeToId[$rowData['_tier_price_website']]
1085                    );
1086                }
1087                // 5. Attributes phase
1088                if (self::SCOPE_NULL == $rowScope) {
1089                    continue; // skip attribute processing for SCOPE_NULL rows
1090                }
1091                $rowStore = self::SCOPE_STORE == $rowScope ? $this->_storeCodeToId[$rowData[self::COL_STORE]] : 0;
1092                $rowData  = $this->_productTypeModels[$rowData[self::COL_TYPE]]->prepareAttributesForSave($rowData);
1093                $product  = Mage::getModel('importexport/import_proxy_product', $rowData);
1094
1095                foreach ($rowData as $attrCode => $attrValue) {
1096                    $attribute = $resource->getAttribute($attrCode);
1097                    $attrId    = $attribute->getId();
1098                    $backModel = $attribute->getBackendModel();
1099                    $attrTable = $attribute->getBackend()->getTable();
1100                    $storeIds  = array(0);
1101
1102                    if ('datetime' == $attribute->getBackendType()) {
1103                        $attrValue = gmstrftime($strftimeFormat, strtotime($attrValue));
1104                    } elseif ($backModel) {
1105                        $attribute->getBackend()->beforeSave($product);
1106                        $attrValue = $product->getData($attribute->getAttributeCode());
1107                    }
1108                    if (self::SCOPE_STORE == $rowScope) {
1109                        if (self::SCOPE_WEBSITE == $attribute->getIsGlobal()) {
1110                            // check website defaults already set
1111                            if (!isset($attributes[$attrTable][$rowSku][$attrId][$rowStore])) {
1112                                $storeIds = $this->_storeIdToWebsiteStoreIds[$rowStore];
1113                            }
1114                        } elseif (self::SCOPE_STORE == $attribute->getIsGlobal()) {
1115                            $storeIds = array($rowStore);
1116                        }
1117                    }
1118                    foreach ($storeIds as $storeId) {
1119                        $attributes[$attrTable][$rowSku][$attrId][$storeId] = $attrValue;
1120                    }
1121                    $attribute->setBackendModel($backModel); // restore 'backend_model' to avoid 'default' setting
1122                }
1123            }
1124            $this->_saveProductEntity($entityRowsIn, $entityRowsUp)
1125                ->_saveProductWebsites($websites)
1126                ->_saveProductCategories($categories)
1127                ->_saveProductTierPrices($tierPrices)
1128                ->_saveProductAttributes($attributes);
1129        }
1130        return $this;
1131    }
1132
1133    /**
1134     * Save product tier prices.
1135     *
1136     * @param array $tierPriceData
1137     * @return Mage_ImportExport_Model_Import_Entity_Product
1138     */
1139    protected function _saveProductTierPrices(array $tierPriceData)
1140    {
1141        static $tableName = null;
1142
1143        if (!$tableName) {
1144            $tableName = Mage::getModel('importexport/import_proxy_product_resource')
1145                    ->getTable('catalog/product_attribute_tier_price');
1146        }
1147        if ($tierPriceData) {
1148            $tierPriceIn  = array();
1149            $delProductId = array();
1150
1151            foreach ($tierPriceData as $delSku => $tierPriceRows) {
1152                $productId      = $this->_newSku[$delSku]['entity_id'];
1153                $delProductId[] = $productId;
1154
1155                foreach ($tierPriceRows as $row) {
1156                    $row['entity_id'] = $productId;
1157                    $tierPriceIn[]  = $row;
1158                }
1159            }
1160            if (Mage_ImportExport_Model_Import::BEHAVIOR_APPEND != $this->getBehavior()) {
1161                $this->_connection->delete(
1162                    $tableName,
1163                    $this->_connection->quoteInto('entity_id IN (?)', $delProductId)
1164                );
1165            }
1166            if ($tierPriceIn) {
1167                $this->_connection->insertOnDuplicate($tableName, $tierPriceIn, array('value'));
1168            }
1169        }
1170        return $this;
1171    }
1172
1173    /**
1174     * Save product websites.
1175     *
1176     * @param array $websiteData
1177     * @return Mage_ImportExport_Model_Import_Entity_Product
1178     */
1179    protected function _saveProductWebsites(array $websiteData)
1180    {
1181        static $tableName = null;
1182
1183        if (!$tableName) {
1184            $tableName = Mage::getModel('importexport/import_proxy_product_resource')->getProductWebsiteTable();
1185        }
1186        if ($websiteData) {
1187            $websitesData = array();
1188            $delProductId = array();
1189
1190            foreach ($websiteData as $delSku => $websites) {
1191                $productId      = $this->_newSku[$delSku]['entity_id'];
1192                $delProductId[] = $productId;
1193
1194                foreach (array_keys($websites) as $websiteId) {
1195                    $websitesData[] = array(
1196           

Large files files are truncated, but you can click here to view the full file