23
23
use Magento \Framework \Exception \LocalizedException ;
24
24
use Magento \Framework \Exception \NoSuchEntityException ;
25
25
use Magento \Framework \Filesystem ;
26
+ use Magento \Framework \Filesystem \Driver \File ;
26
27
use Magento \Framework \Intl \DateTimeFactory ;
27
28
use Magento \Framework \Model \ResourceModel \Db \ObjectRelationProcessor ;
28
29
use Magento \Framework \Model \ResourceModel \Db \TransactionManagerInterface ;
44
45
* @SuppressWarnings(PHPMD.ExcessivePublicCount)
45
46
* @since 100.0.2
46
47
*/
47
- class Product extends \ Magento \ ImportExport \ Model \ Import \ Entity \ AbstractEntity
48
+ class Product extends AbstractEntity
48
49
{
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 ' ;
50
52
51
53
/**
52
54
* Size of bunch - part of products to save in one step.
@@ -766,6 +768,11 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity
766
768
*/
767
769
private $ linkProcessor ;
768
770
771
+ /**
772
+ * @var File
773
+ */
774
+ private $ fileDriver ;
775
+
769
776
/**
770
777
* @param \Magento\Framework\Json\Helper\Data $jsonHelper
771
778
* @param \Magento\ImportExport\Helper\Data $importExportData
@@ -814,6 +821,7 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity
814
821
* @param StatusProcessor|null $statusProcessor
815
822
* @param StockProcessor|null $stockProcessor
816
823
* @param LinkProcessor|null $linkProcessor
824
+ * @param File|null $fileDriver
817
825
* @throws LocalizedException
818
826
* @throws \Magento\Framework\Exception\FileSystemException
819
827
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
@@ -866,7 +874,8 @@ public function __construct(
866
874
ProductRepositoryInterface $ productRepository = null ,
867
875
StatusProcessor $ statusProcessor = null ,
868
876
StockProcessor $ stockProcessor = null ,
869
- LinkProcessor $ linkProcessor = null
877
+ LinkProcessor $ linkProcessor = null ,
878
+ ?File $ fileDriver = null
870
879
) {
871
880
$ this ->_eventManager = $ eventManager ;
872
881
$ this ->stockRegistry = $ stockRegistry ;
@@ -930,6 +939,7 @@ public function __construct(
930
939
$ this ->dateTimeFactory = $ dateTimeFactory ?? ObjectManager::getInstance ()->get (DateTimeFactory::class);
931
940
$ this ->productRepository = $ productRepository ?? ObjectManager::getInstance ()
932
941
->get (ProductRepositoryInterface::class);
942
+ $ this ->fileDriver = $ fileDriver ?: ObjectManager::getInstance ()->get (File::class);
933
943
}
934
944
935
945
/**
@@ -1570,7 +1580,10 @@ protected function _saveProducts()
1570
1580
$ uploadedImages = [];
1571
1581
$ previousType = null ;
1572
1582
$ prevAttributeSet = null ;
1583
+
1584
+ $ importDir = $ this ->_mediaDirectory ->getAbsolutePath ($ this ->getUploader ()->getTmpDir ());
1573
1585
$ existingImages = $ this ->getExistingImages ($ bunch );
1586
+ $ this ->addImageHashes ($ existingImages );
1574
1587
1575
1588
foreach ($ bunch as $ rowNum => $ rowData ) {
1576
1589
// reset category processor's failed categories array
@@ -1738,7 +1751,8 @@ protected function _saveProducts()
1738
1751
$ position = 0 ;
1739
1752
foreach ($ rowImages as $ column => $ columnImages ) {
1740
1753
foreach ($ columnImages as $ columnImageKey => $ columnImage ) {
1741
- if (!isset ($ uploadedImages [$ columnImage ])) {
1754
+ $ uploadedFile = $ this ->getAlreadyExistedImage ($ rowExistingImages , $ columnImage , $ importDir );
1755
+ if (!$ uploadedFile && !isset ($ uploadedImages [$ columnImage ])) {
1742
1756
$ uploadedFile = $ this ->uploadMediaFiles ($ columnImage );
1743
1757
$ uploadedFile = $ uploadedFile ?: $ this ->getSystemFile ($ columnImage );
1744
1758
if ($ uploadedFile ) {
@@ -1753,7 +1767,7 @@ protected function _saveProducts()
1753
1767
ProcessingError::ERROR_LEVEL_NOT_CRITICAL
1754
1768
);
1755
1769
}
1756
- } else {
1770
+ } elseif ( isset ( $ uploadedImages [ $ columnImage ])) {
1757
1771
$ uploadedFile = $ uploadedImages [$ columnImage ];
1758
1772
}
1759
1773
@@ -1782,8 +1796,7 @@ protected function _saveProducts()
1782
1796
}
1783
1797
1784
1798
if (isset ($ rowLabels [$ column ][$ columnImageKey ])
1785
- && $ rowLabels [$ column ][$ columnImageKey ] !=
1786
- $ currentFileData ['label ' ]
1799
+ && $ rowLabels [$ column ][$ columnImageKey ] !== $ currentFileData ['label ' ]
1787
1800
) {
1788
1801
$ labelsForUpdate [] = [
1789
1802
'label ' => $ rowLabels [$ column ][$ columnImageKey ],
@@ -1792,7 +1805,7 @@ protected function _saveProducts()
1792
1805
];
1793
1806
}
1794
1807
} else {
1795
- if ($ column == self ::COL_MEDIA_IMAGE ) {
1808
+ if ($ column === self ::COL_MEDIA_IMAGE ) {
1796
1809
$ rowData [$ column ][] = $ uploadedFile ;
1797
1810
}
1798
1811
$ mediaGallery [$ storeId ][$ rowSku ][$ uploadedFile ] = [
@@ -1908,24 +1921,14 @@ protected function _saveProducts()
1908
1921
}
1909
1922
}
1910
1923
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 );
1929
1932
1930
1933
$ this ->_eventManager ->dispatch (
1931
1934
'catalog_product_import_bunch_save_after ' ,
@@ -1939,6 +1942,87 @@ protected function _saveProducts()
1939
1942
1940
1943
// phpcs:enable
1941
1944
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
+
1942
2026
/**
1943
2027
* Clears entries from Image Set and Row Data marked as no_selection
1944
2028
*
@@ -1950,9 +2034,8 @@ private function clearNoSelectionImages($rowImages, $rowData)
1950
2034
{
1951
2035
foreach ($ rowImages as $ column => $ columnImages ) {
1952
2036
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 ]);
1956
2039
}
1957
2040
}
1958
2041
}
@@ -2095,6 +2178,21 @@ protected function _saveProductTierPrices(array $tierPriceData)
2095
2178
return $ this ;
2096
2179
}
2097
2180
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
+
2098
2196
/**
2099
2197
* Returns an object for upload a media files
2100
2198
*
@@ -2111,11 +2209,7 @@ protected function _getUploader()
2111
2209
$ dirConfig = DirectoryList::getDefaultConfig ();
2112
2210
$ dirAddon = $ dirConfig [DirectoryList::MEDIA ][DirectoryList::PATH ];
2113
2211
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 ();
2119
2213
2120
2214
if (!$ fileUploader ->setTmpDir ($ tmpPath )) {
2121
2215
throw new LocalizedException (
0 commit comments