Skip to content

Commit 0c6d204

Browse files
Merge branch '2.4-develop' into queue-exchange-argument-processor-fix
2 parents 8c8b9cf + bc8e51d commit 0c6d204

File tree

41 files changed

+1239
-247
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+1239
-247
lines changed

.editorconfig

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,9 @@ trim_trailing_whitespace = true
1010

1111
[*.md]
1212
trim_trailing_whitespace = false
13+
14+
[*.{yml,yaml,json}]
15+
indent_size = 2
16+
17+
[{composer, auth}.json]
18+
indent_size = 4

app/code/Magento/Catalog/Model/Indexer/Category/Product/Plugin/TableResolver.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,10 @@ public function afterGetTableName(
5555
string $result,
5656
$modelEntity
5757
) {
58-
if (!is_array($modelEntity) && $modelEntity === AbstractAction::MAIN_INDEX_TABLE) {
58+
if (!is_array($modelEntity) &&
59+
$modelEntity === AbstractAction::MAIN_INDEX_TABLE &&
60+
$this->storeManager->getStore()->getId()
61+
) {
5962
$catalogCategoryProductDimension = new Dimension(
6063
\Magento\Store\Model\Store::ENTITY,
6164
$this->storeManager->getStore()->getId()
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\Catalog\Test\Unit\Model\Indexer\Category\Product\Plugin;
9+
10+
use Magento\Catalog\Model\Indexer\Category\Product\Plugin\TableResolver;
11+
use Magento\Framework\App\ResourceConnection;
12+
use Magento\Framework\Indexer\ScopeResolver\IndexScopeResolver;
13+
use Magento\Store\Model\Store;
14+
use Magento\Store\Model\StoreManagerInterface;
15+
use PHPUnit\Framework\TestCase;
16+
17+
class TableResolverTest extends TestCase
18+
{
19+
/**
20+
* Tests replacing catalog_category_product_index table name
21+
*
22+
* @param int $storeId
23+
* @param string $tableName
24+
* @param string $expected
25+
* @dataProvider afterGetTableNameDataProvider
26+
*/
27+
public function testAfterGetTableName(int $storeId, string $tableName, string $expected): void
28+
{
29+
$storeManagerMock = $this->getMockForAbstractClass(StoreManagerInterface::class);
30+
31+
$storeMock = $this->getMockBuilder(Store::class)
32+
->onlyMethods(['getId'])
33+
->disableOriginalConstructor()
34+
->getMock();
35+
$storeMock->method('getId')
36+
->willReturn($storeId);
37+
38+
$storeManagerMock->method('getStore')->willReturn($storeMock);
39+
40+
$tableResolverMock = $this->getMockBuilder(IndexScopeResolver::class)
41+
->disableOriginalConstructor()
42+
->getMock();
43+
$tableResolverMock->method('resolve')->willReturn('catalog_category_product_index_store1');
44+
45+
$subjectMock = $this->getMockBuilder(ResourceConnection::class)
46+
->disableOriginalConstructor()
47+
->getMock();
48+
49+
$model = new TableResolver($storeManagerMock, $tableResolverMock);
50+
51+
$this->assertEquals(
52+
$expected,
53+
$model->afterGetTableName($subjectMock, $tableName, 'catalog_category_product_index')
54+
);
55+
}
56+
57+
/**
58+
* Data provider for testAfterGetTableName
59+
*
60+
* @return array
61+
*/
62+
public function afterGetTableNameDataProvider(): array
63+
{
64+
return [
65+
[
66+
'storeId' => 1,
67+
'tableName' => 'catalog_category_product_index',
68+
'expected' => 'catalog_category_product_index_store1'
69+
],
70+
[
71+
'storeId' => 0,
72+
'tableName' => 'catalog_category_product_index',
73+
'expected' => 'catalog_category_product_index'
74+
],
75+
];
76+
}
77+
}

app/code/Magento/CatalogImportExport/Model/Import/Product.php

Lines changed: 128 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
use Magento\Framework\Exception\LocalizedException;
2424
use Magento\Framework\Exception\NoSuchEntityException;
2525
use Magento\Framework\Filesystem;
26+
use Magento\Framework\Filesystem\Driver\File;
2627
use Magento\Framework\Intl\DateTimeFactory;
2728
use Magento\Framework\Model\ResourceModel\Db\ObjectRelationProcessor;
2829
use Magento\Framework\Model\ResourceModel\Db\TransactionManagerInterface;
@@ -44,9 +45,10 @@
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

Comments
 (0)