diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product.php b/app/code/Magento/CatalogImportExport/Model/Import/Product.php index 0b7fbaf86826b..8b25ea5ab1898 100644 --- a/app/code/Magento/CatalogImportExport/Model/Import/Product.php +++ b/app/code/Magento/CatalogImportExport/Model/Import/Product.php @@ -997,6 +997,7 @@ public function setParameters(array $params) * Delete products for replacement. * * @return $this + * @throws \Exception */ public function deleteProductsForReplacement() { @@ -1088,6 +1089,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() { @@ -1109,6 +1115,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() { @@ -1249,6 +1260,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() { @@ -1280,6 +1296,7 @@ protected function _saveLinks() * * @param array $attributesData * @return $this + * @throws \Exception */ protected function _saveProductAttributes(array $attributesData) { @@ -1350,6 +1367,7 @@ protected function _saveProductCategories(array $categoriesData) * @param array $entityRowsUp Row for update * @return $this * @since 100.1.0 + * @throws \Exception */ public function saveProductEntity(array $entityRowsIn, array $entityRowsUp) { @@ -1402,6 +1420,7 @@ private function getOldSkuFieldsForSelect() * * @param array $newProducts * @return void + * @throws \Exception */ private function updateOldSku(array $newProducts) { @@ -1425,6 +1444,7 @@ private function updateOldSku(array $newProducts) * Get new SKU fields for select * * @return array + * @throws \Exception */ private function getNewSkuFieldsForSelect() { @@ -1461,6 +1481,7 @@ protected function initMediaGalleryResources() * * @param array $bunch * @return array + * @throws \Exception */ protected function getExistingImages($bunch) { @@ -1508,6 +1529,8 @@ public function getImagesFromRow(array $rowData) * @SuppressWarnings(PHPMD.ExcessiveMethodLength) * @SuppressWarnings(PHPMD.UnusedLocalVariable) * @throws LocalizedException + * @throws \Zend_Validate_Exception + * phpcs:disable Generic.Metrics.NestingLevel */ protected function _saveProducts() { @@ -1524,12 +1547,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 @@ -1618,6 +1646,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]); @@ -1691,29 +1720,67 @@ 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 (!isset($existingImages[$rowSku])) { + $imageAlreadyExists = false; + } else { + $imageAlreadyExists = array_reduce( + $existingImages[$rowSku], + 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; } @@ -1756,6 +1823,11 @@ protected function _saveProducts() } } + // 5.1 Items to remove phase + if (isset($existingImages[$rowSku])) { + $galleryItemsToRemove = \array_diff(\array_keys($existingImages[$rowSku]), $uploadedFiles); + } + // 6. Attributes phase $rowStore = (self::SCOPE_STORE == $rowScope) ? $this->storeResolver->getStoreCodeToId($rowData[self::COL_STORE]) @@ -1866,6 +1938,8 @@ protected function _saveProducts() $tierPrices )->_saveMediaGallery( $mediaGallery + )->_removeOldMediaGalleryItems( + $galleryItemsToRemove )->_saveProductAttributes( $attributes )->updateMediaGalleryVisibility( @@ -1883,6 +1957,23 @@ protected function _saveProducts() return $this; } + /** + * 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 $sku => $files) { + foreach ($files as $path => $file) { + $images[$sku][$path]['hash'] = hash_file("sha256",$productMediaPath . $file['value']); + } + } + } + /** * Prepare array with image states (visible or hidden from product page) * @@ -1984,6 +2075,7 @@ public function getStoreIdByCode($storeCode) * * @param array $tierPriceData * @return $this + * @throws \Exception */ protected function _saveProductTierPrices(array $tierPriceData) { @@ -2018,6 +2110,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 * @@ -2034,11 +2144,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 (!$this->_fileUploader->setTmpDir($tmpPath)) { throw new LocalizedException( @@ -2110,6 +2216,7 @@ private function getSystemFile($fileName) * * @param array $mediaGalleryData * @return $this + * @throws \Exception */ protected function _saveMediaGallery(array $mediaGalleryData) { @@ -2121,6 +2228,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. * @@ -2163,6 +2286,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() { @@ -2716,6 +2842,7 @@ private function _customFieldsMapping($rowData) * Validate data rows and save bunches to DB * * @return $this|AbstractEntity + * @throws LocalizedException */ protected function _saveValidatedBunches() { @@ -2856,6 +2983,7 @@ private function isNeedToChangeUrlKey(array $rowData): bool * Get product entity link field * * @return string + * @throws \Exception */ private function getProductEntityLinkField() { @@ -2871,6 +2999,7 @@ private function getProductEntityLinkField() * Get product entity identifier field * * @return string + * @throws \Exception */ private function getProductIdentifierField() { @@ -2887,6 +3016,7 @@ private function getProductIdentifierField() * * @param array $labels * @return void + * @throws \Exception */ private function updateMediaGalleryLabels(array $labels) { @@ -2900,6 +3030,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 d43dc11a68fcf..36d0ad7d216b5 100644 --- a/app/code/Magento/CatalogImportExport/Model/Import/Product/MediaGalleryProcessor.php +++ b/app/code/Magento/CatalogImportExport/Model/Import/Product/MediaGalleryProcessor.php @@ -105,6 +105,7 @@ public function __construct( * * @param array $mediaGalleryData * @return void + * @throws \Exception */ public function saveMediaGallery(array $mediaGalleryData) { @@ -150,6 +151,7 @@ public function saveMediaGallery(array $mediaGalleryData) * * @param array $labels * @return void + * @throws \Exception */ public function updateMediaGalleryLabels(array $labels) { @@ -161,6 +163,7 @@ public function updateMediaGalleryLabels(array $labels) * * @param array $images * @return void + * @throws \Exception */ public function updateMediaGalleryVisibility(array $images) { @@ -173,6 +176,7 @@ public function updateMediaGalleryVisibility(array $images) * @param array $data * @param string $field * @return void + * @throws \Exception */ private function updateMediaGalleryField(array $data, $field) { @@ -215,6 +219,7 @@ private function updateMediaGalleryField(array $data, $field) * * @param array $bunch * @return array + * @throws \Exception */ public function getExistingImages(array $bunch) { @@ -292,6 +297,7 @@ private function initMediaGalleryResources() * @param array $newMediaValues * @param array $valueToProductId * @return void + * @throws \Exception */ private function processMediaPerStore( int $storeId, @@ -348,10 +354,25 @@ private function processMediaPerStore( } } + /** + * 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/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/ProductTest.php b/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/ProductTest.php index f85d33edb5d8c..33891b8a66761 100644 --- a/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/ProductTest.php +++ b/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/ProductTest.php @@ -284,9 +284,11 @@ protected function setUp() ->getMock(); $this->storeResolver = $this->getMockBuilder(\Magento\CatalogImportExport\Model\Import\Product\StoreResolver::class) - ->setMethods([ - 'getStoreCodeToId', - ]) + ->setMethods( + [ + 'getStoreCodeToId', + ] + ) ->disableOriginalConstructor() ->getMock(); $this->skuProcessor = @@ -596,9 +598,13 @@ public function testGetMultipleValueSeparatorDefault() public function testGetMultipleValueSeparatorFromParameters() { $expectedSeparator = 'value'; - $this->setPropertyValue($this->importProduct, '_parameters', [ - \Magento\ImportExport\Model\Import::FIELD_FIELD_MULTIPLE_VALUE_SEPARATOR => $expectedSeparator, - ]); + $this->setPropertyValue( + $this->importProduct, + '_parameters', + [ + \Magento\ImportExport\Model\Import::FIELD_FIELD_MULTIPLE_VALUE_SEPARATOR => $expectedSeparator, + ] + ); $this->assertEquals( $expectedSeparator, @@ -618,9 +624,13 @@ public function testGetEmptyAttributeValueConstantDefault() public function testGetEmptyAttributeValueConstantFromParameters() { $expectedSeparator = '__EMPTY__VALUE__TEST__'; - $this->setPropertyValue($this->importProduct, '_parameters', [ - \Magento\ImportExport\Model\Import::FIELD_EMPTY_ATTRIBUTE_VALUE_CONSTANT => $expectedSeparator, - ]); + $this->setPropertyValue( + $this->importProduct, + '_parameters', + [ + \Magento\ImportExport\Model\Import::FIELD_EMPTY_ATTRIBUTE_VALUE_CONSTANT => $expectedSeparator, + ] + ); $this->assertEquals( $expectedSeparator, @@ -632,9 +642,11 @@ public function testDeleteProductsForReplacement() { $importProduct = $this->getMockBuilder(Product::class) ->disableOriginalConstructor() - ->setMethods([ - 'setParameters', '_deleteProducts' - ]) + ->setMethods( + [ + 'setParameters', '_deleteProducts' + ] + ) ->getMock(); $importProduct->expects($this->once())->method('setParameters')->with( @@ -764,9 +776,13 @@ public function testGetProductWebsites() 'key 3' => 'val', ]; $expectedResult = array_keys($productValue); - $this->setPropertyValue($this->importProduct, 'websitesCache', [ - $productSku => $productValue - ]); + $this->setPropertyValue( + $this->importProduct, + 'websitesCache', + [ + $productSku => $productValue + ] + ); $actualResult = $this->importProduct->getProductWebsites($productSku); @@ -785,9 +801,13 @@ public function testGetProductCategories() 'key 3' => 'val', ]; $expectedResult = array_keys($productValue); - $this->setPropertyValue($this->importProduct, 'categoriesCache', [ - $productSku => $productValue - ]); + $this->setPropertyValue( + $this->importProduct, + 'categoriesCache', + [ + $productSku => $productValue + ] + ); $actualResult = $this->importProduct->getProductCategories($productSku); @@ -1112,9 +1132,13 @@ public function testValidateRowSetAttributeSetCodeIntoRowData() ->disableOriginalConstructor() ->getMock(); $productType->expects($this->once())->method('isRowValid')->with($expectedRowData); - $this->setPropertyValue($importProduct, '_productTypeModels', [ - $newSku['type_id'] => $productType - ]); + $this->setPropertyValue( + $importProduct, + '_productTypeModels', + [ + $newSku['type_id'] => $productType + ] + ); //suppress option validation $this->_rewriteGetOptionEntityInImportProduct($importProduct); @@ -1603,6 +1627,7 @@ public function returnQuoteCallback() * @param $methodName * @param array $parameters * @return mixed + * @throws \ReflectionException */ protected function invokeMethod(&$object, $methodName, array $parameters = []) { @@ -1617,6 +1642,7 @@ protected function invokeMethod(&$object, $methodName, array $parameters = []) * @param $object * @param $property * @param $value + * @throws \ReflectionException */ protected function setPropertyValue(&$object, $property, $value) { @@ -1630,6 +1656,8 @@ protected function setPropertyValue(&$object, $property, $value) /** * @param $object * @param $property + * @return mixed + * @throws \ReflectionException */ protected function getPropertyValue(&$object, $property) { @@ -1645,6 +1673,7 @@ protected function getPropertyValue(&$object, $property) * @param $methodName * @param array $parameters * @return mixed + * @throws \ReflectionException */ protected function overrideMethod(&$object, $methodName, array $parameters = []) { @@ -1681,6 +1710,7 @@ private function _suppressValidateRowOptionValidatorInvalidRows($importProduct) * * @param Product * @return \Magento\CatalogImportExport\Model\Import\Product\Validator|MockObject + * @throws \ReflectionException */ private function _setValidatorMockInImportProduct($importProduct) { 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 0000000000000..2c21e0238ede7 Binary files /dev/null and b/dev/tests/integration/testsuite/Magento/Catalog/_files/magento_image_2.jpg differ diff --git a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/AbstractProductExportImportTestCase.php b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/AbstractProductExportImportTestCase.php index d3a2e4c53f246..b1ca38be5015a 100644 --- a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/AbstractProductExportImportTestCase.php +++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/AbstractProductExportImportTestCase.php @@ -233,11 +233,13 @@ protected function executeImportDeleteTest(array $skus, string $csvFile = null): * @param bool $rollback * @return void * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ protected function executeFixtures(array $fixtures, bool $rollback = false) { foreach ($fixtures as $fixture) { $fixturePath = $this->resolveFixturePath($fixture, $rollback); + // phpcs:ignore include $fixturePath; } } @@ -320,10 +322,13 @@ protected function executeImportReplaceTest( $exportProduct = $this->objectManager->create(\Magento\CatalogImportExport\Model\Export\Product::class); if ($usePagination) { /** @var \ReflectionProperty $itemsPerPageProperty */ - $itemsPerPageProperty = $this->objectManager->create(\ReflectionProperty::class, [ - 'class' => \Magento\CatalogImportExport\Model\Export\Product::class, - 'name' => '_itemsPerPage' - ]); + $itemsPerPageProperty = $this->objectManager->create( + \ReflectionProperty::class, + [ + 'class' => \Magento\CatalogImportExport\Model\Export\Product::class, + 'name' => '_itemsPerPage' + ] + ); $itemsPerPageProperty->setAccessible(true); $itemsPerPageProperty->setValue($exportProduct, 1); } @@ -430,6 +435,7 @@ private function importProducts(string $csvfile, string $behavior): void [ 'behavior' => $behavior, 'entity' => 'catalog_product', + 'import_images_file_dir' => $tmpDir ] )->setSource( $source 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 b04d1394e5c12..b1a94c76201b6 100644 --- a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest.php +++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest.php @@ -498,6 +498,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) { @@ -838,6 +839,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); } /** @@ -866,29 +884,39 @@ public static function mediaImportImageFixture() DirectoryList::MEDIA ); + $path = 'catalog' . DIRECTORY_SEPARATOR . 'product'; $mediaDirectory->create('import'); - $dirPath = $mediaDirectory->getAbsolutePath('import'); + $importMediaPath = $mediaDirectory->getAbsolutePath('import'); + $mediaDirectory->create($path); $items = [ [ 'source' => __DIR__ . '/../../../../Magento/Catalog/_files/magento_image.jpg', - 'dest' => $dirPath . '/magento_image.jpg', + 'dest' => $importMediaPath . '/magento_image.jpg', + ], + [ + 'source' => __DIR__ . '/../../../../Magento/Catalog/_files/magento_image_2.jpg', + 'dest' => $importMediaPath . '/magento_image_2.jpg', ], [ 'source' => __DIR__ . '/../../../../Magento/Catalog/_files/magento_small_image.jpg', - 'dest' => $dirPath . '/magento_small_image.jpg', + 'dest' => $importMediaPath . '/magento_small_image.jpg', + ], + [ + 'source' => __DIR__ . '/../../../../Magento/Catalog/_files/magento_small_image.jpg', + 'dest' => $importMediaPath . '/magento_small_image_2.jpg', ], [ 'source' => __DIR__ . '/../../../../Magento/Catalog/_files/magento_thumbnail.jpg', - 'dest' => $dirPath . '/magento_thumbnail.jpg', + 'dest' => $importMediaPath . '/magento_thumbnail.jpg', ], [ 'source' => __DIR__ . '/_files/magento_additional_image_one.jpg', - 'dest' => $dirPath . '/magento_additional_image_one.jpg', + 'dest' => $importMediaPath . '/magento_additional_image_one.jpg', ], [ 'source' => __DIR__ . '/_files/magento_additional_image_two.jpg', - 'dest' => $dirPath . '/magento_additional_image_two.jpg', + 'dest' => $importMediaPath . '/magento_additional_image_two.jpg', ], ]; @@ -1624,7 +1652,8 @@ public function testAddUpdateProductWithInvalidUrlKeys() : void } /** - * Make sure the non existing image in the csv file won't erase the qty key of the existing products. + * Make sure the non existing image in the csv file causes import to stop. + * This test will also catch the exception * * @magentoDbIsolation enabled * @magentoAppIsolation enabled @@ -1634,9 +1663,7 @@ public function testImportWithNonExistingImage() $products = [ 'simple_new' => 100, ]; - $this->importFile('products_to_import_with_non_existing_image.csv'); - $productRepository = $this->objectManager->create(\Magento\Catalog\Api\ProductRepositoryInterface::class); foreach ($products as $productSku => $productQty) { $product = $productRepository->get($productSku); @@ -1953,12 +1980,13 @@ public function testProductWithWrappedAdditionalAttributes() * @param string $fileName * @param int $expectedErrors * @return void + * @throws \Magento\Framework\Exception\LocalizedException */ private function importDataForMediaTest(string $fileName, int $expectedErrors = 0) { - $filesystem = $this->objectManager->create(\Magento\Framework\Filesystem::class); + $filesystem = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() + ->get(\Magento\Framework\Filesystem::class); $directory = $filesystem->getDirectoryWrite(DirectoryList::ROOT); - $source = $this->objectManager->create( \Magento\ImportExport\Model\Import\Source\Csv::class, [ @@ -2152,11 +2180,10 @@ public function testValidateData() */ public function testImportWithFilesystemImages() { - /** @var Filesystem $filesystem */ - $filesystem = ObjectManager::getInstance()->get(Filesystem::class); /** @var \Magento\Framework\Filesystem\Directory\WriteInterface $writeAdapter */ - $writeAdapter = $filesystem->getDirectoryWrite(DirectoryList::MEDIA); - + $writeAdapter = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() + ->get(\Magento\Framework\Filesystem::class) + ->getDirectoryWrite(\Magento\Framework\App\Filesystem\DirectoryList::MEDIA); if (!$writeAdapter->isWritable()) { $this->markTestSkipped('Due to unwritable media directory'); } 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 04b3092c8fa8a..00ede63ccce0f 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 @@ -14,29 +14,66 @@ $path = 'catalog' . DIRECTORY_SEPARATOR . 'product'; // Is required for using importDataForMediaTest method. $mediaDirectory->create('import'); +$importMediaPath = $mediaDirectory->getAbsolutePath('import'); $mediaDirectory->create($path); -$dirPath = $mediaDirectory->getAbsolutePath($path); +$productMediaPath = $mediaDirectory->getAbsolutePath($path); $items = [ [ 'source' => __DIR__ . '/../../../../../Magento/Catalog/_files/magento_image.jpg', - 'dest' => $dirPath . '/magento_image.jpg', + 'dest' => $productMediaPath . '/magento_image.jpg', + ], + [ + 'source' => __DIR__ . '/../../../../../Magento/Catalog/_files/magento_image_2.jpg', + 'dest' => $productMediaPath . '/magento_image_2.jpg', + ], + [ + 'source' => __DIR__ . '/../../../../../Magento/Catalog/_files/magento_small_image.jpg', + 'dest' => $productMediaPath . '/magento_small_image.jpg', + ], + [ + 'source' => __DIR__ . '/../../../../../Magento/Catalog/_files/magento_small_image.jpg', + 'dest' => $productMediaPath . '/magento_small_image_2.jpg', + ], + [ + 'source' => __DIR__ . '/../../../../../Magento/Catalog/_files/magento_thumbnail.jpg', + 'dest' => $productMediaPath . '/magento_thumbnail.jpg', + ], + [ + 'source' => __DIR__ . '/magento_additional_image_one.jpg', + 'dest' => $productMediaPath . '/magento_additional_image_one.jpg', + ], + [ + 'source' => __DIR__ . '/magento_additional_image_two.jpg', + 'dest' => $productMediaPath . '/magento_additional_image_two.jpg', + ], + [ + 'source' => __DIR__ . '/../../../../../Magento/Catalog/_files/magento_image.jpg', + 'dest' => $importMediaPath . '/magento_image.jpg', + ], + [ + 'source' => __DIR__ . '/../../../../../Magento/Catalog/_files/magento_image_2.jpg', + 'dest' => $importMediaPath . '/magento_image_2.jpg', + ], + [ + 'source' => __DIR__ . '/../../../../../Magento/Catalog/_files/magento_small_image.jpg', + 'dest' => $importMediaPath . '/magento_small_image.jpg', ], [ 'source' => __DIR__ . '/../../../../../Magento/Catalog/_files/magento_small_image.jpg', - 'dest' => $dirPath . '/magento_small_image.jpg', + 'dest' => $importMediaPath . '/magento_small_image_2.jpg', ], [ 'source' => __DIR__ . '/../../../../../Magento/Catalog/_files/magento_thumbnail.jpg', - 'dest' => $dirPath . '/magento_thumbnail.jpg', + 'dest' => $importMediaPath . '/magento_thumbnail.jpg', ], [ 'source' => __DIR__ . '/magento_additional_image_one.jpg', - 'dest' => $dirPath . '/magento_additional_image_one.jpg', + 'dest' => $importMediaPath . '/magento_additional_image_one.jpg', ], [ 'source' => __DIR__ . '/magento_additional_image_two.jpg', - 'dest' => $dirPath . '/magento_additional_image_two.jpg', + 'dest' => $importMediaPath . '/magento_additional_image_two.jpg', ], ];