Magento 2 Documentation  2.3
Documentation for Magento 2 CMS v2.3 (December 2018)
Product.php
Go to the documentation of this file.
1 <?php
7 
11 use \Magento\Store\Model\Store;
12 use \Magento\CatalogImportExport\Model\Import\Product as ImportProduct;
14 
25 class Product extends \Magento\ImportExport\Model\Export\Entity\AbstractEntity
26 {
32  protected $_bannedAttributes = ['media_gallery'];
33 
37  const VALUE_ALL = 'all';
38 
45  const COL_STORE = '_store';
46 
47  const COL_ATTR_SET = '_attribute_set';
48 
49  const COL_TYPE = '_type';
50 
51  const COL_PRODUCT_WEBSITES = '_product_websites';
52 
53  const COL_CATEGORY = '_category';
54 
55  const COL_ROOT_CATEGORY = '_root_category';
56 
57  const COL_SKU = 'sku';
58 
59  const COL_VISIBILITY = 'visibility';
60 
61  const COL_MEDIA_IMAGE = '_media_image';
62 
63  const COL_ADDITIONAL_ATTRIBUTES = 'additional_attributes';
64 
70  protected $_attrSetIdToName = [];
71 
77  protected $_categories = [];
78 
84  protected $_rootCategories = [];
85 
91  protected $_indexValueAttributes = [
92  'status',
93  ];
94 
99 
106 
112  protected $_productTypeModels = [];
113 
119  protected $_storeIdToCode = [];
120 
126  protected $_websiteIdToCode = [];
127 
133  protected $_attributeTypes = [];
134 
140  private $userDefinedAttributes = [];
141 
148 
155 
161  protected $_itemsPerPage = null;
162 
169  protected $_headerColumns = [];
170 
174  protected $_exportConfig;
175 
179  protected $_logger;
180 
184  protected $_productFactory;
185 
190 
195 
199  protected $_resourceModel;
200 
204  protected $_itemFactory;
205 
210 
215 
219  protected $_typeFactory;
220 
227 
231  protected $rowCustomizer;
232 
238  protected $_fieldsMap = [
239  'image' => 'base_image',
240  'image_label' => "base_image_label",
241  'thumbnail' => 'thumbnail_image',
242  'thumbnail_label' => 'thumbnail_image_label',
243  self::COL_MEDIA_IMAGE => 'additional_images',
244  '_media_image_label' => 'additional_image_labels',
245  self::COL_STORE => 'store_view_code',
246  self::COL_ATTR_SET => 'attribute_set_code',
247  self::COL_TYPE => 'product_type',
248  self::COL_CATEGORY => 'categories',
249  self::COL_PRODUCT_WEBSITES => 'product_websites',
250  'status' => 'product_online',
251  'news_from_date' => 'new_from_date',
252  'news_to_date' => 'new_to_date',
253  'options_container' => 'display_product_options_in',
254  'minimal_price' => 'map_price',
255  'msrp' => 'msrp_price',
256  'msrp_enabled' => 'map_enabled',
257  'special_from_date' => 'special_price_from_date',
258  'special_to_date' => 'special_price_to_date',
259  'min_qty' => 'out_of_stock_qty',
260  'backorders' => 'allow_backorders',
261  'min_sale_qty' => 'min_cart_qty',
262  'max_sale_qty' => 'max_cart_qty',
263  'notify_stock_qty' => 'notify_on_stock_below',
264  'meta_keyword' => 'meta_keywords',
265  'tax_class_id' => 'tax_class_name',
266  ];
267 
274  protected $dateAttrCodes = [
275  'special_from_date',
276  'special_to_date',
277  'news_from_date',
278  'news_to_date',
279  'custom_design_from',
280  'custom_design_to'
281  ];
282 
288  protected $_exportMainAttrCodes = [
290  'name',
291  'description',
292  'short_description',
293  'weight',
294  'product_online',
295  'tax_class_name',
296  'visibility',
297  'price',
298  'special_price',
299  'special_price_from_date',
300  'special_price_to_date',
301  'url_key',
302  'meta_title',
303  'meta_keywords',
304  'meta_description',
305  'base_image',
306  'base_image_label',
307  'small_image',
308  'small_image_label',
309  'thumbnail_image',
310  'thumbnail_image_label',
311  'swatch_image',
312  'swatch_image_label',
313  'created_at',
314  'updated_at',
315  'new_from_date',
316  'new_to_date',
317  'display_product_options_in',
318  'map_price',
319  'msrp_price',
320  'map_enabled',
321  'special_price_from_date',
322  'special_price_to_date',
323  'gift_message_available',
324  'custom_design',
325  'custom_design_from',
326  'custom_design_to',
327  'custom_layout_update',
328  'page_layout',
329  'product_options_container',
330  'msrp_price',
331  'msrp_display_actual_price_type',
332  'map_enabled',
333  'country_of_manufacture',
334  'map_price',
335  'display_product_options_in',
336  ];
337 
342  protected $metadataPool;
343 
349  private $productEntityLinkField;
350 
371  public function __construct(
372  \Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate,
373  \Magento\Eav\Model\Config $config,
376  \Psr\Log\LoggerInterface $logger,
377  \Magento\Catalog\Model\ResourceModel\Product\CollectionFactory $collectionFactory,
378  \Magento\ImportExport\Model\Export\ConfigInterface $exportConfig,
379  \Magento\Catalog\Model\ResourceModel\ProductFactory $productFactory,
380  \Magento\Eav\Model\ResourceModel\Entity\Attribute\Set\CollectionFactory $attrSetColFactory,
381  \Magento\Catalog\Model\ResourceModel\Category\CollectionFactory $categoryColFactory,
382  \Magento\CatalogInventory\Model\ResourceModel\Stock\ItemFactory $itemFactory,
383  \Magento\Catalog\Model\ResourceModel\Product\Option\CollectionFactory $optionColFactory,
384  \Magento\Catalog\Model\ResourceModel\Product\Attribute\CollectionFactory $attributeColFactory,
385  \Magento\CatalogImportExport\Model\Export\Product\Type\Factory $_typeFactory,
386  \Magento\Catalog\Model\Product\LinkTypeProvider $linkTypeProvider,
387  \Magento\CatalogImportExport\Model\Export\RowCustomizerInterface $rowCustomizer,
388  array $dateAttrCodes = []
389  ) {
390  $this->_entityCollectionFactory = $collectionFactory;
391  $this->_exportConfig = $exportConfig;
392  $this->_logger = $logger;
393  $this->_productFactory = $productFactory;
394  $this->_attrSetColFactory = $attrSetColFactory;
395  $this->_categoryColFactory = $categoryColFactory;
396  $this->_resourceModel = $resource;
397  $this->_itemFactory = $itemFactory;
398  $this->_optionColFactory = $optionColFactory;
399  $this->_attributeColFactory = $attributeColFactory;
400  $this->_typeFactory = $_typeFactory;
401  $this->_linkTypeProvider = $linkTypeProvider;
402  $this->rowCustomizer = $rowCustomizer;
403  $this->dateAttrCodes = array_merge($this->dateAttrCodes, $dateAttrCodes);
404 
405  parent::__construct($localeDate, $config, $resource, $storeManager);
406 
407  $this->initTypeModels()
408  ->initAttributes()
409  ->_initStores()
410  ->initAttributeSets()
411  ->initWebsites()
412  ->initCategories();
413  }
414 
420  protected function initAttributeSets()
421  {
422  $productTypeId = $this->_productFactory->create()->getTypeId();
423  foreach ($this->_attrSetColFactory->create()->setEntityTypeFilter($productTypeId) as $attributeSet) {
424  $this->_attrSetIdToName[$attributeSet->getId()] = $attributeSet->getAttributeSetName();
425  }
426  return $this;
427  }
428 
434  protected function initCategories()
435  {
436  $collection = $this->_categoryColFactory->create()->addNameToResult();
437  /* @var $collection \Magento\Catalog\Model\ResourceModel\Category\Collection */
438  foreach ($collection as $category) {
439  $structure = preg_split('#/+#', $category->getPath());
440  $pathSize = count($structure);
441  if ($pathSize > 1) {
442  $path = [];
443  for ($i = 1; $i < $pathSize; $i++) {
444  $name = $collection->getItemById($structure[$i])->getName();
445  $path[] = $this->quoteCategoryDelimiter($name);
446  }
447  $this->_rootCategories[$category->getId()] = array_shift($path);
448  if ($pathSize > 2) {
449  $this->_categories[$category->getId()] = implode(CategoryProcessor::DELIMITER_CATEGORY, $path);
450  }
451  }
452  }
453  return $this;
454  }
455 
462  protected function initTypeModels()
463  {
464  $productTypes = $this->_exportConfig->getEntityTypes($this->getEntityTypeCode());
465  foreach ($productTypes as $productTypeName => $productTypeConfig) {
466  if (!($model = $this->_typeFactory->create($productTypeConfig['model']))) {
467  throw new \Magento\Framework\Exception\LocalizedException(
468  __('Entity type model \'%1\' is not found', $productTypeConfig['model'])
469  );
470  }
471  if (!$model instanceof \Magento\CatalogImportExport\Model\Export\Product\Type\AbstractType) {
472  throw new \Magento\Framework\Exception\LocalizedException(
473  __(
474  'Entity type model must be an instance of'
475  . ' \Magento\CatalogImportExport\Model\Export\Product\Type\AbstractType'
476  )
477  );
478  }
479  if ($model->isSuitable()) {
480  $this->_productTypeModels[$productTypeName] = $model;
481  $this->_disabledAttrs = array_merge($this->_disabledAttrs, $model->getDisabledAttrs());
482  $this->_indexValueAttributes = array_merge(
483  $this->_indexValueAttributes,
484  $model->getIndexValueAttributes()
485  );
486  }
487  }
488  if (!$this->_productTypeModels) {
489  throw new \Magento\Framework\Exception\LocalizedException(
490  __('There are no product types available for export.')
491  );
492  }
493  $this->_disabledAttrs = array_unique($this->_disabledAttrs);
494 
495  return $this;
496  }
497 
503  protected function initWebsites()
504  {
506  foreach ($this->_storeManager->getWebsites() as $website) {
507  $this->_websiteIdToCode[$website->getId()] = $website->getCode();
508  }
509  return $this;
510  }
511 
518  protected function getMediaGallery(array $productIds)
519  {
520  if (empty($productIds)) {
521  return [];
522  }
523  $select = $this->_connection->select()->from(
524  ['mgvte' => $this->_resourceModel->getTableName('catalog_product_entity_media_gallery_value_to_entity')],
525  [
526  "mgvte.{$this->getProductEntityLinkField()}",
527  'mgvte.value_id'
528  ]
529  )->joinLeft(
530  ['mg' => $this->_resourceModel->getTableName('catalog_product_entity_media_gallery')],
531  '(mg.value_id = mgvte.value_id)',
532  [
533  'mg.attribute_id',
534  'filename' => 'mg.value',
535  ]
536  )->joinLeft(
537  ['mgv' => $this->_resourceModel->getTableName('catalog_product_entity_media_gallery_value')],
538  '(mg.value_id = mgv.value_id)',
539  [
540  'mgv.label',
541  'mgv.position',
542  'mgv.disabled',
543  'mgv.store_id',
544  ]
545  )->where(
546  "mgvte.{$this->getProductEntityLinkField()} IN (?)",
548  );
549 
550  $rowMediaGallery = [];
551  $stmt = $this->_connection->query($select);
552  while ($mediaRow = $stmt->fetch()) {
553  $rowMediaGallery[$mediaRow[$this->getProductEntityLinkField()]][] = [
554  '_media_attribute_id' => $mediaRow['attribute_id'],
555  '_media_image' => $mediaRow['filename'],
556  '_media_label' => $mediaRow['label'],
557  '_media_position' => $mediaRow['position'],
558  '_media_is_disabled' => $mediaRow['disabled'],
559  '_media_store_id' => $mediaRow['store_id'],
560  ];
561  }
562 
563  return $rowMediaGallery;
564  }
565 
572  protected function prepareCatalogInventory(array $productIds)
573  {
574  if (empty($productIds)) {
575  return [];
576  }
577  $select = $this->_connection->select()->from(
578  $this->_itemFactory->create()->getMainTable()
579  )->where(
580  'product_id IN (?)',
582  );
583 
584  $stmt = $this->_connection->query($select);
585  $stockItemRows = [];
586  while ($stockItemRow = $stmt->fetch()) {
587  $productId = $stockItemRow['product_id'];
588  unset(
589  $stockItemRow['item_id'],
590  $stockItemRow['product_id'],
591  $stockItemRow['low_stock_date'],
592  $stockItemRow['stock_id'],
593  $stockItemRow['stock_status_changed_auto']
594  );
595  $stockItemRows[$productId] = $stockItemRow;
596  }
597  return $stockItemRows;
598  }
599 
606  protected function prepareLinks(array $productIds)
607  {
608  if (empty($productIds)) {
609  return [];
610  }
611  $select = $this->_connection->select()->from(
612  ['cpl' => $this->_resourceModel->getTableName('catalog_product_link')],
613  [
614  'cpl.product_id',
615  'cpe.sku',
616  'cpl.link_type_id',
617  'position' => 'cplai.value',
618  'default_qty' => 'cplad.value'
619  ]
620  )->joinLeft(
621  ['cpe' => $this->_resourceModel->getTableName('catalog_product_entity')],
622  '(cpe.entity_id = cpl.linked_product_id)',
623  []
624  )->joinLeft(
625  ['cpla' => $this->_resourceModel->getTableName('catalog_product_link_attribute')],
626  $this->_connection->quoteInto(
627  '(cpla.link_type_id = cpl.link_type_id AND cpla.product_link_attribute_code = ?)',
628  'position'
629  ),
630  []
631  )->joinLeft(
632  ['cplaq' => $this->_resourceModel->getTableName('catalog_product_link_attribute')],
633  $this->_connection->quoteInto(
634  '(cplaq.link_type_id = cpl.link_type_id AND cplaq.product_link_attribute_code = ?)',
635  'qty'
636  ),
637  []
638  )->joinLeft(
639  ['cplai' => $this->_resourceModel->getTableName('catalog_product_link_attribute_int')],
640  '(cplai.link_id = cpl.link_id AND cplai.product_link_attribute_id = cpla.product_link_attribute_id)',
641  []
642  )->joinLeft(
643  ['cplad' => $this->_resourceModel->getTableName('catalog_product_link_attribute_decimal')],
644  '(cplad.link_id = cpl.link_id AND cplad.product_link_attribute_id = cplaq.product_link_attribute_id)',
645  []
646  )->where(
647  'cpl.link_type_id IN (?)',
648  array_values($this->_linkTypeProvider->getLinkTypes())
649  )->where(
650  'cpl.product_id IN (?)',
652  );
653 
654  $stmt = $this->_connection->query($select);
655  $linksRows = [];
656  while ($linksRow = $stmt->fetch()) {
657  $linksRows[$linksRow['product_id']][$linksRow['link_type_id']][] = [
658  'sku' => $linksRow['sku'],
659  'position' => $linksRow['position'],
660  'default_qty' => $linksRow['default_qty'],
661  ];
662  }
663 
664  return $linksRows;
665  }
666 
675  protected function updateDataWithCategoryColumns(&$dataRow, &$rowCategories, $productId)
676  {
677  if (!isset($rowCategories[$productId])) {
678  return false;
679  }
680  $categories = [];
681  foreach ($rowCategories[$productId] as $categoryId) {
682  $categoryPath = $this->_rootCategories[$categoryId];
683  if (isset($this->_categories[$categoryId])) {
684  $categoryPath .= '/' . $this->_categories[$categoryId];
685  }
686  $categories[] = $categoryPath;
687  }
689  unset($rowCategories[$productId]);
690 
691  return true;
692  }
693 
697  public function _getHeaderColumns()
698  {
699  return $this->_customHeadersMapping($this->rowCustomizer->addHeaderColumns($this->_headerColumns));
700  }
701 
712  protected function setHeaderColumns($customOptionsData, $stockItemRows)
713  {
714  if (!$this->_headerColumns) {
715  $this->_headerColumns = array_merge(
716  [
717  self::COL_SKU,
718  self::COL_STORE,
719  self::COL_ATTR_SET,
720  self::COL_TYPE,
721  self::COL_CATEGORY,
722  self::COL_PRODUCT_WEBSITES,
723  ],
724  $this->_getExportMainAttrCodes(),
725  [self::COL_ADDITIONAL_ATTRIBUTES],
726  reset($stockItemRows) ? array_keys(end($stockItemRows)) : [],
727  [
728  'related_skus',
729  'related_position',
730  'crosssell_skus',
731  'crosssell_position',
732  'upsell_skus',
733  'upsell_position',
734  'additional_images',
735  'additional_image_labels',
736  'hide_from_product_page',
737  'custom_options'
738  ]
739  );
740  }
741  }
742 
748  protected function _getExportMainAttrCodes()
749  {
751  }
752 
756  protected function _getEntityCollection($resetCollection = false)
757  {
758  if ($resetCollection || empty($this->_entityCollection)) {
759  $this->_entityCollection = $this->_entityCollectionFactory->create();
760  }
762  }
763 
769  protected function getItemsPerPage()
770  {
771  if ($this->_itemsPerPage === null) {
772  $memoryLimitConfigValue = trim(ini_get('memory_limit'));
773  $lastMemoryLimitLetter = strtolower($memoryLimitConfigValue[strlen($memoryLimitConfigValue) - 1]);
774  $memoryLimit = (int) $memoryLimitConfigValue;
775  switch ($lastMemoryLimitLetter) {
776  case 'g':
777  $memoryLimit *= 1024;
778  // fall-through intentional
779  case 'm':
780  $memoryLimit *= 1024;
781  // fall-through intentional
782  case 'k':
783  $memoryLimit *= 1024;
784  break;
785  default:
786  // minimum memory required by Magento
787  $memoryLimit = 250000000;
788  }
789 
790  // Tested one product to have up to such size
791  $memoryPerProduct = 500000;
792  // Decrease memory limit to have supply
793  $memoryUsagePercent = 0.8;
794  // Minimum Products limit
795  $minProductsLimit = 500;
796  // Maximal Products limit
797  $maxProductsLimit = 5000;
798 
799  $this->_itemsPerPage = intval(
800  ($memoryLimit * $memoryUsagePercent - memory_get_usage(true)) / $memoryPerProduct
801  );
802  if ($this->_itemsPerPage < $minProductsLimit) {
803  $this->_itemsPerPage = $minProductsLimit;
804  }
805  if ($this->_itemsPerPage > $maxProductsLimit) {
806  $this->_itemsPerPage = $maxProductsLimit;
807  }
808  }
809  return $this->_itemsPerPage;
810  }
811 
819  protected function paginateCollection($page, $pageSize)
820  {
821  $this->_getEntityCollection()->setPage($page, $pageSize);
822  }
823 
829  public function export()
830  {
831  //Execution time may be very long
832  set_time_limit(0);
833 
834  $writer = $this->getWriter();
835  $page = 0;
836  while (true) {
837  ++$page;
838  $entityCollection = $this->_getEntityCollection(true);
839  $entityCollection->setOrder('entity_id', 'asc');
840  $entityCollection->setStoreId(Store::DEFAULT_STORE_ID);
841  $this->_prepareEntityCollection($entityCollection);
842  $this->paginateCollection($page, $this->getItemsPerPage());
843  if ($entityCollection->count() == 0) {
844  break;
845  }
846  $exportData = $this->getExportData();
847  if ($page == 1) {
848  $writer->setHeaderCols($this->_getHeaderColumns());
849  }
850  foreach ($exportData as $dataRow) {
851  $writer->writeRow($this->_customFieldsMapping($dataRow));
852  }
853  if ($entityCollection->getCurPage() >= $entityCollection->getLastPageNumber()) {
854  break;
855  }
856  }
857  return $writer->getContents();
858  }
859 
864  protected function _prepareEntityCollection(\Magento\Eav\Model\Entity\Collection\AbstractCollection $collection)
865  {
866  $exportFilter = !empty($this->_parameters[\Magento\ImportExport\Model\Export::FILTER_ELEMENT_GROUP]) ?
868 
869  if (isset($exportFilter['category_ids'])
870  && trim($exportFilter['category_ids'])
871  && $collection instanceof \Magento\Catalog\Model\ResourceModel\Product\Collection
872  ) {
873  $collection->addCategoriesFilter(['in' => explode(',', $exportFilter['category_ids'])]);
874  }
875 
876  return parent::_prepareEntityCollection($collection);
877  }
878 
888  protected function getExportData()
889  {
890  $exportData = [];
891  try {
892  $rawData = $this->collectRawData();
893  $multirawData = $this->collectMultirawData();
894 
895  $productIds = array_keys($rawData);
896  $stockItemRows = $this->prepareCatalogInventory($productIds);
897 
898  $this->rowCustomizer->prepareData(
899  $this->_prepareEntityCollection($this->_entityCollectionFactory->create()),
901  );
902 
903  $this->setHeaderColumns($multirawData['customOptionsData'], $stockItemRows);
904 
905  foreach ($rawData as $productId => $productData) {
906  foreach ($productData as $storeId => $dataRow) {
907  if ($storeId == Store::DEFAULT_STORE_ID && isset($stockItemRows[$productId])) {
908  $dataRow = array_merge($dataRow, $stockItemRows[$productId]);
909  }
910  $this->appendMultirowData($dataRow, $multirawData);
911  if ($dataRow) {
912  $exportData[] = $dataRow;
913  }
914  }
915  }
916  } catch (\Exception $e) {
917  $this->_logger->critical($e);
918  }
919  return $exportData;
920  }
921 
930  protected function loadCollection(): array
931  {
932  $data = [];
933 
935  foreach (array_keys($this->_storeIdToCode) as $storeId) {
936  $collection->setStoreId($storeId);
937  foreach ($collection as $itemId => $item) {
938  $data[$itemId][$storeId] = $item;
939  }
940  }
941  $collection->clear();
942 
943  return $data;
944  }
945 
953  protected function collectRawData()
954  {
955  $data = [];
956  $items = $this->loadCollection();
957 
962  foreach ($items as $itemId => $itemByStore) {
963  foreach ($this->_storeIdToCode as $storeId => $storeCode) {
964  $item = $itemByStore[$storeId];
965  $additionalAttributes = [];
966  $productLinkId = $item->getData($this->getProductEntityLinkField());
967  foreach ($this->_getExportAttrCodes() as $code) {
968  $attrValue = $item->getData($code);
969  if (!$this->isValidAttributeValue($code, $attrValue)) {
970  continue;
971  }
972 
973  if (isset($this->_attributeValues[$code][$attrValue]) && !empty($this->_attributeValues[$code])) {
974  $attrValue = $this->_attributeValues[$code][$attrValue];
975  }
976  $fieldName = isset($this->_fieldsMap[$code]) ? $this->_fieldsMap[$code] : $code;
977 
978  if ($this->_attributeTypes[$code] == 'datetime') {
979  if (in_array($code, $this->dateAttrCodes)
980  || in_array($code, $this->userDefinedAttributes)
981  ) {
982  $attrValue = $this->_localeDate->formatDateTime(
983  new \DateTime($attrValue),
984  \IntlDateFormatter::SHORT,
985  \IntlDateFormatter::NONE,
986  null,
987  date_default_timezone_get()
988  );
989  } else {
990  $attrValue = $this->_localeDate->formatDateTime(
991  new \DateTime($attrValue),
992  \IntlDateFormatter::SHORT,
993  \IntlDateFormatter::SHORT
994  );
995  }
996  }
997 
999  && isset($data[$itemId][Store::DEFAULT_STORE_ID][$fieldName])
1000  && $data[$itemId][Store::DEFAULT_STORE_ID][$fieldName] == htmlspecialchars_decode($attrValue)
1001  ) {
1002  continue;
1003  }
1004 
1005  if ($this->_attributeTypes[$code] !== 'multiselect') {
1006  if (is_scalar($attrValue)) {
1007  if (!in_array($fieldName, $this->_getExportMainAttrCodes())) {
1008  $additionalAttributes[$fieldName] = $fieldName .
1009  ImportProduct::PAIR_NAME_VALUE_SEPARATOR . $this->wrapValue($attrValue);
1010  }
1011  $data[$itemId][$storeId][$fieldName] = htmlspecialchars_decode($attrValue);
1012  }
1013  } else {
1014  $this->collectMultiselectValues($item, $code, $storeId);
1015  if (!empty($this->collectedMultiselectsData[$storeId][$productLinkId][$code])) {
1016  $additionalAttributes[$code] = $fieldName .
1017  ImportProduct::PAIR_NAME_VALUE_SEPARATOR . implode(
1018  ImportProduct::PSEUDO_MULTI_LINE_SEPARATOR,
1019  $this->wrapValue($this->collectedMultiselectsData[$storeId][$productLinkId][$code])
1020  );
1021  }
1022  }
1023  }
1024 
1025  if (!empty($additionalAttributes)) {
1026  $additionalAttributes = array_map('htmlspecialchars_decode', $additionalAttributes);
1028  implode(Import::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR, $additionalAttributes);
1029  } else {
1030  unset($data[$itemId][$storeId][self::COL_ADDITIONAL_ATTRIBUTES]);
1031  }
1032 
1033  $attrSetId = $item->getAttributeSetId();
1035  $data[$itemId][$storeId][self::COL_ATTR_SET] = $this->_attrSetIdToName[$attrSetId];
1036  $data[$itemId][$storeId][self::COL_TYPE] = $item->getTypeId();
1037  $data[$itemId][$storeId][self::COL_SKU] = htmlspecialchars_decode($item->getSku());
1038  $data[$itemId][$storeId]['store_id'] = $storeId;
1039  $data[$itemId][$storeId]['product_id'] = $itemId;
1040  $data[$itemId][$storeId]['product_link_id'] = $productLinkId;
1041  }
1042  }
1043 
1044  return $data;
1045  }
1046 
1053  private function wrapValue($value)
1054  {
1055  if (!empty($this->_parameters[\Magento\ImportExport\Model\Export::FIELDS_ENCLOSURE])) {
1056  $wrap = function ($value) {
1057  return sprintf('"%s"', str_replace('"', '""', $value));
1058  };
1059 
1060  $value = is_array($value) ? array_map($wrap, $value) : $wrap($value);
1061  }
1062 
1063  return $value;
1064  }
1065 
1069  protected function collectMultirawData()
1070  {
1071  $data = [];
1072  $productIds = [];
1073  $rowWebsites = [];
1074  $rowCategories = [];
1075 
1076  $collection = $this->_getEntityCollection();
1077  $collection->setStoreId(Store::DEFAULT_STORE_ID);
1078  $collection->addCategoryIds()->addWebsiteNamesToResult();
1080  foreach ($collection as $item) {
1081  $productLinkIds[] = $item->getData($this->getProductEntityLinkField());
1082  $productIds[] = $item->getId();
1083  $rowWebsites[$item->getId()] = array_intersect(
1084  array_keys($this->_websiteIdToCode),
1085  $item->getWebsites()
1086  );
1087  $rowCategories[$item->getId()] = array_combine($item->getCategoryIds(), $item->getCategoryIds());
1088  }
1089  $collection->clear();
1090 
1091  $allCategoriesIds = array_merge(array_keys($this->_categories), array_keys($this->_rootCategories));
1092  $allCategoriesIds = array_combine($allCategoriesIds, $allCategoriesIds);
1093  foreach ($rowCategories as &$categories) {
1094  $categories = array_intersect_key($categories, $allCategoriesIds);
1095  }
1096 
1097  $data['rowWebsites'] = $rowWebsites;
1098  $data['rowCategories'] = $rowCategories;
1099  $data['mediaGalery'] = $this->getMediaGallery($productLinkIds);
1100  $data['linksRows'] = $this->prepareLinks($productLinkIds);
1101 
1102  $data['customOptionsData'] = $this->getCustomOptionsData($productLinkIds);
1103 
1104  return $data;
1105  }
1106 
1113  protected function hasMultiselectData($item, $storeId)
1114  {
1115  $linkId = $item->getData($this->getProductEntityLinkField());
1116  return !empty($this->collectedMultiselectsData[$storeId][$linkId]);
1117  }
1118 
1125  protected function collectMultiselectValues($item, $attrCode, $storeId)
1126  {
1127  $attrValue = $item->getData($attrCode);
1129  $options = array_intersect_key(
1130  $this->_attributeValues[$attrCode],
1131  array_flip($optionIds)
1132  );
1133  $linkId = $item->getData($this->getProductEntityLinkField());
1134  if (!(isset($this->collectedMultiselectsData[Store::DEFAULT_STORE_ID][$linkId][$attrCode])
1135  && $this->collectedMultiselectsData[Store::DEFAULT_STORE_ID][$linkId][$attrCode] == $options)
1136  ) {
1137  $this->collectedMultiselectsData[$storeId][$linkId][$attrCode] = $options;
1138  }
1139 
1140  return $this;
1141  }
1142 
1148  protected function isValidAttributeValue($code, $value)
1149  {
1150  $isValid = true;
1151  if (!is_numeric($value) && empty($value)) {
1152  $isValid = false;
1153  }
1154 
1155  if (!isset($this->_attributeValues[$code])) {
1156  $isValid = false;
1157  }
1158 
1159  return $isValid;
1160  }
1161 
1170  private function appendMultirowData(&$dataRow, $multiRawData)
1171  {
1172  $productId = $dataRow['product_id'];
1173  $productLinkId = $dataRow['product_link_id'];
1174  $storeId = $dataRow['store_id'];
1175  $sku = $dataRow[self::COL_SKU];
1176  $type = $dataRow[self::COL_TYPE];
1177  $attributeSet = $dataRow[self::COL_ATTR_SET];
1178 
1179  unset($dataRow['product_id']);
1180  unset($dataRow['product_link_id']);
1181  unset($dataRow['store_id']);
1182  unset($dataRow[self::COL_SKU]);
1183  unset($dataRow[self::COL_STORE]);
1184  unset($dataRow[self::COL_ATTR_SET]);
1185  unset($dataRow[self::COL_TYPE]);
1186 
1188  $this->updateDataWithCategoryColumns($dataRow, $multiRawData['rowCategories'], $productId);
1189  if (!empty($multiRawData['rowWebsites'][$productId])) {
1190  $websiteCodes = [];
1191  foreach ($multiRawData['rowWebsites'][$productId] as $productWebsite) {
1192  $websiteCodes[] = $this->_websiteIdToCode[$productWebsite];
1193  }
1194  $dataRow[self::COL_PRODUCT_WEBSITES] =
1196  $multiRawData['rowWebsites'][$productId] = [];
1197  }
1198  if (!empty($multiRawData['mediaGalery'][$productLinkId])) {
1199  $additionalImages = [];
1200  $additionalImageLabels = [];
1201  $additionalImageIsDisabled = [];
1202  foreach ($multiRawData['mediaGalery'][$productLinkId] as $mediaItem) {
1203  if ((int)$mediaItem['_media_store_id'] === Store::DEFAULT_STORE_ID) {
1204  $additionalImages[] = $mediaItem['_media_image'];
1205  $additionalImageLabels[] = $mediaItem['_media_label'];
1206 
1207  if ($mediaItem['_media_is_disabled'] == true) {
1208  $additionalImageIsDisabled[] = $mediaItem['_media_image'];
1209  }
1210  }
1211  }
1212  $dataRow['additional_images'] =
1213  implode(Import::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR, $additionalImages);
1214  $dataRow['additional_image_labels'] =
1215  implode(Import::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR, $additionalImageLabels);
1216  $dataRow['hide_from_product_page'] =
1217  implode(Import::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR, $additionalImageIsDisabled);
1218  $multiRawData['mediaGalery'][$productLinkId] = [];
1219  }
1220  foreach ($this->_linkTypeProvider->getLinkTypes() as $linkTypeName => $linkId) {
1221  if (!empty($multiRawData['linksRows'][$productLinkId][$linkId])) {
1222  $colPrefix = $linkTypeName . '_';
1223 
1224  $associations = [];
1225  foreach ($multiRawData['linksRows'][$productLinkId][$linkId] as $linkData) {
1226  if ($linkData['default_qty'] !== null) {
1227  $skuItem = $linkData['sku'] . ImportProduct::PAIR_NAME_VALUE_SEPARATOR .
1228  $linkData['default_qty'];
1229  } else {
1230  $skuItem = $linkData['sku'];
1231  }
1232  $associations[$skuItem] = $linkData['position'];
1233  }
1234  $multiRawData['linksRows'][$productLinkId][$linkId] = [];
1235  asort($associations);
1236  $dataRow[$colPrefix . 'skus'] =
1237  implode(Import::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR, array_keys($associations));
1238  $dataRow[$colPrefix . 'position'] =
1239  implode(Import::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR, array_values($associations));
1240  }
1241  }
1242  $dataRow = $this->rowCustomizer->addData($dataRow, $productId);
1243  } else {
1244  $additionalImageIsDisabled = [];
1245  if (!empty($multiRawData['mediaGalery'][$productLinkId])) {
1246  foreach ($multiRawData['mediaGalery'][$productLinkId] as $mediaItem) {
1247  if ((int)$mediaItem['_media_store_id'] === $storeId) {
1248  if ($mediaItem['_media_is_disabled'] == true) {
1249  $additionalImageIsDisabled[] = $mediaItem['_media_image'];
1250  }
1251  }
1252  }
1253  }
1254  if ($additionalImageIsDisabled) {
1255  $dataRow['hide_from_product_page'] =
1256  implode(Import::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR, $additionalImageIsDisabled);
1257  }
1258  }
1259 
1260  if (!empty($this->collectedMultiselectsData[$storeId][$productId])) {
1261  foreach (array_keys($this->collectedMultiselectsData[$storeId][$productId]) as $attrKey) {
1262  if (!empty($this->collectedMultiselectsData[$storeId][$productId][$attrKey])) {
1263  $dataRow[$attrKey] = implode(
1265  $this->collectedMultiselectsData[$storeId][$productId][$attrKey]
1266  );
1267  }
1268  }
1269  }
1270 
1271  if (!empty($multiRawData['customOptionsData'][$productLinkId][$storeId])) {
1272  $customOptionsRows = $multiRawData['customOptionsData'][$productLinkId][$storeId];
1273  $multiRawData['customOptionsData'][$productLinkId][$storeId] = [];
1274  $customOptions = implode(ImportProduct::PSEUDO_MULTI_LINE_SEPARATOR, $customOptionsRows);
1275 
1276  $dataRow = array_merge($dataRow, ['custom_options' => $customOptions]);
1277  }
1278 
1279  if (empty($dataRow)) {
1280  return null;
1282  $dataRow[self::COL_STORE] = $this->_storeIdToCode[$storeId];
1283  }
1284  $dataRow[self::COL_SKU] = $sku;
1285  $dataRow[self::COL_ATTR_SET] = $attributeSet;
1286  $dataRow[self::COL_TYPE] = $type;
1287 
1288  return $dataRow;
1289  }
1290 
1297  protected function addMultirowData($dataRow, $multiRawData)
1298  {
1299  $data = $this->appendMultirowData($dataRow, $multiRawData);
1300  return $data ? [$data] : [];
1301  }
1302 
1310  protected function _customFieldsMapping($rowData)
1311  {
1312  foreach ($this->_fieldsMap as $systemFieldName => $fileFieldName) {
1313  if (isset($rowData[$systemFieldName])) {
1314  $rowData[$fileFieldName] = $rowData[$systemFieldName];
1315  unset($rowData[$systemFieldName]);
1316  }
1317  }
1318  return $rowData;
1319  }
1320 
1328  protected function _customHeadersMapping($rowData)
1329  {
1330  foreach ($rowData as $key => $fieldName) {
1331  if (isset($this->_fieldsMap[$fieldName])) {
1332  $rowData[$key] = $this->_fieldsMap[$fieldName];
1333  }
1334  }
1335  return $rowData;
1336  }
1337 
1342  protected function optionRowToCellString($option)
1343  {
1344  $result = [];
1345 
1346  foreach ($option as $key => $value) {
1347  $result[] = $key . ImportProduct::PAIR_NAME_VALUE_SEPARATOR . $value;
1348  }
1349 
1351  }
1352 
1365  protected function getCustomOptionsData($productIds)
1366  {
1367  $customOptionsData = [];
1368 
1369  foreach (array_keys($this->_storeIdToCode) as $storeId) {
1370  $options = $this->_optionColFactory->create();
1371  /* @var Collection $options*/
1372  $options->reset()
1373  ->addOrder('sort_order', Collection::SORT_ORDER_ASC)
1374  ->addTitleToResult($storeId)
1375  ->addPriceToResult($storeId)
1376  ->addProductToFilter($productIds)
1377  ->addValuesToResult($storeId);
1378 
1379  foreach ($options as $option) {
1380  $row = [];
1381  $productId = $option['product_id'];
1382  $row['name'] = $option['title'];
1383  $row['type'] = $option['type'];
1385  $row['required'] = $option['is_require'];
1386  $row['price'] = $option['price'];
1387  $row['price_type'] = ($option['price_type'] === 'percent') ? 'percent' : 'fixed';
1388  $row['sku'] = $option['sku'];
1389  if ($option['max_characters']) {
1390  $row['max_characters'] = $option['max_characters'];
1391  }
1392 
1393  foreach (['file_extension', 'image_size_x', 'image_size_y'] as $fileOptionKey) {
1394  if (!isset($option[$fileOptionKey])) {
1395  continue;
1396  }
1397 
1398  $row[$fileOptionKey] = $option[$fileOptionKey];
1399  }
1400  }
1401  $values = $option->getValues();
1402 
1403  if ($values) {
1404  foreach ($values as $value) {
1405  $row['option_title'] = $value['title'];
1407  $row['option_title'] = $value['title'];
1408  $row['price'] = $value['price'];
1409  $row['price_type'] = ($value['price_type'] === 'percent') ? 'percent' : 'fixed';
1410  $row['sku'] = $value['sku'];
1411  }
1412  $customOptionsData[$productId][$storeId][] = $this->optionRowToCellString($row);
1413  }
1414  } else {
1415  $customOptionsData[$productId][$storeId][] = $this->optionRowToCellString($row);
1416  }
1417  $option = null;
1418  }
1419  $options = null;
1420  }
1421 
1422  return $customOptionsData;
1423  }
1424 
1431  public function filterAttributeCollection(\Magento\Eav\Model\ResourceModel\Entity\Attribute\Collection $collection)
1432  {
1433  $validTypes = array_keys($this->_productTypeModels);
1434  $validTypes = array_combine($validTypes, $validTypes);
1435 
1436  foreach (parent::filterAttributeCollection($collection) as $attribute) {
1437  if (in_array($attribute->getAttributeCode(), $this->_bannedAttributes)) {
1438  $collection->removeItemByKey($attribute->getId());
1439  continue;
1440  }
1441  $attrApplyTo = $attribute->getApplyTo();
1442  $attrApplyTo = array_combine($attrApplyTo, $attrApplyTo);
1443  $attrApplyTo = $attrApplyTo ? array_intersect_key($attrApplyTo, $validTypes) : $validTypes;
1444 
1445  if ($attrApplyTo) {
1446  foreach ($attrApplyTo as $productType) {
1447  // override attributes by its product type model
1448  if ($this->_productTypeModels[$productType]->overrideAttribute($attribute)) {
1449  break;
1450  }
1451  }
1452  } else {
1453  // remove attributes of not-supported product types
1454  $collection->removeItemByKey($attribute->getId());
1455  }
1456  }
1457  return $collection;
1458  }
1459 
1465  public function getAttributeCollection()
1466  {
1467  return $this->_attributeColFactory->create();
1468  }
1469 
1475  public function getEntityTypeCode()
1476  {
1477  return 'catalog_product';
1478  }
1479 
1485  protected function initAttributes()
1486  {
1487  foreach ($this->getAttributeCollection() as $attribute) {
1488  $this->_attributeValues[$attribute->getAttributeCode()] = $this->getAttributeOptions($attribute);
1489  $this->_attributeTypes[$attribute->getAttributeCode()] =
1491  if ($attribute->getIsUserDefined()) {
1492  $this->userDefinedAttributes[] = $attribute->getAttributeCode();
1493  }
1494  }
1495  return $this;
1496  }
1497 
1503  private function getMetadataPool()
1504  {
1505  if (!$this->metadataPool) {
1507  ->get(\Magento\Framework\EntityManager\MetadataPool::class);
1508  }
1509  return $this->metadataPool;
1510  }
1511 
1518  protected function getProductEntityLinkField()
1519  {
1520  if (!$this->productEntityLinkField) {
1521  $this->productEntityLinkField = $this->getMetadataPool()
1522  ->getMetadata(\Magento\Catalog\Api\Data\ProductInterface::class)
1523  ->getLinkField();
1524  }
1525  return $this->productEntityLinkField;
1526  }
1527 
1534  private function quoteCategoryDelimiter($string)
1535  {
1536  return str_replace(
1539  $string
1540  );
1541  }
1542 }
updateDataWithCategoryColumns(&$dataRow, &$rowCategories, $productId)
Definition: Product.php:675
static getAttributeType(\Magento\Eav\Model\Entity\Attribute\AbstractAttribute $attribute)
Definition: Import.php:345
_prepareEntityCollection(\Magento\Eav\Model\Entity\Collection\AbstractCollection $collection)
Definition: Product.php:864
elseif(isset( $params[ 'redirect_parent']))
Definition: iframe.phtml:17
getAttributeOptions(\Magento\Eav\Model\Entity\Attribute\AbstractAttribute $attribute)
$config
Definition: fraud_order.php:17
filterAttributeCollection(\Magento\Eav\Model\ResourceModel\Entity\Attribute\Collection $collection)
Definition: Product.php:1431
$storeManager
$values
Definition: options.phtml:88
__()
Definition: __.php:13
$resource
Definition: bulk.php:12
$logger
$storeCode
Definition: indexer.php:15
$type
Definition: item.phtml:13
_getEntityCollection($resetCollection=false)
Definition: Product.php:756
$value
Definition: gender.phtml:16
setHeaderColumns($customOptionsData, $stockItemRows)
Definition: Product.php:712
$page
Definition: pages.php:8
collectMultiselectValues($item, $attrCode, $storeId)
Definition: Product.php:1125
$productData
__construct(\Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate, \Magento\Eav\Model\Config $config, \Magento\Framework\App\ResourceConnection $resource, \Magento\Store\Model\StoreManagerInterface $storeManager, \Psr\Log\LoggerInterface $logger, \Magento\Catalog\Model\ResourceModel\Product\CollectionFactory $collectionFactory, \Magento\ImportExport\Model\Export\ConfigInterface $exportConfig, \Magento\Catalog\Model\ResourceModel\ProductFactory $productFactory, \Magento\Eav\Model\ResourceModel\Entity\Attribute\Set\CollectionFactory $attrSetColFactory, \Magento\Catalog\Model\ResourceModel\Category\CollectionFactory $categoryColFactory, \Magento\CatalogInventory\Model\ResourceModel\Stock\ItemFactory $itemFactory, \Magento\Catalog\Model\ResourceModel\Product\Option\CollectionFactory $optionColFactory, \Magento\Catalog\Model\ResourceModel\Product\Attribute\CollectionFactory $attributeColFactory, \Magento\CatalogImportExport\Model\Export\Product\Type\Factory $_typeFactory, \Magento\Catalog\Model\Product\LinkTypeProvider $linkTypeProvider, \Magento\CatalogImportExport\Model\Export\RowCustomizerInterface $rowCustomizer, array $dateAttrCodes=[])
Definition: Product.php:371
$categories
$i
Definition: gallery.phtml:31
$code
Definition: info.phtml:12
$items
if(!isset($_GET['name'])) $name
Definition: log.php:14