PageRenderTime 210ms CodeModel.GetById 91ms app.highlight 57ms RepoModel.GetById 53ms app.codeStats 1ms

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

https://bitbucket.org/claudiu_marginean/magento-hg-mirror
PHP | 794 lines | 561 code | 65 blank | 168 comment | 79 complexity | dacf53165b45b69700e16909763f68b1 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_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 * Export entity product model
 29 *
 30 * @category    Mage
 31 * @package     Mage_ImportExport
 32 * @author      Magento Core Team <core@magentocommerce.com>
 33 */
 34class Mage_ImportExport_Model_Export_Entity_Product extends Mage_ImportExport_Model_Export_Entity_Abstract
 35{
 36    const CONFIG_KEY_PRODUCT_TYPES = 'global/importexport/export_product_types';
 37
 38    /**
 39     * Value that means all entities (e.g. websites, groups etc.)
 40     */
 41    const VALUE_ALL = 'all';
 42
 43    /**
 44     * Permanent column names.
 45     *
 46     * Names that begins with underscore is not an attribute. This name convention is for
 47     * to avoid interference with same attribute name.
 48     */
 49    const COL_STORE    = '_store';
 50    const COL_ATTR_SET = '_attribute_set';
 51    const COL_TYPE     = '_type';
 52    const COL_CATEGORY = '_category';
 53    const COL_SKU      = 'sku';
 54
 55    /**
 56     * Pairs of attribute set ID-to-name.
 57     *
 58     * @var array
 59     */
 60    protected $_attrSetIdToName = array();
 61
 62    /**
 63     * Categories ID to text-path hash.
 64     *
 65     * @var array
 66     */
 67    protected $_categories = array();
 68
 69    /**
 70     * Attributes with index (not label) value.
 71     *
 72     * @var array
 73     */
 74    protected $_indexValueAttributes = array(
 75        'status',
 76        'tax_class_id',
 77        'visibility',
 78        'enable_googlecheckout',
 79        'gift_message_available',
 80        'custom_design'
 81    );
 82
 83    /**
 84     * Permanent entity columns.
 85     *
 86     * @var array
 87     */
 88    protected $_permanentAttributes = array(self::COL_SKU);
 89
 90    /**
 91     * Array of supported product types as keys with appropriate model object as value.
 92     *
 93     * @var array
 94     */
 95    protected $_productTypeModels = array();
 96
 97    /**
 98     * Array of pairs store ID to its code.
 99     *
100     * @var array
101     */
102    protected $_storeIdToCode = array();
103
104    /**
105     * Website ID-to-code.
106     *
107     * @var array
108     */
109    protected $_websiteIdToCode = array();
110
111    /**
112     * Constructor.
113     *
114     * @return void
115     */
116    public function __construct()
117    {
118        parent::__construct();
119
120        $this->_initTypeModels()
121                ->_initAttrValues()
122                ->_initStores()
123                ->_initAttributeSets()
124                ->_initWebsites()
125                ->_initCategories();
126    }
127
128    /**
129     * Initialize attribute sets code-to-id pairs.
130     *
131     * @return Mage_ImportExport_Model_Export_Entity_Product
132     */
133    protected function _initAttributeSets()
134    {
135        $productTypeId = Mage::getModel('catalog/product')->getResource()->getTypeId();
136        foreach (Mage::getResourceModel('eav/entity_attribute_set_collection')
137                ->setEntityTypeFilter($productTypeId) as $attributeSet) {
138            $this->_attrSetIdToName[$attributeSet->getId()] = $attributeSet->getAttributeSetName();
139        }
140        return $this;
141    }
142
143    /**
144     * Initialize categories ID to text-path hash.
145     *
146     * @return Mage_ImportExport_Model_Export_Entity_Product
147     */
148    protected function _initCategories()
149    {
150        $collection = Mage::getResourceModel('catalog/category_collection')->addNameToResult();
151        /* @var $collection Mage_Catalog_Model_Resource_Eav_Mysql4_Category_Collection */
152        foreach ($collection as $category) {
153            $structure = preg_split('#/+#', $category->getPath());
154            $pathSize  = count($structure);
155            if ($pathSize > 2) {
156                $path = array();
157                for ($i = 2; $i < $pathSize; $i++) {
158                    $path[] = $collection->getItemById($structure[$i])->getName();
159                }
160                $this->_categories[$category->getId()] = implode('/', $path);
161            }
162        }
163        return $this;
164    }
165
166    /**
167     * Initialize product type models.
168     *
169     * @throws Exception
170     * @return Mage_ImportExport_Model_Export_Entity_Product
171     */
172    protected function _initTypeModels()
173    {
174        $config = Mage::getConfig()->getNode(self::CONFIG_KEY_PRODUCT_TYPES)->asCanonicalArray();
175        foreach ($config as $type => $typeModel) {
176            if (!($model = Mage::getModel($typeModel, array($this, $type)))) {
177                Mage::throwException("Entity type model '{$typeModel}' is not found");
178            }
179            if (! $model instanceof Mage_ImportExport_Model_Export_Entity_Product_Type_Abstract) {
180                Mage::throwException(
181                    Mage::helper('importexport')->__('Entity type model must be an instance of Mage_ImportExport_Model_Export_Entity_Product_Type_Abstract')
182                );
183            }
184            if ($model->isSuitable()) {
185                $this->_productTypeModels[$type] = $model;
186                $this->_disabledAttrs            = array_merge($this->_disabledAttrs, $model->getDisabledAttrs());
187                $this->_indexValueAttributes     = array_merge(
188                    $this->_indexValueAttributes, $model->getIndexValueAttributes()
189                );
190            }
191        }
192        if (!$this->_productTypeModels) {
193            Mage::throwException(Mage::helper('importexport')->__('There are no product types available for export'));
194        }
195        $this->_disabledAttrs = array_unique($this->_disabledAttrs);
196
197        return $this;
198    }
199
200    /**
201     * Initialize website values.
202     *
203     * @return Mage_ImportExport_Model_Export_Entity_Product
204     */
205    protected function _initWebsites()
206    {
207        /** @var $website Mage_Core_Model_Website */
208        foreach (Mage::app()->getWebsites() as $website) {
209            $this->_websiteIdToCode[$website->getId()] = $website->getCode();
210        }
211        return $this;
212    }
213
214    /**
215     * Prepare products tier prices
216     *
217     * @param  array $productIds
218     * @return array
219     */
220    protected function _prepareTierPrices(array $productIds)
221    {
222        $resource = Mage::getSingleton('core/resource');
223        $select = $this->_connection->select()
224            ->from($resource->getTableName('catalog/product_attribute_tier_price'))
225            ->where('entity_id IN(?)', $productIds);
226
227        $rowTierPrices = array();
228        $stmt = $this->_connection->query($select);
229        while ($tierRow = $stmt->fetch()) {
230            $rowTierPrices[$tierRow['entity_id']][] = array(
231                '_tier_price_customer_group' => $tierRow['all_groups']
232                                                ? self::VALUE_ALL : $tierRow['customer_group_id'],
233                '_tier_price_website'        => 0 == $tierRow['website_id']
234                                                ? self::VALUE_ALL
235                                                : $this->_websiteIdToCode[$tierRow['website_id']],
236                '_tier_price_qty'            => $tierRow['qty'],
237                '_tier_price_price'          => $tierRow['value']
238            );
239        }
240
241        return $rowTierPrices;
242    }
243
244    /**
245     * Prepare catalog inventory
246     *
247     * @param  array $productIds
248     * @return array
249     */
250    protected function _prepareCatalogInventory(array $productIds)
251    {
252        $select = $this->_connection->select()
253            ->from(Mage::getResourceModel('cataloginventory/stock_item')->getMainTable())
254            ->where('product_id IN (?)', $productIds);
255
256        $stmt = $this->_connection->query($select);
257        $stockItemRows = array();
258        while ($stockItemRow = $stmt->fetch()) {
259            $productId = $stockItemRow['product_id'];
260            unset(
261                $stockItemRow['item_id'], $stockItemRow['product_id'], $stockItemRow['low_stock_date'],
262                $stockItemRow['stock_id'], $stockItemRow['stock_status_changed_automatically']
263            );
264            $stockItemRows[$productId] = $stockItemRow;
265        }
266        return $stockItemRows;
267    }
268
269    /**
270     * Prepare product links
271     *
272     * @param  array $productIds
273     * @return array
274     */
275    protected function _prepareLinks(array $productIds)
276    {
277        $resource = Mage::getSingleton('core/resource');
278        $select = $this->_connection->select()
279            ->from(
280                array('cpl' => $resource->getTableName('catalog/product_link')),
281                array(
282                    'cpl.product_id', 'cpe.sku', 'cpl.link_type_id',
283                    'position' => 'cplai.value', 'default_qty' => 'cplad.value'
284                )
285            )
286            ->joinLeft(
287                array('cpe' => $resource->getTableName('catalog/product')),
288                '(cpe.entity_id = cpl.linked_product_id)',
289                array()
290            )
291            ->joinLeft(
292                array('cpla' => $resource->getTableName('catalog/product_link_attribute')),
293                '(cpla.link_type_id = cpl.link_type_id AND cpla.product_link_attribute_code = "position")',
294                array()
295            )
296            ->joinLeft(
297                array('cplaq' => $resource->getTableName('catalog/product_link_attribute')),
298                '(cplaq.link_type_id = cpl.link_type_id AND cplaq.product_link_attribute_code = "qty")',
299                array()
300            )
301            ->joinLeft(
302                array('cplai' => $resource->getTableName('catalog/product_link_attribute_int')),
303                '(cplai.link_id = cpl.link_id AND cplai.product_link_attribute_id = cpla.product_link_attribute_id)',
304                array()
305            )
306            ->joinLeft(
307                array('cplad' => $resource->getTableName('catalog/product_link_attribute_decimal')),
308                '(cplad.link_id = cpl.link_id AND cplad.product_link_attribute_id = cplaq.product_link_attribute_id)',
309                array()
310            )
311            ->where('cpl.link_type_id IN (?)', array(
312                Mage_Catalog_Model_Product_Link::LINK_TYPE_RELATED,
313                Mage_Catalog_Model_Product_Link::LINK_TYPE_UPSELL,
314                Mage_Catalog_Model_Product_Link::LINK_TYPE_CROSSSELL,
315                Mage_Catalog_Model_Product_Link::LINK_TYPE_GROUPED
316            ))
317            ->where('cpl.product_id IN (?)', $productIds);
318
319        $stmt = $this->_connection->query($select);
320        $linksRows = array();
321        while ($linksRow = $stmt->fetch()) {
322            $linksRows[$linksRow['product_id']][$linksRow['link_type_id']][] = array(
323                'sku'         => $linksRow['sku'],
324                'position'    => $linksRow['position'],
325                'default_qty' => $linksRow['default_qty']
326            );
327        }
328
329        return $linksRows;
330    }
331
332    /**
333     * Prepare configurable product data
334     *
335     * @param  array $productIds
336     * @return array
337     */
338    protected function _prepareConfigurableProductData(array $productIds)
339    {
340        $resource = Mage::getSingleton('core/resource');
341        $select = $this->_connection->select()
342            ->from(
343                array('cpsl' => $resource->getTableName('catalog/product_super_link')),
344                array('cpsl.parent_id', 'cpe.sku')
345            )
346            ->joinLeft(
347                array('cpe' => $resource->getTableName('catalog/product')),
348                '(cpe.entity_id = cpsl.product_id)',
349                array()
350            )
351            ->where('parent_id IN (?)', $productIds);
352        $stmt = $this->_connection->query($select);
353        $configurableData = array();
354        while ($cfgLinkRow = $stmt->fetch()) {
355            $configurableData[$cfgLinkRow['parent_id']][] = array('_super_products_sku' => $cfgLinkRow['sku']);
356        }
357
358        return $configurableData;
359    }
360
361    /**
362     * Prepare configurable product price
363     *
364     * @param  array $productIds
365     * @return array
366     */
367    protected function _prepareConfigurableProductPrice(array $productIds)
368    {
369        $resource = Mage::getSingleton('core/resource');
370        $select = $this->_connection->select()
371            ->from(
372                array('cpsa' => $resource->getTableName('catalog/product_super_attribute')),
373                array(
374                    'cpsa.product_id', 'ea.attribute_code', 'eaov.value', 'cpsap.pricing_value', 'cpsap.is_percent'
375                )
376            )
377            ->joinLeft(
378                array('cpsap' => $resource->getTableName('catalog/product_super_attribute_pricing')),
379                '(cpsap.product_super_attribute_id = cpsa.product_super_attribute_id)',
380                array()
381            )
382            ->joinLeft(
383                array('ea' => $resource->getTableName('eav/attribute')),
384                '(ea.attribute_id = cpsa.attribute_id)',
385                array()
386            )
387            ->joinLeft(
388                array('eaov' => $resource->getTableName('eav/attribute_option_value')),
389                '(eaov.option_id = cpsap.value_index AND store_id = 0)',
390                array()
391            )
392            ->where('cpsa.product_id IN (?)', $productIds);
393        $configurablePrice = array();
394        $stmt = $this->_connection->query($select);
395        while ($priceRow = $stmt->fetch()) {
396            $configurablePrice[$priceRow['product_id']][] = array(
397                '_super_attribute_code'       => $priceRow['attribute_code'],
398                '_super_attribute_option'     => $priceRow['value'],
399                '_super_attribute_price_corr' => $priceRow['pricing_value'] . ($priceRow['is_percent'] ? '%' : '')
400            );
401        }
402        return $configurablePrice;
403    }
404
405    /**
406     * Export process.
407     *
408     * @return string
409     */
410    public function export()
411    {
412        /** @var $collection Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Collection */
413        $validAttrCodes  = $this->_getExportAttrCodes();
414        $writer          = $this->getWriter();
415        $resource        = Mage::getSingleton('core/resource');
416        $dataRows        = array();
417        $rowCategories   = array();
418        $rowWebsites     = array();
419        $rowTierPrices   = array();
420        $stockItemRows   = array();
421        $linksRows       = array();
422        $gfAmountFields  = array();
423        $defaultStoreId  = Mage_Catalog_Model_Abstract::DEFAULT_STORE_ID;
424        $collection = $this->_prepareEntityCollection(Mage::getResourceModel('catalog/product_collection'));
425
426        // prepare multi-store values and system columns values
427        foreach ($this->_storeIdToCode as $storeId => &$storeCode) { // go through all stores
428            $collection->setStoreId($storeId)
429                ->load();
430
431            if ($defaultStoreId == $storeId) {
432                $collection->addCategoryIds()->addWebsiteNamesToResult();
433
434                // tier price data getting only once
435                $rowTierPrices = $this->_prepareTierPrices($collection->getAllIds());
436            }
437            foreach ($collection as $itemId => $item) { // go through all products
438                $rowIsEmpty = true; // row is empty by default
439
440                foreach ($validAttrCodes as &$attrCode) { // go through all valid attribute codes
441                    $attrValue = $item->getData($attrCode);
442
443                    if (!empty($this->_attributeValues[$attrCode])) {
444                        if (isset($this->_attributeValues[$attrCode][$attrValue])) {
445                            $attrValue = $this->_attributeValues[$attrCode][$attrValue];
446                        } else {
447                            $attrValue = null;
448                        }
449                    }
450                    // do not save value same as default or not existent
451                    if ($storeId != $defaultStoreId
452                        && isset($dataRows[$itemId][$defaultStoreId][$attrCode])
453                        && $dataRows[$itemId][$defaultStoreId][$attrCode] == $attrValue
454                    ) {
455                        $attrValue = null;
456                    }
457                    if (is_scalar($attrValue)) {
458                        $dataRows[$itemId][$storeId][$attrCode] = $attrValue;
459                        $rowIsEmpty = false; // mark row as not empty
460                    }
461                }
462                if ($rowIsEmpty) { // remove empty rows
463                    unset($dataRows[$itemId][$storeId]);
464                } else {
465                    $attrSetId = $item->getAttributeSetId();
466                    $dataRows[$itemId][$storeId][self::COL_STORE]    = $storeCode;
467                    $dataRows[$itemId][$storeId][self::COL_ATTR_SET] = $this->_attrSetIdToName[$attrSetId];
468                    $dataRows[$itemId][$storeId][self::COL_TYPE]     = $item->getTypeId();
469
470                    if ($defaultStoreId == $storeId) {
471                        $rowWebsites[$itemId]   = $item->getWebsites();
472                        $rowCategories[$itemId] = $item->getCategoryIds();
473                    }
474                }
475                $item = null;
476            }
477            $collection->clear();
478        }
479
480        // remove root categories
481        foreach ($rowCategories as $productId => &$categories) {
482            $categories = array_intersect($categories, array_keys($this->_categories));
483        }
484
485        // prepare catalog inventory information
486        $productIds = array_keys($dataRows);
487        $stockItemRows = $this->_prepareCatalogInventory($productIds);
488
489        // prepare links information
490        $this->_prepareLinks($productIds);
491        $linkIdColPrefix = array(
492            Mage_Catalog_Model_Product_Link::LINK_TYPE_RELATED   => '_links_related_',
493            Mage_Catalog_Model_Product_Link::LINK_TYPE_UPSELL    => '_links_upsell_',
494            Mage_Catalog_Model_Product_Link::LINK_TYPE_CROSSSELL => '_links_crosssell_',
495            Mage_Catalog_Model_Product_Link::LINK_TYPE_GROUPED   => '_associated_'
496        );
497
498        // prepare configurable products data
499        $configurableData  = $this->_prepareConfigurableProductData($productIds);
500        $configurablePrice = array();
501        if ($configurableData) {
502            $configurablePrice = $this->_prepareConfigurableProductPrice($productIds);
503            foreach ($configurableData as $productId => &$rows) {
504                if (isset($configurablePrice[$productId])) {
505                    $largest = max(count($rows), count($configurablePrice[$productId]));
506
507                    for ($i = 0; $i < $largest; $i++) {
508                        if (!isset($configurableData[$productId][$i])) {
509                            $configurableData[$productId][$i] = array();
510                        }
511                        if (isset($configurablePrice[$productId][$i])) {
512                            $configurableData[$productId][$i] = array_merge(
513                                $configurableData[$productId][$i],
514                                $configurablePrice[$productId][$i]
515                            );
516                        }
517                    }
518                }
519            }
520            unset($configurablePrice);
521        }
522
523        // prepare custom options information
524        $customOptionsData    = array();
525        $customOptionsDataPre = array();
526        $customOptCols        = array(
527            '_custom_option_store', '_custom_option_type', '_custom_option_title', '_custom_option_is_required',
528            '_custom_option_price', '_custom_option_sku', '_custom_option_max_characters',
529            '_custom_option_sort_order', '_custom_option_row_title', '_custom_option_row_price',
530            '_custom_option_row_sku', '_custom_option_row_sort'
531        );
532
533        foreach ($this->_storeIdToCode as $storeId => &$storeCode) {
534            $options = Mage::getResourceModel('catalog/product_option_collection')
535                ->reset()
536                ->addTitleToResult($storeId)
537                ->addPriceToResult($storeId)
538                ->addProductToFilter($productIds)
539                ->addValuesToResult($storeId);
540
541            foreach ($options as $option) {
542                $row = array();
543                $productId = $option['product_id'];
544                $optionId  = $option['option_id'];
545                $customOptions = isset($customOptionsDataPre[$productId][$optionId])
546                               ? $customOptionsDataPre[$productId][$optionId]
547                               : array();
548
549                if ($defaultStoreId == $storeId) {
550                    $row['_custom_option_type']           = $option['type'];
551                    $row['_custom_option_title']          = $option['title'];
552                    $row['_custom_option_is_required']    = $option['is_require'];
553                    $row['_custom_option_price'] = $option['price'] . ($option['price_type'] == 'percent' ? '%' : '');
554                    $row['_custom_option_sku']            = $option['sku'];
555                    $row['_custom_option_max_characters'] = $option['max_characters'];
556                    $row['_custom_option_sort_order']     = $option['sort_order'];
557
558                    // remember default title for later comparisons
559                    $defaultTitles[$option['option_id']] = $option['title'];
560                } elseif ($option['title'] != $customOptions[0]['_custom_option_title']) {
561                    $row['_custom_option_title'] = $option['title'];
562                }
563                if ($values = $option->getValues()) {
564                    $firstValue = array_shift($values);
565                    $priceType  = $firstValue['price_type'] == 'percent' ? '%' : '';
566
567                    if ($defaultStoreId == $storeId) {
568                        $row['_custom_option_row_title'] = $firstValue['title'];
569                        $row['_custom_option_row_price'] = $firstValue['price'] . $priceType;
570                        $row['_custom_option_row_sku']   = $firstValue['sku'];
571                        $row['_custom_option_row_sort']  = $firstValue['sort_order'];
572
573                        $defaultValueTitles[$firstValue['option_type_id']] = $firstValue['title'];
574                    } elseif ($firstValue['title'] != $customOptions[0]['_custom_option_row_title']) {
575                        $row['_custom_option_row_title'] = $firstValue['title'];
576                    }
577                }
578                if ($row) {
579                    if ($defaultStoreId != $storeId) {
580                        $row['_custom_option_store'] = $this->_storeIdToCode[$storeId];
581                    }
582                    $customOptionsDataPre[$productId][$optionId][] = $row;
583                }
584                foreach ($values as $value) {
585                    $row = array();
586                    $valuePriceType = $value['price_type'] == 'percent' ? '%' : '';
587
588                    if ($defaultStoreId == $storeId) {
589                        $row['_custom_option_row_title'] = $value['title'];
590                        $row['_custom_option_row_price'] = $value['price'] . $valuePriceType;
591                        $row['_custom_option_row_sku']   = $value['sku'];
592                        $row['_custom_option_row_sort']  = $value['sort_order'];
593                    } elseif ($value['title'] != $customOptions[0]['_custom_option_row_title']) {
594                        $row['_custom_option_row_title'] = $value['title'];
595                    }
596                    if ($row) {
597                        if ($defaultStoreId != $storeId) {
598                            $row['_custom_option_store'] = $this->_storeIdToCode[$storeId];
599                        }
600                        $customOptionsDataPre[$option['product_id']][$option['option_id']][] = $row;
601                    }
602                }
603                $option = null;
604            }
605            $options = null;
606        }
607        foreach ($customOptionsDataPre as $productId => &$optionsData) {
608            $customOptionsData[$productId] = array();
609
610            foreach ($optionsData as $optionId => &$optionRows) {
611                $customOptionsData[$productId] = array_merge($customOptionsData[$productId], $optionRows);
612            }
613            unset($optionRows, $optionsData);
614        }
615        unset($customOptionsDataPre);
616
617        // create export file
618        $headerCols = array_merge(
619            array(
620                self::COL_SKU, self::COL_STORE, self::COL_ATTR_SET,
621                self::COL_TYPE, self::COL_CATEGORY, '_product_websites'
622            ),
623            $validAttrCodes,
624            reset($stockItemRows) ? array_keys(end($stockItemRows)) : array(),
625            $gfAmountFields,
626            array(
627                '_links_related_sku', '_links_related_position', '_links_crosssell_sku',
628                '_links_crosssell_position', '_links_upsell_sku', '_links_upsell_position',
629                '_associated_sku', '_associated_default_qty', '_associated_position'
630            ),
631            array('_tier_price_website', '_tier_price_customer_group', '_tier_price_qty', '_tier_price_price')
632        );
633
634        // have we merge custom options columns
635        if ($customOptionsData) {
636            $headerCols = array_merge($headerCols, $customOptCols);
637        }
638
639        // have we merge configurable products data
640        if ($configurableData) {
641            $headerCols = array_merge($headerCols, array(
642                '_super_products_sku', '_super_attribute_code',
643                '_super_attribute_option', '_super_attribute_price_corr'
644            ));
645        }
646
647        $writer->setHeaderCols($headerCols);
648
649        foreach ($dataRows as $productId => &$productData) {
650            foreach ($productData as $storeId => &$dataRow) {
651                if ($defaultStoreId != $storeId) {
652                    $dataRow[self::COL_SKU]      = null;
653                    $dataRow[self::COL_ATTR_SET] = null;
654                    $dataRow[self::COL_TYPE]     = null;
655                } else {
656                    $dataRow[self::COL_STORE] = null;
657                    $dataRow += $stockItemRows[$productId];
658                }
659                if ($rowCategories[$productId]) {
660                    $dataRow[self::COL_CATEGORY] = $this->_categories[array_shift($rowCategories[$productId])];
661                }
662                if ($rowWebsites[$productId]) {
663                    $dataRow['_product_websites'] = $this->_websiteIdToCode[array_shift($rowWebsites[$productId])];
664                }
665                if (!empty($rowTierPrices[$productId])) {
666                    $dataRow = array_merge($dataRow, array_shift($rowTierPrices[$productId]));
667                }
668                foreach ($linkIdColPrefix as $linkId => &$colPrefix) {
669                    if (!empty($linksRows[$productId][$linkId])) {
670                        $linkData = array_shift($linksRows[$productId][$linkId]);
671                        $dataRow[$colPrefix . 'position'] = $linkData['position'];
672                        $dataRow[$colPrefix . 'sku'] = $linkData['sku'];
673
674                        if (null !== $linkData['default_qty']) {
675                            $dataRow[$colPrefix . 'default_qty'] = $linkData['default_qty'];
676                        }
677                    }
678                }
679                if (!empty($customOptionsData[$productId])) {
680                    $dataRow = array_merge($dataRow, array_shift($customOptionsData[$productId]));
681                }
682                if (!empty($configurableData[$productId])) {
683                    $dataRow = array_merge($dataRow, array_shift($configurableData[$productId]));
684                }
685
686                $writer->writeRow($dataRow);
687            }
688            // calculate largest links block
689            $largestLinks = 0;
690
691            if (isset($linksRows[$productId])) {
692                foreach ($linksRows[$productId] as &$linkData) {
693                    $largestLinks = max($largestLinks, count($linkData));
694                }
695            }
696            $additionalRowsCount = max(
697                count($rowCategories[$productId]),
698                count($rowWebsites[$productId]),
699                $largestLinks
700            );
701            if (!empty($rowTierPrices[$productId])) {
702                $additionalRowsCount = max($additionalRowsCount, count($rowTierPrices[$productId]));
703            }
704            if (!empty($customOptionsData[$productId])) {
705                $additionalRowsCount = max($additionalRowsCount, count($customOptionsData[$productId]));
706            }
707            if (!empty($configurableData[$productId])) {
708                $additionalRowsCount = max($additionalRowsCount, count($configurableData[$productId]));
709            }
710
711            if ($additionalRowsCount) {
712                for ($i = 0; $i < $additionalRowsCount; $i++) {
713                    $dataRow = array();
714
715                    if ($rowCategories[$productId]) {
716                        $dataRow[self::COL_CATEGORY] = $this->_categories[array_shift($rowCategories[$productId])];
717                    }
718                    if ($rowWebsites[$productId]) {
719                        $dataRow['_product_websites'] = $this->_websiteIdToCode[array_shift($rowWebsites[$productId])];
720                    }
721                    if (!empty($rowTierPrices[$productId])) {
722                        $dataRow = array_merge($dataRow, array_shift($rowTierPrices[$productId]));
723                    }
724                    foreach ($linkIdColPrefix as $linkId => &$colPrefix) {
725                        if (!empty($linksRows[$productId][$linkId])) {
726                            $linkData = array_shift($linksRows[$productId][$linkId]);
727                            $dataRow[$colPrefix . 'position'] = $linkData['position'];
728                            $dataRow[$colPrefix . 'sku'] = $linkData['sku'];
729
730                            if (null !== $linkData['default_qty']) {
731                                $dataRow[$colPrefix . 'default_qty'] = $linkData['default_qty'];
732                            }
733                        }
734                    }
735                    if (!empty($customOptionsData[$productId])) {
736                        $dataRow = array_merge($dataRow, array_shift($customOptionsData[$productId]));
737                    }
738                    if (!empty($configurableData[$productId])) {
739                        $dataRow = array_merge($dataRow, array_shift($configurableData[$productId]));
740                    }
741                    $writer->writeRow($dataRow);
742                }
743            }
744        }
745        return $writer->getContents();
746    }
747
748    /**
749     * Clean up already loaded attribute collection.
750     *
751     * @param Mage_Eav_Model_Mysql4_Entity_Attribute_Collection $collection
752     * @return Mage_Eav_Model_Mysql4_Entity_Attribute_Collection
753     */
754    public function filterAttributeCollection(Mage_Eav_Model_Mysql4_Entity_Attribute_Collection $collection)
755    {
756        $validTypes = array_keys($this->_productTypeModels);
757
758        foreach (parent::filterAttributeCollection($collection) as $attribute) {
759            $attrApplyTo = $attribute->getApplyTo();
760            $attrApplyTo = $attrApplyTo ? array_intersect($attrApplyTo, $validTypes) : $validTypes;
761
762            if ($attrApplyTo) {
763                foreach ($attrApplyTo as $productType) { // override attributes by its product type model
764                    if ($this->_productTypeModels[$productType]->overrideAttribute($attribute)) {
765                        break;
766                    }
767                }
768            } else { // remove attributes of not-supported product types
769                $collection->removeItemByKey($attribute->getId());
770            }
771        }
772        return $collection;
773    }
774
775    /**
776     * Entity attributes collection getter.
777     *
778     * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Attribute_Collection
779     */
780    public function getAttributeCollection()
781    {
782        return Mage::getResourceModel('catalog/product_attribute_collection');
783    }
784
785    /**
786     * EAV entity type code getter.
787     *
788     * @return string
789     */
790    public function getEntityTypeCode()
791    {
792        return 'catalog_product';
793    }
794}