From 912b3f7737259d5977ce3fa64781a3280c712bfe Mon Sep 17 00:00:00 2001 From: Pieter Cappelle Date: Wed, 5 Feb 2020 13:38:28 +0100 Subject: [PATCH 1/8] Added hashbased check to prevent image duplication on product import & remove unused images --- .../Model/Import/Product.php | 204 ++++++++++++++---- .../Import/Product/MediaGalleryProcessor.php | 20 ++ 2 files changed, 186 insertions(+), 38 deletions(-) diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product.php b/app/code/Magento/CatalogImportExport/Model/Import/Product.php index ae5f0f5d79e2a..58ae970e93f82 100644 --- a/app/code/Magento/CatalogImportExport/Model/Import/Product.php +++ b/app/code/Magento/CatalogImportExport/Model/Import/Product.php @@ -1020,6 +1020,7 @@ public function setParameters(array $params) * Delete products for replacement. * * @return $this + * @throws \Exception */ public function deleteProductsForReplacement() { @@ -1111,6 +1112,11 @@ protected function _importData() * Replace imported products. * * @return $this + * @throws LocalizedException + * @throws \Magento\Framework\Exception\CouldNotSaveException + * @throws \Magento\Framework\Exception\InputException + * @throws \Magento\Framework\Validation\ValidationException + * @throws \Zend_Validate_Exception */ protected function _replaceProducts() { @@ -1132,6 +1138,11 @@ protected function _replaceProducts() * Save products data. * * @return $this + * @throws LocalizedException + * @throws \Magento\Framework\Exception\CouldNotSaveException + * @throws \Magento\Framework\Exception\InputException + * @throws \Magento\Framework\Validation\ValidationException + * @throws \Zend_Validate_Exception */ protected function _saveProductsData() { @@ -1274,6 +1285,11 @@ protected function _prepareRowForDb(array $rowData) * Must be called after ALL products saving done. * * @return $this + * @SuppressWarnings(PHPMD.CyclomaticComplexity) + * @SuppressWarnings(PHPMD.NPathComplexity) + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + * @throws LocalizedException + * phpcs:disable Generic.Metrics.NestingLevel */ protected function _saveLinks() { @@ -1305,6 +1321,7 @@ protected function _saveLinks() * * @param array $attributesData * @return $this + * @throws \Exception */ protected function _saveProductAttributes(array $attributesData) { @@ -1436,6 +1453,7 @@ private function getOldSkuFieldsForSelect() * * @param array $newProducts * @return void + * @throws \Exception */ private function updateOldSku(array $newProducts) { @@ -1459,6 +1477,7 @@ private function updateOldSku(array $newProducts) * Get new SKU fields for select * * @return array + * @throws \Exception */ private function getNewSkuFieldsForSelect() { @@ -1542,6 +1561,7 @@ public function getImagesFromRow(array $rowData) * @SuppressWarnings(PHPMD.ExcessiveMethodLength) * @SuppressWarnings(PHPMD.UnusedLocalVariable) * @throws LocalizedException + * @throws \Zend_Validate_Exception * phpcs:disable Generic.Metrics.NestingLevel.TooHigh */ protected function _saveProducts() @@ -1559,12 +1579,17 @@ protected function _saveProducts() $this->categoriesCache = []; $tierPrices = []; $mediaGallery = []; + $uploadedFiles = []; + $galleryItemsToRemove = []; $labelsForUpdate = []; $imagesForChangeVisibility = []; $uploadedImages = []; $previousType = null; $prevAttributeSet = null; + + $importDir = $this->_mediaDirectory->getAbsolutePath($this->getImportDir()); $existingImages = $this->getExistingImages($bunch); + $this->addImageHashes($existingImages); foreach ($bunch as $rowNum => $rowData) { // reset category processor's failed categories array @@ -1660,6 +1685,7 @@ protected function _saveProducts() if (!array_key_exists($rowSku, $this->websitesCache)) { $this->websitesCache[$rowSku] = []; } + // 2. Product-to-Website phase if (!empty($rowData[self::COL_PRODUCT_WEBSITES])) { $websiteCodes = explode($this->getMultipleValueSeparator(), $rowData[self::COL_PRODUCT_WEBSITES]); @@ -1711,12 +1737,11 @@ protected function _saveProducts() foreach (array_keys($imageHiddenStates) as $image) { //Mark image as uploaded if it exists if (array_key_exists($image, $rowExistingImages)) { + $rowImages[self::COL_MEDIA_IMAGE][] = $image; $uploadedImages[$image] = $image; } - //Add image to hide to images list if it does not exist - if (empty($rowImages[self::COL_MEDIA_IMAGE]) - || !in_array($image, $rowImages[self::COL_MEDIA_IMAGE]) - ) { + + if (empty($rowImages)) { $rowImages[self::COL_MEDIA_IMAGE][] = $image; } } @@ -1730,56 +1755,89 @@ protected function _saveProducts() $position = 0; foreach ($rowImages as $column => $columnImages) { foreach ($columnImages as $columnImageKey => $columnImage) { - if (!isset($uploadedImages[$columnImage])) { - $uploadedFile = $this->uploadMediaFiles($columnImage); - $uploadedFile = $uploadedFile ?: $this->getSystemFile($columnImage); - if ($uploadedFile) { - $uploadedImages[$columnImage] = $uploadedFile; + if (filter_var($columnImage, FILTER_VALIDATE_URL) === false) { + $filename = $importDir . DIRECTORY_SEPARATOR . $columnImage; + if (file_exists($filename)) { + $hash = hash_file('sha256', $importDir . DIRECTORY_SEPARATOR . $columnImage); } else { - unset($rowData[$column]); - $this->addRowError( - ValidatorInterface::ERROR_MEDIA_URL_NOT_ACCESSIBLE, - $rowNum, - null, - null, - ProcessingError::ERROR_LEVEL_NOT_CRITICAL - ); + $hash = hash_file('sha256', $filename); } } else { - $uploadedFile = $uploadedImages[$columnImage]; + $hash = hash_file('sha256', $columnImage); + } + + // Add new images + if (empty($rowExistingImages)) { + $imageAlreadyExists = false; + } else { + $imageAlreadyExists = array_reduce( + $rowExistingImages, + function ($exists, $file) use ($hash) { + if ($exists) { + return $exists; + } + if ($file['hash'] === $hash) { + return $file['value']; + } + return $exists; + }, + '' + ); + } + + if ($imageAlreadyExists) { + $uploadedFile = $imageAlreadyExists; + } else { + if (!isset($uploadedImages[$columnImage])) { + $uploadedFile = $this->uploadMediaFiles($columnImage); + $uploadedFile = $uploadedFile ?: $this->getSystemFile($columnImage); + if ($uploadedFile) { + $uploadedImages[$columnImage] = $uploadedFile; + } else { + unset($rowData[$column]); + $this->addRowError( + ValidatorInterface::ERROR_MEDIA_URL_NOT_ACCESSIBLE, + $rowNum, + null, + null, + ProcessingError::ERROR_LEVEL_NOT_CRITICAL + ); + } + } else { + $uploadedFile = $uploadedImages[$columnImage]; + } } if ($uploadedFile && $column !== self::COL_MEDIA_IMAGE) { $rowData[$column] = $uploadedFile; } + if ($uploadedFile) { + $uploadedFiles[] = $uploadedFile; + } + if (!$uploadedFile || isset($mediaGallery[$storeId][$rowSku][$uploadedFile])) { continue; } if (isset($rowExistingImages[$uploadedFile])) { $currentFileData = $rowExistingImages[$uploadedFile]; - $currentFileData['store_id'] = $storeId; - $storeMediaGalleryValueExists = isset($rowStoreMediaGalleryValues[$uploadedFile]); - if (array_key_exists($uploadedFile, $imageHiddenStates) - && $currentFileData['disabled'] != $imageHiddenStates[$uploadedFile] - ) { - $imagesForChangeVisibility[] = [ - 'disabled' => $imageHiddenStates[$uploadedFile], - 'imageData' => $currentFileData, - 'exists' => $storeMediaGalleryValueExists - ]; - $storeMediaGalleryValueExists = true; - } - if (isset($rowLabels[$column][$columnImageKey]) && $rowLabels[$column][$columnImageKey] != $currentFileData['label'] ) { $labelsForUpdate[] = [ 'label' => $rowLabels[$column][$columnImageKey], - 'imageData' => $currentFileData, - 'exists' => $storeMediaGalleryValueExists + 'imageData' => $currentFileData + ]; + } + + if (array_key_exists($uploadedFile, $imageHiddenStates) + && $currentFileData['disabled'] != $imageHiddenStates[$uploadedFile] + ) { + $imagesForChangeVisibility[] = [ + 'disabled' => $imageHiddenStates[$uploadedFile], + 'imageData' => $currentFileData ]; } } else { @@ -1800,6 +1858,17 @@ protected function _saveProducts() } } + // 5.1 Items to remove phase + if (!empty($rowExistingImages)) { + $galleryItemsToRemove = \array_merge( + $galleryItemsToRemove, + \array_diff( + \array_keys($rowExistingImages), + $uploadedFiles + ) + ); + } + // 6. Attributes phase $rowStore = (self::SCOPE_STORE == $rowScope) ? $this->storeResolver->getStoreCodeToId($rowData[self::COL_STORE]) @@ -1910,6 +1979,8 @@ protected function _saveProducts() $tierPrices )->_saveMediaGallery( $mediaGallery + )->_removeOldMediaGalleryItems( + $galleryItemsToRemove )->_saveProductAttributes( $attributes )->updateMediaGalleryVisibility( @@ -1928,6 +1999,25 @@ protected function _saveProducts() } //phpcs:enable Generic.Metrics.NestingLevel + /** + * Generate hashes for existing images for comparison with newly uploaded images. + * + * @param array $images + */ + public function addImageHashes(&$images) + { + $productMediaPath = $this->filesystem->getDirectoryRead(DirectoryList::MEDIA) + ->getAbsolutePath('/catalog/product'); + + foreach ($images as $storeId => $skus) { + foreach ($skus as $sku => $files) { + foreach ($files as $path => $file) { + $images[$storeId][$sku][$path]['hash'] = hash_file('sha256', $productMediaPath . $file['value']); + } + } + } + } + /** * Prepare array with image states (visible or hidden from product page) * @@ -2063,6 +2153,24 @@ protected function _saveProductTierPrices(array $tierPriceData) return $this; } + /** + * Returns the import directory if specified or a default import directory (media/import). + * + * @return string + */ + protected function getImportDir() + { + $dirConfig = DirectoryList::getDefaultConfig(); + $dirAddon = $dirConfig[DirectoryList::MEDIA][DirectoryList::PATH]; + + if (!empty($this->_parameters[Import::FIELD_NAME_IMG_FILE_DIR])) { + $tmpPath = $this->_parameters[Import::FIELD_NAME_IMG_FILE_DIR]; + } else { + $tmpPath = $dirAddon . '/' . $this->_mediaDirectory->getRelativePath('import'); + } + return $tmpPath; + } + /** * Returns an object for upload a media files * @@ -2079,11 +2187,7 @@ protected function _getUploader() $dirConfig = DirectoryList::getDefaultConfig(); $dirAddon = $dirConfig[DirectoryList::MEDIA][DirectoryList::PATH]; - if (!empty($this->_parameters[Import::FIELD_NAME_IMG_FILE_DIR])) { - $tmpPath = $this->_parameters[Import::FIELD_NAME_IMG_FILE_DIR]; - } else { - $tmpPath = $dirAddon . '/' . $this->_mediaDirectory->getRelativePath('import'); - } + $tmpPath = $this->getImportDir(); if (!$fileUploader->setTmpDir($tmpPath)) { throw new LocalizedException( @@ -2168,6 +2272,22 @@ protected function _saveMediaGallery(array $mediaGalleryData) return $this; } + /** + * Remove old media gallery items. + * + * @param array $itemsToRemove + * @return $this + */ + protected function _removeOldMediaGalleryItems(array $itemsToRemove) + { + if (empty($itemsToRemove)) { + return $this; + } + $this->mediaProcessor->removeOldMediaItems($itemsToRemove); + + return $this; + } + /** * Save product websites. * @@ -2210,6 +2330,9 @@ protected function _saveProductWebsites(array $websiteData) * Stock item saving. * * @return $this + * @throws \Magento\Framework\Exception\CouldNotSaveException + * @throws \Magento\Framework\Exception\InputException + * @throws \Magento\Framework\Validation\ValidationException */ protected function _saveStockItem() { @@ -2791,6 +2914,7 @@ private function _customFieldsMapping($rowData) * Validate data rows and save bunches to DB * * @return $this|AbstractEntity + * @throws LocalizedException */ protected function _saveValidatedBunches() { @@ -2930,6 +3054,7 @@ private function isNeedToChangeUrlKey(array $rowData): bool * Get product entity link field * * @return string + * @throws \Exception */ private function getProductEntityLinkField() { @@ -2945,6 +3070,7 @@ private function getProductEntityLinkField() * Get product entity identifier field * * @return string + * @throws \Exception */ private function getProductIdentifierField() { @@ -2961,6 +3087,7 @@ private function getProductIdentifierField() * * @param array $labels * @return void + * @throws \Exception */ private function updateMediaGalleryLabels(array $labels) { @@ -2974,6 +3101,7 @@ private function updateMediaGalleryLabels(array $labels) * * @param array $images * @return $this + * @throws \Exception */ private function updateMediaGalleryVisibility(array $images) { diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product/MediaGalleryProcessor.php b/app/code/Magento/CatalogImportExport/Model/Import/Product/MediaGalleryProcessor.php index a94a87a44b32a..9d24bbdb00440 100644 --- a/app/code/Magento/CatalogImportExport/Model/Import/Product/MediaGalleryProcessor.php +++ b/app/code/Magento/CatalogImportExport/Model/Import/Product/MediaGalleryProcessor.php @@ -104,6 +104,7 @@ public function __construct( * * @param array $mediaGalleryData * @return void + * @throws \Exception */ public function saveMediaGallery(array $mediaGalleryData) { @@ -270,6 +271,7 @@ private function prepareMediaGalleryValueData( * * @param array $labels * @return void + * @throws \Exception */ public function updateMediaGalleryLabels(array $labels) { @@ -281,6 +283,7 @@ public function updateMediaGalleryLabels(array $labels) * * @param array $images * @return void + * @throws \Exception */ public function updateMediaGalleryVisibility(array $images) { @@ -293,6 +296,7 @@ public function updateMediaGalleryVisibility(array $images) * @param array $data * @param string $field * @return void + * @throws \Exception */ private function updateMediaGalleryField(array $data, $field) { @@ -337,6 +341,7 @@ private function updateMediaGalleryField(array $data, $field) * * @param array $bunch * @return array + * @throws \Exception */ public function getExistingImages(array $bunch) { @@ -444,10 +449,25 @@ private function getLastMediaPositionPerProduct(array $productIds): array return $result; } + /** + * Remove old media gallery items. + * + * @param array $oldMediaValues + * @return void + */ + public function removeOldMediaItems(array $oldMediaValues) + { + $this->connection->delete( + $this->mediaGalleryTableName, + $this->connection->quoteInto('value IN (?)', $oldMediaValues) + ); + } + /** * Get product entity link field. * * @return string + * @throws \Exception */ private function getProductEntityLinkField() { From ffdb34112974d74aa2d72b5fd441641e85234ad3 Mon Sep 17 00:00:00 2001 From: Pieter Cappelle Date: Wed, 5 Feb 2020 13:38:38 +0100 Subject: [PATCH 2/8] Adjusted the product import test --- .../Catalog/_files/magento_image_2.jpg | Bin 0 -> 12137 bytes .../Model/Import/ProductTest.php | 18 ++++++++++++++++++ .../_files/import_media_update_images.csv | 2 ++ .../_files/import_with_filesystem_images.php | 4 ++++ 4 files changed, 24 insertions(+) create mode 100644 dev/tests/integration/testsuite/Magento/Catalog/_files/magento_image_2.jpg create mode 100644 dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/import_media_update_images.csv diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/magento_image_2.jpg b/dev/tests/integration/testsuite/Magento/Catalog/_files/magento_image_2.jpg new file mode 100644 index 0000000000000000000000000000000000000000..2c21e0238ede73faf93b6ae9641a2f649f537f55 GIT binary patch literal 12137 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 literal 0 HcmV?d00001 diff --git a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest.php b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest.php index f24981ca40156..47e02b08783ca 100644 --- a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest.php +++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest.php @@ -533,6 +533,7 @@ private function createImportModel($pathToFile, $behavior = \Magento\ImportExpor /** * @param string $productSku * @return array ['optionId' => ['optionValueId' => 'optionValueTitle', ...], ...] + * @throws \Magento\Framework\Exception\NoSuchEntityException */ private function getCustomOptionValues($productSku) { @@ -876,6 +877,23 @@ public function testSaveMediaImage() $this->assertInstanceOf(\Magento\Framework\DataObject::class, $additionalImageTwoItem); $this->assertEquals('/m/a/magento_additional_image_two.jpg', $additionalImageTwoItem->getFile()); $this->assertEquals('Additional Image Label Two', $additionalImageTwoItem->getLabel()); + + // Will check that existing product update works + // New unique images as per MD5 should be added, images not mentioned in the import should be removed + $this->importDataForMediaTest('import_media_update_images.csv'); + + $product = $this->getProductBySku('simple_new'); + $this->assertEquals('/m/a/magento_image_2.jpg', $product->getData('image')); + // small_image should be skipped from update as it is a duplicate (md5 is the same) + $this->assertEquals('/m/a/magento_small_image.jpg', $product->getData('small_image')); + $this->assertEquals('/m/a/magento_thumbnail.jpg', $product->getData('thumbnail')); + $this->assertEquals('/m/a/magento_image.jpg', $product->getData('swatch_image')); + + $gallery = $product->getMediaGalleryImages(); + $this->assertInstanceOf(\Magento\Framework\Data\Collection::class, $gallery); + + $items = $gallery->getItems(); + $this->assertCount(4, $items); } /** diff --git a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/import_media_update_images.csv b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/import_media_update_images.csv new file mode 100644 index 0000000000000..56dd5b4e977bf --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/import_media_update_images.csv @@ -0,0 +1,2 @@ +sku,store_view_code,attribute_set_code,product_type,categories,product_websites,name,description,short_description,weight,product_online,tax_class_name,visibility,price,special_price,special_price_from_date,special_price_to_date,url_key,meta_title,meta_keywords,meta_description,base_image,base_image_label,small_image,small_image_label,thumbnail_image,thumbnail_image_label,swatch_image,swatch_image_label1,created_at,updated_at,new_from_date,new_to_date,display_product_options_in,map_price,msrp_price,map_enabled,gift_message_available,custom_design,custom_design_from,custom_design_to,custom_layout_update,page_layout,product_options_container,msrp_display_actual_price_type,country_of_manufacture,additional_attributes,qty,out_of_stock_qty,use_config_min_qty,is_qty_decimal,allow_backorders,use_config_backorders,min_cart_qty,use_config_min_sale_qty,max_cart_qty,use_config_max_sale_qty,is_in_stock,notify_on_stock_below,use_config_notify_stock_qty,manage_stock,use_config_manage_stock,use_config_qty_increments,qty_increments,use_config_enable_qty_inc,enable_qty_increments,is_decimal_divided,website_id,related_skus,crosssell_skus,upsell_skus,additional_images,additional_image_labels,hide_from_product_page,custom_options,bundle_price_type,bundle_sku_type,bundle_price_view,bundle_weight_type,bundle_values,associated_skus +simple_new,,Default,simple,,base,New Product,,,,1,Taxable Goods,"Catalog, Search",10,,,,new-product,New Product,New Product,New Product ,magento_image_2.jpg,Image Label,magento_small_image_2.jpg,Small Image Label,magento_thumbnail.jpg,Thumbnail Label,magento_image.jpg,Image Label,10/20/15 07:05,10/20/15 07:05,,,Block after Info Column,,,,,,,,,,,,,"has_options=1,quantity_and_stock_status=In Stock,required_options=1",100,0,1,0,0,1,1,1,10000,1,1,1,1,1,0,1,1,0,0,0,1,,,,,,,,,,,,, diff --git a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/import_with_filesystem_images.php b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/import_with_filesystem_images.php index 0ee59aedd8979..d426a1521e5b6 100644 --- a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/import_with_filesystem_images.php +++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/import_with_filesystem_images.php @@ -29,6 +29,10 @@ 'source' => __DIR__ . '/../../../../../Magento/Catalog/_files/magento_image.jpg', 'dest' => $dirPath . '/magento_image.jpg', ], + [ + 'source' => __DIR__ . '/../../../../../Magento/Catalog/_files/magento_image_2.jpg', + 'dest' => $dirPath . '/magento_image_2.jpg', + ], [ 'source' => __DIR__ . '/../../../../../Magento/Catalog/_files/magento_small_image.jpg', 'dest' => $dirPath . '/magento_small_image.jpg', From eb8607c5ab62f10e519e20810da60a2755c34d58 Mon Sep 17 00:00:00 2001 From: Pieter Cappelle Date: Wed, 5 Feb 2020 16:11:33 +0100 Subject: [PATCH 3/8] First try-out to fix static tests / integration test --- .../Model/Import/Product.php | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product.php b/app/code/Magento/CatalogImportExport/Model/Import/Product.php index 58ae970e93f82..c66d465247892 100644 --- a/app/code/Magento/CatalogImportExport/Model/Import/Product.php +++ b/app/code/Magento/CatalogImportExport/Model/Import/Product.php @@ -1776,7 +1776,9 @@ function ($exists, $file) use ($hash) { if ($exists) { return $exists; } - if ($file['hash'] === $hash) { + if (isset($file['hash']) && + !empty($file['hash']) && + $file['hash'] === $hash) { return $file['value']; } return $exists; @@ -1860,12 +1862,9 @@ function ($exists, $file) use ($hash) { // 5.1 Items to remove phase if (!empty($rowExistingImages)) { - $galleryItemsToRemove = \array_merge( - $galleryItemsToRemove, - \array_diff( - \array_keys($rowExistingImages), - $uploadedFiles - ) + $galleryItemsToRemove[] = \array_diff( + \array_keys($rowExistingImages), + $uploadedFiles ); } @@ -2012,7 +2011,9 @@ public function addImageHashes(&$images) foreach ($images as $storeId => $skus) { foreach ($skus as $sku => $files) { foreach ($files as $path => $file) { - $images[$storeId][$sku][$path]['hash'] = hash_file('sha256', $productMediaPath . $file['value']); + if (file_exists($productMediaPath . $file['value'])) { + $images[$storeId][$sku][$path]['hash'] = hash_file('sha256', $productMediaPath . $file['value']); + } } } } @@ -2283,6 +2284,12 @@ protected function _removeOldMediaGalleryItems(array $itemsToRemove) if (empty($itemsToRemove)) { return $this; } + + $itemsToRemove = array_merge(...$itemsToRemove); + if (empty($itemsToRemove)) { + return $this; + } + $this->mediaProcessor->removeOldMediaItems($itemsToRemove); return $this; From e4812b842f300af4f45dd9d6ed3a6faaa2143a38 Mon Sep 17 00:00:00 2001 From: Pieter Cappelle Date: Tue, 25 Feb 2020 08:53:07 +0100 Subject: [PATCH 4/8] Fix unit tests --- .../Model/Import/Product.php | 28 ++++++----- .../Model/Import/ProductTest.php | 47 ++++++++++++------- 2 files changed, 46 insertions(+), 29 deletions(-) diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product.php b/app/code/Magento/CatalogImportExport/Model/Import/Product.php index c66d465247892..6be3141a31db7 100644 --- a/app/code/Magento/CatalogImportExport/Model/Import/Product.php +++ b/app/code/Magento/CatalogImportExport/Model/Import/Product.php @@ -1755,12 +1755,11 @@ protected function _saveProducts() $position = 0; foreach ($rowImages as $column => $columnImages) { foreach ($columnImages as $columnImageKey => $columnImage) { + $hash = ''; if (filter_var($columnImage, FILTER_VALIDATE_URL) === false) { $filename = $importDir . DIRECTORY_SEPARATOR . $columnImage; if (file_exists($filename)) { $hash = hash_file('sha256', $importDir . DIRECTORY_SEPARATOR . $columnImage); - } else { - $hash = hash_file('sha256', $filename); } } else { $hash = hash_file('sha256', $columnImage); @@ -1824,22 +1823,27 @@ function ($exists, $file) use ($hash) { if (isset($rowExistingImages[$uploadedFile])) { $currentFileData = $rowExistingImages[$uploadedFile]; + $currentFileData['store_id'] = $storeId; + $storeMediaGalleryValueExists = isset($rowStoreMediaGalleryValues[$uploadedFile]); + if (array_key_exists($uploadedFile, $imageHiddenStates) + && $currentFileData['disabled'] != $imageHiddenStates[$uploadedFile] + ) { + $imagesForChangeVisibility[] = [ + 'disabled' => $imageHiddenStates[$uploadedFile], + 'imageData' => $currentFileData, + 'exists' => $storeMediaGalleryValueExists + ]; + $storeMediaGalleryValueExists = true; + } + if (isset($rowLabels[$column][$columnImageKey]) && $rowLabels[$column][$columnImageKey] != $currentFileData['label'] ) { $labelsForUpdate[] = [ 'label' => $rowLabels[$column][$columnImageKey], - 'imageData' => $currentFileData - ]; - } - - if (array_key_exists($uploadedFile, $imageHiddenStates) - && $currentFileData['disabled'] != $imageHiddenStates[$uploadedFile] - ) { - $imagesForChangeVisibility[] = [ - 'disabled' => $imageHiddenStates[$uploadedFile], - 'imageData' => $currentFileData + 'imageData' => $currentFileData, + 'exists' => $storeMediaGalleryValueExists ]; } } else { diff --git a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest.php b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest.php index 47e02b08783ca..23d62589a1c37 100644 --- a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest.php +++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest.php @@ -877,23 +877,6 @@ public function testSaveMediaImage() $this->assertInstanceOf(\Magento\Framework\DataObject::class, $additionalImageTwoItem); $this->assertEquals('/m/a/magento_additional_image_two.jpg', $additionalImageTwoItem->getFile()); $this->assertEquals('Additional Image Label Two', $additionalImageTwoItem->getLabel()); - - // Will check that existing product update works - // New unique images as per MD5 should be added, images not mentioned in the import should be removed - $this->importDataForMediaTest('import_media_update_images.csv'); - - $product = $this->getProductBySku('simple_new'); - $this->assertEquals('/m/a/magento_image_2.jpg', $product->getData('image')); - // small_image should be skipped from update as it is a duplicate (md5 is the same) - $this->assertEquals('/m/a/magento_small_image.jpg', $product->getData('small_image')); - $this->assertEquals('/m/a/magento_thumbnail.jpg', $product->getData('thumbnail')); - $this->assertEquals('/m/a/magento_image.jpg', $product->getData('swatch_image')); - - $gallery = $product->getMediaGalleryImages(); - $this->assertInstanceOf(\Magento\Framework\Data\Collection::class, $gallery); - - $items = $gallery->getItems(); - $this->assertCount(4, $items); } /** @@ -979,6 +962,36 @@ function (\Magento\Framework\DataObject $item) { ); } + /** + * Test that product import with images works properly + * + * @magentoDataFixture mediaImportImageFixture + * @magentoAppIsolation enabled + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + */ + public function testSaveMediaImageDuplicateImages() + { + // Will check that existing product update works + // New unique images as per MD5 should be added, images not mentioned in the import should be removed + $this->importDataForMediaTest('import_media_update_images.csv'); + + $product = $this->getProductBySku('simple_new'); + + $gallery = $product->getMediaGalleryImages(); + $this->assertEquals('/m/a/magento_image.jpg', $product->getData('image')); + + // small_image should be skipped from update as it is a duplicate (md5 is the same) + $this->assertEquals('/m/a/magento_small_image.jpg', $product->getData('small_image')); + $this->assertEquals('/m/a/magento_thumbnail.jpg', $product->getData('thumbnail')); + $this->assertEquals('/m/a/magento_image.jpg', $product->getData('swatch_image')); + + $gallery = $product->getMediaGalleryImages(); + $this->assertInstanceOf(\Magento\Framework\Data\Collection::class, $gallery); + + $items = $gallery->getItems(); + $this->assertCount(4, $items); + } + /** * Test that errors occurred during importing images are logged. * From d4931f79648bfd1677a861257697d5f8a57cecd6 Mon Sep 17 00:00:00 2001 From: "vadim.malesh" Date: Mon, 14 Sep 2020 11:45:49 +0300 Subject: [PATCH 5/8] fix duplicate image during import products, refactor tests --- .../Model/Import/Product.php | 136 +++++++----------- .../Import/Product/MediaGalleryProcessor.php | 20 --- .../Catalog/_files/magento_image_2.jpg | Bin 12137 -> 0 bytes .../Model/Import/ProductTest.php | 46 +++--- ...mport_media_additional_long_name_image.csv | 2 + .../_files/import_media_update_images.csv | 2 - .../_files/import_with_filesystem_images.php | 4 - 7 files changed, 75 insertions(+), 135 deletions(-) delete mode 100644 dev/tests/integration/testsuite/Magento/Catalog/_files/magento_image_2.jpg create mode 100644 dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/import_media_additional_long_name_image.csv delete mode 100644 dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/import_media_update_images.csv diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product.php b/app/code/Magento/CatalogImportExport/Model/Import/Product.php index b11f1951b49cb..4384cec88bc46 100644 --- a/app/code/Magento/CatalogImportExport/Model/Import/Product.php +++ b/app/code/Magento/CatalogImportExport/Model/Import/Product.php @@ -23,6 +23,7 @@ use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Exception\NoSuchEntityException; use Magento\Framework\Filesystem; +use Magento\Framework\Filesystem\Driver\File; use Magento\Framework\Intl\DateTimeFactory; use Magento\Framework\Model\ResourceModel\Db\ObjectRelationProcessor; use Magento\Framework\Model\ResourceModel\Db\TransactionManagerInterface; @@ -766,6 +767,11 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity */ private $linkProcessor; + /** + * @var File + */ + private $fileDriver; + /** * @param \Magento\Framework\Json\Helper\Data $jsonHelper * @param \Magento\ImportExport\Helper\Data $importExportData @@ -814,6 +820,7 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity * @param StatusProcessor|null $statusProcessor * @param StockProcessor|null $stockProcessor * @param LinkProcessor|null $linkProcessor + * @param File|null $fileDriver * @throws LocalizedException * @throws \Magento\Framework\Exception\FileSystemException * @SuppressWarnings(PHPMD.ExcessiveParameterList) @@ -866,7 +873,8 @@ public function __construct( ProductRepositoryInterface $productRepository = null, StatusProcessor $statusProcessor = null, StockProcessor $stockProcessor = null, - LinkProcessor $linkProcessor = null + LinkProcessor $linkProcessor = null, + ?File $fileDriver = null ) { $this->_eventManager = $eventManager; $this->stockRegistry = $stockRegistry; @@ -930,6 +938,7 @@ public function __construct( $this->dateTimeFactory = $dateTimeFactory ?? ObjectManager::getInstance()->get(DateTimeFactory::class); $this->productRepository = $productRepository ?? ObjectManager::getInstance() ->get(ProductRepositoryInterface::class); + $this->fileDriver = $fileDriver ?: ObjectManager::getInstance()->get(File::class); } /** @@ -1148,6 +1157,7 @@ protected function _replaceProducts() * Save products data. * * @return $this + * @throws LocalizedException */ protected function _saveProductsData() { @@ -1155,7 +1165,7 @@ protected function _saveProductsData() foreach ($this->_productTypeModels as $productTypeModel) { $productTypeModel->saveData(); } - $this->_saveLinks(); + $this->linkProcessor->saveLinks($this, $this->_dataSourceModel, $this->getProductEntityLinkField()); $this->_saveStockItem(); if ($this->_replaceFlag) { $this->getOptionEntity()->clearProductsSkuToId(); @@ -1563,15 +1573,13 @@ protected function _saveProducts() $this->categoriesCache = []; $tierPrices = []; $mediaGallery = []; - $uploadedFiles = []; - $galleryItemsToRemove = []; $labelsForUpdate = []; $imagesForChangeVisibility = []; $uploadedImages = []; $previousType = null; $prevAttributeSet = null; - $importDir = $this->_mediaDirectory->getAbsolutePath($this->getImportDir()); + $importDir = $this->_mediaDirectory->getAbsolutePath($this->getUploader()->getTmpDir()); $existingImages = $this->getExistingImages($bunch); $this->addImageHashes($existingImages); @@ -1721,11 +1729,12 @@ protected function _saveProducts() foreach (array_keys($imageHiddenStates) as $image) { //Mark image as uploaded if it exists if (array_key_exists($image, $rowExistingImages)) { - $rowImages[self::COL_MEDIA_IMAGE][] = $image; $uploadedImages[$image] = $image; } - - if (empty($rowImages)) { + //Add image to hide to images list if it does not exist + if (empty($rowImages[self::COL_MEDIA_IMAGE]) + || !in_array($image, $rowImages[self::COL_MEDIA_IMAGE]) + ) { $rowImages[self::COL_MEDIA_IMAGE][] = $image; } } @@ -1743,8 +1752,11 @@ protected function _saveProducts() $hash = ''; if (filter_var($columnImage, FILTER_VALIDATE_URL) === false) { $filename = $importDir . DIRECTORY_SEPARATOR . $columnImage; - if (file_exists($filename)) { - $hash = hash_file('sha256', $importDir . DIRECTORY_SEPARATOR . $columnImage); + if ($this->fileDriver->isExists($filename)) { + $hash = hash_file( + 'sha256', + $importDir . DIRECTORY_SEPARATOR . $columnImage + ); } } else { $hash = hash_file('sha256', $columnImage); @@ -1760,11 +1772,11 @@ function ($exists, $file) use ($hash) { if ($exists) { return $exists; } - if (isset($file['hash']) && - !empty($file['hash']) && - $file['hash'] === $hash) { + + if (isset($file['hash']) && $file['hash'] === $hash) { return $file['value']; } + return $exists; }, '' @@ -1773,35 +1785,29 @@ function ($exists, $file) use ($hash) { if ($imageAlreadyExists) { $uploadedFile = $imageAlreadyExists; - } else { - if (!isset($uploadedImages[$columnImage])) { - $uploadedFile = $this->uploadMediaFiles($columnImage); - $uploadedFile = $uploadedFile ?: $this->getSystemFile($columnImage); - if ($uploadedFile) { - $uploadedImages[$columnImage] = $uploadedFile; - } else { - unset($rowData[$column]); - $this->addRowError( - ValidatorInterface::ERROR_MEDIA_URL_NOT_ACCESSIBLE, - $rowNum, - null, - null, - ProcessingError::ERROR_LEVEL_NOT_CRITICAL - ); - } + } elseif (!isset($uploadedImages[$columnImage])) { + $uploadedFile = $this->uploadMediaFiles($columnImage); + $uploadedFile = $uploadedFile ?: $this->getSystemFile($columnImage); + if ($uploadedFile) { + $uploadedImages[$columnImage] = $uploadedFile; } else { - $uploadedFile = $uploadedImages[$columnImage]; + unset($rowData[$column]); + $this->addRowError( + ValidatorInterface::ERROR_MEDIA_URL_NOT_ACCESSIBLE, + $rowNum, + null, + null, + ProcessingError::ERROR_LEVEL_NOT_CRITICAL + ); } + } else { + $uploadedFile = $uploadedImages[$columnImage]; } if ($uploadedFile && $column !== self::COL_MEDIA_IMAGE) { $rowData[$column] = $uploadedFile; } - if ($uploadedFile) { - $uploadedFiles[] = $uploadedFile; - } - if (!$uploadedFile || isset($mediaGallery[$storeId][$rowSku][$uploadedFile])) { continue; } @@ -1823,8 +1829,7 @@ function ($exists, $file) use ($hash) { } if (isset($rowLabels[$column][$columnImageKey]) - && $rowLabels[$column][$columnImageKey] != - $currentFileData['label'] + && $rowLabels[$column][$columnImageKey] !== $currentFileData['label'] ) { $labelsForUpdate[] = [ 'label' => $rowLabels[$column][$columnImageKey], @@ -1833,7 +1838,7 @@ function ($exists, $file) use ($hash) { ]; } } else { - if ($column == self::COL_MEDIA_IMAGE) { + if ($column === self::COL_MEDIA_IMAGE) { $rowData[$column][] = $uploadedFile; } $mediaGallery[$storeId][$rowSku][$uploadedFile] = [ @@ -1850,14 +1855,6 @@ function ($exists, $file) use ($hash) { } } - // 5.1 Items to remove phase - if (!empty($rowExistingImages)) { - $galleryItemsToRemove[] = \array_diff( - \array_keys($rowExistingImages), - $uploadedFiles - ); - } - // 6. Attributes phase $rowStore = (self::SCOPE_STORE == $rowScope) ? $this->storeResolver->getStoreCodeToId($rowData[self::COL_STORE]) @@ -1968,8 +1965,6 @@ function ($exists, $file) use ($hash) { $tierPrices )->_saveMediaGallery( $mediaGallery - )->_removeOldMediaGalleryItems( - $galleryItemsToRemove )->_saveProductAttributes( $attributes )->updateMediaGalleryVisibility( @@ -1988,29 +1983,31 @@ function ($exists, $file) use ($hash) { } //phpcs:enable Generic.Metrics.NestingLevel + // phpcs:enable + /** * Generate hashes for existing images for comparison with newly uploaded images. * * @param array $images + * @return void */ - public function addImageHashes(&$images) + private function addImageHashes(array &$images): void { $productMediaPath = $this->filesystem->getDirectoryRead(DirectoryList::MEDIA) - ->getAbsolutePath('/catalog/product'); + ->getAbsolutePath(DS . 'catalog' . DS . 'product'); foreach ($images as $storeId => $skus) { foreach ($skus as $sku => $files) { foreach ($files as $path => $file) { - if (file_exists($productMediaPath . $file['value'])) { - $images[$storeId][$sku][$path]['hash'] = hash_file('sha256', $productMediaPath . $file['value']); + if ($this->fileDriver->isExists($productMediaPath . $file['value'])) { + $fileName = $productMediaPath . $file['value']; + $images[$storeId][$sku][$path]['hash'] = hash_file('sha256', $fileName); } } } } } - // phpcs:enable - /** * Clears entries from Image Set and Row Data marked as no_selection * @@ -2172,17 +2169,14 @@ protected function _saveProductTierPrices(array $tierPriceData) * * @return string */ - protected function getImportDir() + private function getImportDir(): string { $dirConfig = DirectoryList::getDefaultConfig(); $dirAddon = $dirConfig[DirectoryList::MEDIA][DirectoryList::PATH]; - if (!empty($this->_parameters[Import::FIELD_NAME_IMG_FILE_DIR])) { - $tmpPath = $this->_parameters[Import::FIELD_NAME_IMG_FILE_DIR]; - } else { - $tmpPath = $dirAddon . '/' . $this->_mediaDirectory->getRelativePath('import'); - } - return $tmpPath; + return empty($this->_parameters[Import::FIELD_NAME_IMG_FILE_DIR]) + ? $dirAddon . DS . $this->_mediaDirectory->getRelativePath('import') + : $this->_parameters[Import::FIELD_NAME_IMG_FILE_DIR]; } /** @@ -2286,28 +2280,6 @@ protected function _saveMediaGallery(array $mediaGalleryData) return $this; } - /** - * Remove old media gallery items. - * - * @param array $itemsToRemove - * @return $this - */ - protected function _removeOldMediaGalleryItems(array $itemsToRemove) - { - if (empty($itemsToRemove)) { - return $this; - } - - $itemsToRemove = array_merge(...$itemsToRemove); - if (empty($itemsToRemove)) { - return $this; - } - - $this->mediaProcessor->removeOldMediaItems($itemsToRemove); - - return $this; - } - /** * Save product websites. * diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product/MediaGalleryProcessor.php b/app/code/Magento/CatalogImportExport/Model/Import/Product/MediaGalleryProcessor.php index a608d792ff6fb..d4694b72ba64f 100644 --- a/app/code/Magento/CatalogImportExport/Model/Import/Product/MediaGalleryProcessor.php +++ b/app/code/Magento/CatalogImportExport/Model/Import/Product/MediaGalleryProcessor.php @@ -104,7 +104,6 @@ public function __construct( * * @param array $mediaGalleryData * @return void - * @throws \Exception */ public function saveMediaGallery(array $mediaGalleryData) { @@ -271,7 +270,6 @@ private function prepareMediaGalleryValueData( * * @param array $labels * @return void - * @throws \Exception */ public function updateMediaGalleryLabels(array $labels) { @@ -283,7 +281,6 @@ public function updateMediaGalleryLabels(array $labels) * * @param array $images * @return void - * @throws \Exception */ public function updateMediaGalleryVisibility(array $images) { @@ -296,7 +293,6 @@ public function updateMediaGalleryVisibility(array $images) * @param array $data * @param string $field * @return void - * @throws \Exception */ private function updateMediaGalleryField(array $data, $field) { @@ -341,7 +337,6 @@ private function updateMediaGalleryField(array $data, $field) * * @param array $bunch * @return array - * @throws \Exception */ public function getExistingImages(array $bunch) { @@ -451,25 +446,10 @@ private function getLastMediaPositionPerProduct(array $productIds): array return $result; } - /** - * Remove old media gallery items. - * - * @param array $oldMediaValues - * @return void - */ - public function removeOldMediaItems(array $oldMediaValues) - { - $this->connection->delete( - $this->mediaGalleryTableName, - $this->connection->quoteInto('value IN (?)', $oldMediaValues) - ); - } - /** * Get product entity link field. * * @return string - * @throws \Exception */ private function getProductEntityLinkField() { diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/magento_image_2.jpg b/dev/tests/integration/testsuite/Magento/Catalog/_files/magento_image_2.jpg deleted file mode 100644 index 2c21e0238ede73faf93b6ae9641a2f649f537f55..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12137 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 diff --git a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest.php b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest.php index 7368d755036de..4c4f2138abf35 100644 --- a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest.php +++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest.php @@ -34,6 +34,7 @@ use Magento\Store\Model\Store; use Magento\Store\Model\StoreManagerInterface; use Magento\TestFramework\Helper\Bootstrap as BootstrapHelper; +use Magento\TestFramework\Indexer\TestCase; use Magento\UrlRewrite\Model\ResourceModel\UrlRewriteCollection; use Psr\Log\LoggerInterface; @@ -50,8 +51,10 @@ * @SuppressWarnings(PHPMD.ExcessivePublicCount) * phpcs:disable Generic.PHP.NoSilencedErrors, Generic.Metrics.NestingLevel, Magento2.Functions.StaticFunction */ -class ProductTest extends \Magento\TestFramework\Indexer\TestCase +class ProductTest extends TestCase { + private const LONG_FILE_NAME_IMAGE = 'magento_long_image_name_magento_long_image_name_magento_long_image_name.jpg'; + /** * @var \Magento\CatalogImportExport\Model\Import\Product */ @@ -602,7 +605,6 @@ private function createImportModel($pathToFile, $behavior = \Magento\ImportExpor /** * @param string $productSku * @return array ['optionId' => ['optionValueId' => 'optionValueTitle', ...], ...] - * @throws \Magento\Framework\Exception\NoSuchEntityException */ private function getCustomOptionValues($productSku) { @@ -1030,13 +1032,12 @@ function (\Magento\Framework\DataObject $item) { ) ); - $this->importDataForMediaTest('import_media_additional_images.csv'); + $this->importDataForMediaTest('import_media_additional_long_name_image.csv'); $product->cleanModelCache(); $product = $this->getProductBySku('simple_new'); $items = array_values($product->getMediaGalleryImages()->getItems()); - $images[] = ['file' => '/m/a/magento_additional_image_three.jpg', 'label' => '']; - $images[] = ['file' => '/m/a/magento_additional_image_four.jpg', 'label' => '']; - $this->assertCount(7, $items); + $images[] = ['file' => '/m/a/' . self::LONG_FILE_NAME_IMAGE, 'label' => '']; + $this->assertCount(6, $items); $this->assertEquals( $images, array_map( @@ -1049,33 +1050,20 @@ function (\Magento\Framework\DataObject $item) { } /** - * Test that product import with images works properly + * Test import twice and check that image will not be duplicate * * @magentoDataFixture mediaImportImageFixture - * @magentoAppIsolation enabled - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + * @return void */ - public function testSaveMediaImageDuplicateImages() + public function testSaveMediaImageDuplicateImages(): void { - // Will check that existing product update works - // New unique images as per MD5 should be added, images not mentioned in the import should be removed - $this->importDataForMediaTest('import_media_update_images.csv'); - - $product = $this->getProductBySku('simple_new'); - - $gallery = $product->getMediaGalleryImages(); - $this->assertEquals('/m/a/magento_image.jpg', $product->getData('image')); - - // small_image should be skipped from update as it is a duplicate (md5 is the same) - $this->assertEquals('/m/a/magento_small_image.jpg', $product->getData('small_image')); - $this->assertEquals('/m/a/magento_thumbnail.jpg', $product->getData('thumbnail')); - $this->assertEquals('/m/a/magento_image.jpg', $product->getData('swatch_image')); + $this->importDataForMediaTest('import_media.csv'); + $imagesCount = count($this->getProductBySku('simple_new')->getMediaGalleryImages()->getItems()); - $gallery = $product->getMediaGalleryImages(); - $this->assertInstanceOf(\Magento\Framework\Data\Collection::class, $gallery); + // import the same file again + $this->importDataForMediaTest('import_media.csv'); - $items = $gallery->getItems(); - $this->assertCount(4, $items); + $this->assertCount($imagesCount, $this->getProductBySku('simple_new')->getMediaGalleryImages()->getItems()); } /** @@ -1120,6 +1108,10 @@ public static function mediaImportImageFixture() 'source' => __DIR__ . '/../../../../Magento/Catalog/_files/magento_thumbnail.jpg', 'dest' => $dirPath . '/magento_thumbnail.jpg', ], + [ + 'source' => __DIR__ . '/../../../../Magento/Catalog/_files/' . self::LONG_FILE_NAME_IMAGE, + 'dest' => $dirPath . '/' . self::LONG_FILE_NAME_IMAGE, + ], [ 'source' => __DIR__ . '/_files/magento_additional_image_one.jpg', 'dest' => $dirPath . '/magento_additional_image_one.jpg', diff --git a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/import_media_additional_long_name_image.csv b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/import_media_additional_long_name_image.csv new file mode 100644 index 0000000000000..2d2a192ed6c7c --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/import_media_additional_long_name_image.csv @@ -0,0 +1,2 @@ +sku,additional_images +simple_new,magento_long_image_name_magento_long_image_name_magento_long_image_name.jpg diff --git a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/import_media_update_images.csv b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/import_media_update_images.csv deleted file mode 100644 index 56dd5b4e977bf..0000000000000 --- a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/import_media_update_images.csv +++ /dev/null @@ -1,2 +0,0 @@ -sku,store_view_code,attribute_set_code,product_type,categories,product_websites,name,description,short_description,weight,product_online,tax_class_name,visibility,price,special_price,special_price_from_date,special_price_to_date,url_key,meta_title,meta_keywords,meta_description,base_image,base_image_label,small_image,small_image_label,thumbnail_image,thumbnail_image_label,swatch_image,swatch_image_label1,created_at,updated_at,new_from_date,new_to_date,display_product_options_in,map_price,msrp_price,map_enabled,gift_message_available,custom_design,custom_design_from,custom_design_to,custom_layout_update,page_layout,product_options_container,msrp_display_actual_price_type,country_of_manufacture,additional_attributes,qty,out_of_stock_qty,use_config_min_qty,is_qty_decimal,allow_backorders,use_config_backorders,min_cart_qty,use_config_min_sale_qty,max_cart_qty,use_config_max_sale_qty,is_in_stock,notify_on_stock_below,use_config_notify_stock_qty,manage_stock,use_config_manage_stock,use_config_qty_increments,qty_increments,use_config_enable_qty_inc,enable_qty_increments,is_decimal_divided,website_id,related_skus,crosssell_skus,upsell_skus,additional_images,additional_image_labels,hide_from_product_page,custom_options,bundle_price_type,bundle_sku_type,bundle_price_view,bundle_weight_type,bundle_values,associated_skus -simple_new,,Default,simple,,base,New Product,,,,1,Taxable Goods,"Catalog, Search",10,,,,new-product,New Product,New Product,New Product ,magento_image_2.jpg,Image Label,magento_small_image_2.jpg,Small Image Label,magento_thumbnail.jpg,Thumbnail Label,magento_image.jpg,Image Label,10/20/15 07:05,10/20/15 07:05,,,Block after Info Column,,,,,,,,,,,,,"has_options=1,quantity_and_stock_status=In Stock,required_options=1",100,0,1,0,0,1,1,1,10000,1,1,1,1,1,0,1,1,0,0,0,1,,,,,,,,,,,,, diff --git a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/import_with_filesystem_images.php b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/import_with_filesystem_images.php index d426a1521e5b6..0ee59aedd8979 100644 --- a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/import_with_filesystem_images.php +++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/import_with_filesystem_images.php @@ -29,10 +29,6 @@ 'source' => __DIR__ . '/../../../../../Magento/Catalog/_files/magento_image.jpg', 'dest' => $dirPath . '/magento_image.jpg', ], - [ - 'source' => __DIR__ . '/../../../../../Magento/Catalog/_files/magento_image_2.jpg', - 'dest' => $dirPath . '/magento_image_2.jpg', - ], [ 'source' => __DIR__ . '/../../../../../Magento/Catalog/_files/magento_small_image.jpg', 'dest' => $dirPath . '/magento_small_image.jpg', From 819fc7cfef122559826edbfd17ce397527461ae1 Mon Sep 17 00:00:00 2001 From: "vadim.malesh" Date: Tue, 22 Sep 2020 16:56:13 +0300 Subject: [PATCH 6/8] refactor --- .../Model/Import/Product.php | 126 +++++++++--------- 1 file changed, 65 insertions(+), 61 deletions(-) diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product.php b/app/code/Magento/CatalogImportExport/Model/Import/Product.php index 4384cec88bc46..e249e5b3722e7 100644 --- a/app/code/Magento/CatalogImportExport/Model/Import/Product.php +++ b/app/code/Magento/CatalogImportExport/Model/Import/Product.php @@ -45,9 +45,10 @@ * @SuppressWarnings(PHPMD.ExcessivePublicCount) * @since 100.0.2 */ -class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity +class Product extends AbstractEntity { - const CONFIG_KEY_PRODUCT_TYPES = 'global/importexport/import_product_types'; + public const CONFIG_KEY_PRODUCT_TYPES = 'global/importexport/import_product_types'; + private const HASH_ALGORITHM = 'sha256'; /** * Size of bunch - part of products to save in one step. @@ -1749,43 +1750,12 @@ protected function _saveProducts() $position = 0; foreach ($rowImages as $column => $columnImages) { foreach ($columnImages as $columnImageKey => $columnImage) { - $hash = ''; - if (filter_var($columnImage, FILTER_VALIDATE_URL) === false) { - $filename = $importDir . DIRECTORY_SEPARATOR . $columnImage; - if ($this->fileDriver->isExists($filename)) { - $hash = hash_file( - 'sha256', - $importDir . DIRECTORY_SEPARATOR . $columnImage - ); - } - } else { - $hash = hash_file('sha256', $columnImage); - } + $filePath = filter_var($columnImage, FILTER_VALIDATE_URL) + ? $columnImage + : $importDir . DS . $columnImage; - // Add new images - if (empty($rowExistingImages)) { - $imageAlreadyExists = false; - } else { - $imageAlreadyExists = array_reduce( - $rowExistingImages, - function ($exists, $file) use ($hash) { - if ($exists) { - return $exists; - } - - if (isset($file['hash']) && $file['hash'] === $hash) { - return $file['value']; - } - - return $exists; - }, - '' - ); - } - - if ($imageAlreadyExists) { - $uploadedFile = $imageAlreadyExists; - } elseif (!isset($uploadedImages[$columnImage])) { + $uploadedFile = $this->getAlreadyExistedImage($rowExistingImages, $filePath); + if (!$uploadedFile && !isset($uploadedImages[$columnImage])) { $uploadedFile = $this->uploadMediaFiles($columnImage); $uploadedFile = $uploadedFile ?: $this->getSystemFile($columnImage); if ($uploadedFile) { @@ -1800,7 +1770,7 @@ function ($exists, $file) use ($hash) { ProcessingError::ERROR_LEVEL_NOT_CRITICAL ); } - } else { + } elseif (isset($uploadedImages[$columnImage])) { $uploadedFile = $uploadedImages[$columnImage]; } @@ -1954,24 +1924,14 @@ function ($exists, $file) use ($hash) { } } - $this->saveProductEntity( - $entityRowsIn, - $entityRowsUp - )->_saveProductWebsites( - $this->websitesCache - )->_saveProductCategories( - $this->categoriesCache - )->_saveProductTierPrices( - $tierPrices - )->_saveMediaGallery( - $mediaGallery - )->_saveProductAttributes( - $attributes - )->updateMediaGalleryVisibility( - $imagesForChangeVisibility - )->updateMediaGalleryLabels( - $labelsForUpdate - ); + $this->saveProductEntity($entityRowsIn, $entityRowsUp) + ->_saveProductWebsites($this->websitesCache) + ->_saveProductCategories($this->categoriesCache) + ->_saveProductTierPrices($tierPrices) + ->_saveMediaGallery($mediaGallery) + ->_saveProductAttributes($attributes) + ->updateMediaGalleryVisibility($imagesForChangeVisibility) + ->updateMediaGalleryLabels($labelsForUpdate); $this->_eventManager->dispatch( 'catalog_product_import_bunch_save_after', @@ -1985,6 +1945,51 @@ function ($exists, $file) use ($hash) { // phpcs:enable + /** + * Returns image hash by path + * + * @param string $filePath + * @return string + */ + private function getFileHash(string $filePath): string + { + try { + $fileExists = $this->fileDriver->isExists($filePath); + } catch (\Exception $exception) { + $fileExists = false; + } + + return $fileExists ? hash_file(self::HASH_ALGORITHM, $filePath) : ''; + } + + /** + * Returns existed image + * + * @param array $imageRow + * @param string $filePath + * @return string + */ + private function getAlreadyExistedImage(array $imageRow, string $filePath): string + { + $hash = $this->getFileHash($filePath); + + return array_reduce( + $imageRow, + function ($exists, $file) use ($hash) { + if ($exists) { + return $exists; + } + + if (isset($file['hash']) && $file['hash'] === $hash) { + return $file['value']; + } + + return $exists; + }, + '' + ); + } + /** * Generate hashes for existing images for comparison with newly uploaded images. * @@ -2001,7 +2006,7 @@ private function addImageHashes(array &$images): void foreach ($files as $path => $file) { if ($this->fileDriver->isExists($productMediaPath . $file['value'])) { $fileName = $productMediaPath . $file['value']; - $images[$storeId][$sku][$path]['hash'] = hash_file('sha256', $fileName); + $images[$storeId][$sku][$path]['hash'] = $this->getFileHash($fileName); } } } @@ -2019,9 +2024,8 @@ private function clearNoSelectionImages($rowImages, $rowData) { foreach ($rowImages as $column => $columnImages) { foreach ($columnImages as $key => $image) { - if ($image == 'no_selection') { - unset($rowImages[$column][$key]); - unset($rowData[$column]); + if ($image === 'no_selection') { + unset($rowImages[$column][$key], $rowData[$column]); } } } From bca6bf6db9f3eeca2f10c753c34a23b568307bdf Mon Sep 17 00:00:00 2001 From: "vadim.malesh" Date: Thu, 24 Sep 2020 11:36:14 +0300 Subject: [PATCH 7/8] fix import image from external url --- .../Model/Import/Product.php | 47 ++++++++++++------- 1 file changed, 30 insertions(+), 17 deletions(-) diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product.php b/app/code/Magento/CatalogImportExport/Model/Import/Product.php index e249e5b3722e7..3e72824674773 100644 --- a/app/code/Magento/CatalogImportExport/Model/Import/Product.php +++ b/app/code/Magento/CatalogImportExport/Model/Import/Product.php @@ -1750,11 +1750,7 @@ protected function _saveProducts() $position = 0; foreach ($rowImages as $column => $columnImages) { foreach ($columnImages as $columnImageKey => $columnImage) { - $filePath = filter_var($columnImage, FILTER_VALIDATE_URL) - ? $columnImage - : $importDir . DS . $columnImage; - - $uploadedFile = $this->getAlreadyExistedImage($rowExistingImages, $filePath); + $uploadedFile = $this->getAlreadyExistedImage($rowExistingImages, $columnImage, $importDir); if (!$uploadedFile && !isset($uploadedImages[$columnImage])) { $uploadedFile = $this->uploadMediaFiles($columnImage); $uploadedFile = $uploadedFile ?: $this->getSystemFile($columnImage); @@ -1948,30 +1944,30 @@ protected function _saveProducts() /** * Returns image hash by path * - * @param string $filePath + * @param string $path * @return string */ - private function getFileHash(string $filePath): string + private function getFileHash(string $path): string { - try { - $fileExists = $this->fileDriver->isExists($filePath); - } catch (\Exception $exception) { - $fileExists = false; - } - - return $fileExists ? hash_file(self::HASH_ALGORITHM, $filePath) : ''; + return hash_file(self::HASH_ALGORITHM, $path); } /** * Returns existed image * * @param array $imageRow - * @param string $filePath + * @param string $columnImage + * @param string $importDir * @return string */ - private function getAlreadyExistedImage(array $imageRow, string $filePath): string + private function getAlreadyExistedImage(array $imageRow, string $columnImage, string $importDir): string { - $hash = $this->getFileHash($filePath); + if (filter_var($columnImage, FILTER_VALIDATE_URL)) { + $hash = $this->getFileHash($columnImage); + } else { + $path = $importDir . DS . $columnImage; + $hash = $this->isFileExists($path) ? $this->getFileHash($path) : ''; + } return array_reduce( $imageRow, @@ -2013,6 +2009,23 @@ private function addImageHashes(array &$images): void } } + /** + * Is file exists + * + * @param string $path + * @return bool + */ + private function isFileExists(string $path): bool + { + try { + $fileExists = $this->fileDriver->isExists($path); + } catch (\Exception $exception) { + $fileExists = false; + } + + return $fileExists; + } + /** * Clears entries from Image Set and Row Data marked as no_selection * From 3da6f23f4fb20553580431bdcdbb35afb4551d7f Mon Sep 17 00:00:00 2001 From: "vadim.malesh" Date: Fri, 25 Sep 2020 10:24:25 +0300 Subject: [PATCH 8/8] refactor --- .../Magento/CatalogImportExport/Model/Import/Product.php | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product.php b/app/code/Magento/CatalogImportExport/Model/Import/Product.php index 3e72824674773..0a030e2d65a05 100644 --- a/app/code/Magento/CatalogImportExport/Model/Import/Product.php +++ b/app/code/Magento/CatalogImportExport/Model/Import/Product.php @@ -1972,11 +1972,7 @@ private function getAlreadyExistedImage(array $imageRow, string $columnImage, st return array_reduce( $imageRow, function ($exists, $file) use ($hash) { - if ($exists) { - return $exists; - } - - if (isset($file['hash']) && $file['hash'] === $hash) { + if (!$exists && isset($file['hash']) && $file['hash'] === $hash) { return $file['value']; }