2323use Magento \Framework \Exception \LocalizedException ;
2424use Magento \Framework \Exception \NoSuchEntityException ;
2525use Magento \Framework \Filesystem ;
26+ use Magento \Framework \Filesystem \Driver \File ;
2627use Magento \Framework \Intl \DateTimeFactory ;
2728use Magento \Framework \Model \ResourceModel \Db \ObjectRelationProcessor ;
2829use Magento \Framework \Model \ResourceModel \Db \TransactionManagerInterface ;
4445 * @SuppressWarnings(PHPMD.ExcessivePublicCount)
4546 * @since 100.0.2
4647 */
47- class Product extends \ Magento \ ImportExport \ Model \ Import \ Entity \ AbstractEntity
48+ class Product extends AbstractEntity
4849{
49- const CONFIG_KEY_PRODUCT_TYPES = 'global/importexport/import_product_types ' ;
50+ public const CONFIG_KEY_PRODUCT_TYPES = 'global/importexport/import_product_types ' ;
51+ private const HASH_ALGORITHM = 'sha256 ' ;
5052
5153 /**
5254 * Size of bunch - part of products to save in one step.
@@ -766,6 +768,11 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity
766768 */
767769 private $ linkProcessor ;
768770
771+ /**
772+ * @var File
773+ */
774+ private $ fileDriver ;
775+
769776 /**
770777 * @param \Magento\Framework\Json\Helper\Data $jsonHelper
771778 * @param \Magento\ImportExport\Helper\Data $importExportData
@@ -814,6 +821,7 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity
814821 * @param StatusProcessor|null $statusProcessor
815822 * @param StockProcessor|null $stockProcessor
816823 * @param LinkProcessor|null $linkProcessor
824+ * @param File|null $fileDriver
817825 * @throws LocalizedException
818826 * @throws \Magento\Framework\Exception\FileSystemException
819827 * @SuppressWarnings(PHPMD.ExcessiveParameterList)
@@ -866,7 +874,8 @@ public function __construct(
866874 ProductRepositoryInterface $ productRepository = null ,
867875 StatusProcessor $ statusProcessor = null ,
868876 StockProcessor $ stockProcessor = null ,
869- LinkProcessor $ linkProcessor = null
877+ LinkProcessor $ linkProcessor = null ,
878+ ?File $ fileDriver = null
870879 ) {
871880 $ this ->_eventManager = $ eventManager ;
872881 $ this ->stockRegistry = $ stockRegistry ;
@@ -930,6 +939,7 @@ public function __construct(
930939 $ this ->dateTimeFactory = $ dateTimeFactory ?? ObjectManager::getInstance ()->get (DateTimeFactory::class);
931940 $ this ->productRepository = $ productRepository ?? ObjectManager::getInstance ()
932941 ->get (ProductRepositoryInterface::class);
942+ $ this ->fileDriver = $ fileDriver ?: ObjectManager::getInstance ()->get (File::class);
933943 }
934944
935945 /**
@@ -1570,7 +1580,10 @@ protected function _saveProducts()
15701580 $ uploadedImages = [];
15711581 $ previousType = null ;
15721582 $ prevAttributeSet = null ;
1583+
1584+ $ importDir = $ this ->_mediaDirectory ->getAbsolutePath ($ this ->getUploader ()->getTmpDir ());
15731585 $ existingImages = $ this ->getExistingImages ($ bunch );
1586+ $ this ->addImageHashes ($ existingImages );
15741587
15751588 foreach ($ bunch as $ rowNum => $ rowData ) {
15761589 // reset category processor's failed categories array
@@ -1738,7 +1751,8 @@ protected function _saveProducts()
17381751 $ position = 0 ;
17391752 foreach ($ rowImages as $ column => $ columnImages ) {
17401753 foreach ($ columnImages as $ columnImageKey => $ columnImage ) {
1741- if (!isset ($ uploadedImages [$ columnImage ])) {
1754+ $ uploadedFile = $ this ->getAlreadyExistedImage ($ rowExistingImages , $ columnImage , $ importDir );
1755+ if (!$ uploadedFile && !isset ($ uploadedImages [$ columnImage ])) {
17421756 $ uploadedFile = $ this ->uploadMediaFiles ($ columnImage );
17431757 $ uploadedFile = $ uploadedFile ?: $ this ->getSystemFile ($ columnImage );
17441758 if ($ uploadedFile ) {
@@ -1753,7 +1767,7 @@ protected function _saveProducts()
17531767 ProcessingError::ERROR_LEVEL_NOT_CRITICAL
17541768 );
17551769 }
1756- } else {
1770+ } elseif ( isset ( $ uploadedImages [ $ columnImage ])) {
17571771 $ uploadedFile = $ uploadedImages [$ columnImage ];
17581772 }
17591773
@@ -1782,8 +1796,7 @@ protected function _saveProducts()
17821796 }
17831797
17841798 if (isset ($ rowLabels [$ column ][$ columnImageKey ])
1785- && $ rowLabels [$ column ][$ columnImageKey ] !=
1786- $ currentFileData ['label ' ]
1799+ && $ rowLabels [$ column ][$ columnImageKey ] !== $ currentFileData ['label ' ]
17871800 ) {
17881801 $ labelsForUpdate [] = [
17891802 'label ' => $ rowLabels [$ column ][$ columnImageKey ],
@@ -1792,7 +1805,7 @@ protected function _saveProducts()
17921805 ];
17931806 }
17941807 } else {
1795- if ($ column == self ::COL_MEDIA_IMAGE ) {
1808+ if ($ column === self ::COL_MEDIA_IMAGE ) {
17961809 $ rowData [$ column ][] = $ uploadedFile ;
17971810 }
17981811 $ mediaGallery [$ storeId ][$ rowSku ][$ uploadedFile ] = [
@@ -1908,24 +1921,14 @@ protected function _saveProducts()
19081921 }
19091922 }
19101923
1911- $ this ->saveProductEntity (
1912- $ entityRowsIn ,
1913- $ entityRowsUp
1914- )->_saveProductWebsites (
1915- $ this ->websitesCache
1916- )->_saveProductCategories (
1917- $ this ->categoriesCache
1918- )->_saveProductTierPrices (
1919- $ tierPrices
1920- )->_saveMediaGallery (
1921- $ mediaGallery
1922- )->_saveProductAttributes (
1923- $ attributes
1924- )->updateMediaGalleryVisibility (
1925- $ imagesForChangeVisibility
1926- )->updateMediaGalleryLabels (
1927- $ labelsForUpdate
1928- );
1924+ $ this ->saveProductEntity ($ entityRowsIn , $ entityRowsUp )
1925+ ->_saveProductWebsites ($ this ->websitesCache )
1926+ ->_saveProductCategories ($ this ->categoriesCache )
1927+ ->_saveProductTierPrices ($ tierPrices )
1928+ ->_saveMediaGallery ($ mediaGallery )
1929+ ->_saveProductAttributes ($ attributes )
1930+ ->updateMediaGalleryVisibility ($ imagesForChangeVisibility )
1931+ ->updateMediaGalleryLabels ($ labelsForUpdate );
19291932
19301933 $ this ->_eventManager ->dispatch (
19311934 'catalog_product_import_bunch_save_after ' ,
@@ -1939,6 +1942,87 @@ protected function _saveProducts()
19391942
19401943 // phpcs:enable
19411944
1945+ /**
1946+ * Returns image hash by path
1947+ *
1948+ * @param string $path
1949+ * @return string
1950+ */
1951+ private function getFileHash (string $ path ): string
1952+ {
1953+ return hash_file (self ::HASH_ALGORITHM , $ path );
1954+ }
1955+
1956+ /**
1957+ * Returns existed image
1958+ *
1959+ * @param array $imageRow
1960+ * @param string $columnImage
1961+ * @param string $importDir
1962+ * @return string
1963+ */
1964+ private function getAlreadyExistedImage (array $ imageRow , string $ columnImage , string $ importDir ): string
1965+ {
1966+ if (filter_var ($ columnImage , FILTER_VALIDATE_URL )) {
1967+ $ hash = $ this ->getFileHash ($ columnImage );
1968+ } else {
1969+ $ path = $ importDir . DS . $ columnImage ;
1970+ $ hash = $ this ->isFileExists ($ path ) ? $ this ->getFileHash ($ path ) : '' ;
1971+ }
1972+
1973+ return array_reduce (
1974+ $ imageRow ,
1975+ function ($ exists , $ file ) use ($ hash ) {
1976+ if (!$ exists && isset ($ file ['hash ' ]) && $ file ['hash ' ] === $ hash ) {
1977+ return $ file ['value ' ];
1978+ }
1979+
1980+ return $ exists ;
1981+ },
1982+ ''
1983+ );
1984+ }
1985+
1986+ /**
1987+ * Generate hashes for existing images for comparison with newly uploaded images.
1988+ *
1989+ * @param array $images
1990+ * @return void
1991+ */
1992+ private function addImageHashes (array &$ images ): void
1993+ {
1994+ $ productMediaPath = $ this ->filesystem ->getDirectoryRead (DirectoryList::MEDIA )
1995+ ->getAbsolutePath (DS . 'catalog ' . DS . 'product ' );
1996+
1997+ foreach ($ images as $ storeId => $ skus ) {
1998+ foreach ($ skus as $ sku => $ files ) {
1999+ foreach ($ files as $ path => $ file ) {
2000+ if ($ this ->fileDriver ->isExists ($ productMediaPath . $ file ['value ' ])) {
2001+ $ fileName = $ productMediaPath . $ file ['value ' ];
2002+ $ images [$ storeId ][$ sku ][$ path ]['hash ' ] = $ this ->getFileHash ($ fileName );
2003+ }
2004+ }
2005+ }
2006+ }
2007+ }
2008+
2009+ /**
2010+ * Is file exists
2011+ *
2012+ * @param string $path
2013+ * @return bool
2014+ */
2015+ private function isFileExists (string $ path ): bool
2016+ {
2017+ try {
2018+ $ fileExists = $ this ->fileDriver ->isExists ($ path );
2019+ } catch (\Exception $ exception ) {
2020+ $ fileExists = false ;
2021+ }
2022+
2023+ return $ fileExists ;
2024+ }
2025+
19422026 /**
19432027 * Clears entries from Image Set and Row Data marked as no_selection
19442028 *
@@ -1950,9 +2034,8 @@ private function clearNoSelectionImages($rowImages, $rowData)
19502034 {
19512035 foreach ($ rowImages as $ column => $ columnImages ) {
19522036 foreach ($ columnImages as $ key => $ image ) {
1953- if ($ image == 'no_selection ' ) {
1954- unset($ rowImages [$ column ][$ key ]);
1955- unset($ rowData [$ column ]);
2037+ if ($ image === 'no_selection ' ) {
2038+ unset($ rowImages [$ column ][$ key ], $ rowData [$ column ]);
19562039 }
19572040 }
19582041 }
@@ -2095,6 +2178,21 @@ protected function _saveProductTierPrices(array $tierPriceData)
20952178 return $ this ;
20962179 }
20972180
2181+ /**
2182+ * Returns the import directory if specified or a default import directory (media/import).
2183+ *
2184+ * @return string
2185+ */
2186+ private function getImportDir (): string
2187+ {
2188+ $ dirConfig = DirectoryList::getDefaultConfig ();
2189+ $ dirAddon = $ dirConfig [DirectoryList::MEDIA ][DirectoryList::PATH ];
2190+
2191+ return empty ($ this ->_parameters [Import::FIELD_NAME_IMG_FILE_DIR ])
2192+ ? $ dirAddon . DS . $ this ->_mediaDirectory ->getRelativePath ('import ' )
2193+ : $ this ->_parameters [Import::FIELD_NAME_IMG_FILE_DIR ];
2194+ }
2195+
20982196 /**
20992197 * Returns an object for upload a media files
21002198 *
@@ -2111,11 +2209,7 @@ protected function _getUploader()
21112209 $ dirConfig = DirectoryList::getDefaultConfig ();
21122210 $ dirAddon = $ dirConfig [DirectoryList::MEDIA ][DirectoryList::PATH ];
21132211
2114- if (!empty ($ this ->_parameters [Import::FIELD_NAME_IMG_FILE_DIR ])) {
2115- $ tmpPath = $ this ->_parameters [Import::FIELD_NAME_IMG_FILE_DIR ];
2116- } else {
2117- $ tmpPath = $ dirAddon . '/ ' . $ this ->_mediaDirectory ->getRelativePath ('import ' );
2118- }
2212+ $ tmpPath = $ this ->getImportDir ();
21192213
21202214 if (!$ fileUploader ->setTmpDir ($ tmpPath )) {
21212215 throw new LocalizedException (
0 commit comments