Skip to content

Commit 4527d76

Browse files
🔃 [Magento Community Engineering] Community Contributions - 2.4-develop
Accepted Community Pull Requests: - #26713: Product images are being duplicated on import (by @PieterCappelle) - #30062: Feature/formatting json yaml (by @GrimLink) - #30358: Added correct block class for frontend viewModel reference example. (by @sanganinamrata) - #30333: Clean code and remove whitespace in markup layered navigation filters (by @mrtuvn) - #30318: Fix exception which is thrown on search results page (by @wojtekn) - #29773: Add font weight heavy variable (by @mrtuvn) Fixed GitHub Issues: - #14398: Images Not Replaced via CSV Import (reported by @itsabe) has been fixed in #26713 by @PieterCappelle in 2.4-develop branch Related commits: 1. 912b3f7 2. ffdb341 3. eb8607c 4. e4812b8 5. d03b223 6. d4931f7 7. f16a12f 8. 819fc7c 9. f16811c 10. bca6bf6 11. 3da6f23 12. 9b505ee - #21885: Product images are being duplicated on import (reported by @simonworkhouse) has been fixed in #26713 by @PieterCappelle in 2.4-develop branch Related commits: 1. 912b3f7 2. ffdb341 3. eb8607c 4. e4812b8 5. d03b223 6. d4931f7 7. f16a12f 8. 819fc7c 9. f16811c 10. bca6bf6 11. 3da6f23 12. 9b505ee - #30063: editorconfig: use an indentation of 2 for json and yaml files (reported by @GrimLink) has been fixed in #30062 by @GrimLink in 2.4-develop branch Related commits: 1. b70851d 2. f7f5dce 3. 9ef8d9d 4. 93d79b8 5. 0533f2e 6. 5b47647 7. 9d8a772 8. 5f704db 9. c722790 - #30066: [Issue] Feature/formatting json yaml (reported by @m2-assistant[bot]) has been fixed in #30062 by @GrimLink in 2.4-develop branch Related commits: 1. b70851d 2. f7f5dce 3. 9ef8d9d 4. 93d79b8 5. 0533f2e 6. 5b47647 7. 9d8a772 8. 5f704db 9. c722790 - #30450: [Issue] Added correct block class for frontend viewModel reference example. (reported by @m2-assistant[bot]) has been fixed in #30358 by @sanganinamrata in 2.4-develop branch Related commits: 1. cc51b77 - #30448: [Issue] Clean code and remove whitespace in markup layered navigation filters (reported by @m2-assistant[bot]) has been fixed in #30333 by @mrtuvn in 2.4-develop branch Related commits: 1. 374593d - #25110: Elasticsearch not able to search for term with a slash / in it (reported by @jorgb90) has been fixed in #30318 by @wojtekn in 2.4-develop branch Related commits: 1. ce1263d 2. ccec05a 3. 970d179 4. 38c4a3e 5. 88f6101 - #25886: SynonymAnalyzer.php in module-search is evaluating user input as a regex (reported by @dan-ding) has been fixed in #30318 by @wojtekn in 2.4-develop branch Related commits: 1. ce1263d 2. ccec05a 3. 970d179 4. 38c4a3e 5. 88f6101 - #28286: ElasticSearch breaks with / (reported by @rafaelstz) has been fixed in #30318 by @wojtekn in 2.4-develop branch Related commits: 1. ce1263d 2. ccec05a 3. 970d179 4. 38c4a3e 5. 88f6101 - #29778: [Issue] Add font weight heavy variable (reported by @m2-assistant[bot]) has been fixed in #29773 by @mrtuvn in 2.4-develop branch Related commits: 1. 977f6ff
2 parents dd862cd + cbe73fe commit 4527d76

File tree

20 files changed

+337
-204
lines changed

20 files changed

+337
-204
lines changed

.editorconfig

+6
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/CatalogImportExport/Model/Import/Product.php

+128-34
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(

app/code/Magento/LayeredNavigation/view/frontend/templates/layer/filter.phtml

+22-21
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@
44
* See COPYING.txt for license details.
55
*/
66

7-
// phpcs:disable Magento2.Templates.ThisInTemplate.FoundThis
87
?>
98
<?php
10-
/** @var $block \Magento\LayeredNavigation\Block\Navigation\FilterRenderer */
9+
/** @var \Magento\LayeredNavigation\Block\Navigation\FilterRenderer $block */
10+
/** @var \Magento\Framework\Escaper $escaper */
1111
/** @var \Magento\LayeredNavigation\ViewModel\Layer\Filter $viewModel */
1212
$viewModel = $block->getData('product_layer_view_model');
1313
?>
@@ -16,28 +16,29 @@ $viewModel = $block->getData('product_layer_view_model');
1616
<?php foreach ($filterItems as $filterItem): ?>
1717
<li class="item">
1818
<?php if ($filterItem->getCount() > 0): ?>
19-
<a href="<?= $block->escapeUrl($filterItem->getUrl()) ?>" rel="nofollow">
20-
<?= /* @noEscape */ $filterItem->getLabel() ?>
21-
<?php if ($viewModel->shouldDisplayProductCountOnLayer()): ?>
22-
<span class="count"><?= /* @noEscape */ (int)$filterItem->getCount() ?>
23-
<span class="filter-count-label">
24-
<?php if ($filterItem->getCount() == 1):
25-
?> <?= $block->escapeHtml(__('item')) ?><?php
19+
<a
20+
href="<?= $escaper->escapeUrl($filterItem->getUrl()) ?>"
21+
rel="nofollow"
22+
><?= /* @noEscape */ $filterItem->getLabel() ?><?php
23+
if ($viewModel->shouldDisplayProductCountOnLayer()): ?><span
24+
class="count"><?= /* @noEscape */ (int) $filterItem->getCount() ?><span
25+
class="filter-count-label"><?php
26+
if ($filterItem->getCount() == 1): ?>
27+
<?= $escaper->escapeHtml(__('item')) ?><?php
2628
else:
27-
?> <?= $block->escapeHtml(__('item')) ?><?php
29+
?><?= $escaper->escapeHtml(__('item')) ?><?php
2830
endif;?></span></span>
29-
<?php endif; ?>
30-
</a>
31+
<?php endif; ?></a>
3132
<?php else: ?>
32-
<?= /* @noEscape */ $filterItem->getLabel() ?>
33-
<?php if ($viewModel->shouldDisplayProductCountOnLayer()): ?>
34-
<span class="count"><?= /* @noEscape */ (int)$filterItem->getCount() ?>
35-
<span class="filter-count-label">
36-
<?php if ($filterItem->getCount() == 1):
37-
?><?= $block->escapeHtml(__('items')) ?><?php
38-
else:
39-
?><?= $block->escapeHtml(__('items')) ?><?php
40-
endif;?></span></span>
33+
<?= /* @noEscape */ $filterItem->getLabel() ?><?php
34+
if ($viewModel->shouldDisplayProductCountOnLayer()): ?><span
35+
class="count"><?= /* @noEscape */ (int) $filterItem->getCount() ?><span
36+
class="filter-count-label"><?php
37+
if ($filterItem->getCount() == 1): ?>
38+
<?= $escaper->escapeHtml(__('items')) ?><?php
39+
else:
40+
?><?= $escaper->escapeHtml(__('items')) ?><?php
41+
endif;?></span></span>
4142
<?php endif; ?>
4243
<?php endif; ?>
4344
</li>

app/code/Magento/Search/Model/SynonymAnalyzer.php

+4
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ public function __construct(SynonymReader $synReader)
4242
* 3 => [ 0 => "british", 1 => "english" ],
4343
* 4 => [ 0 => "queen", 1 => "monarch" ]
4444
* ]
45+
*
4546
* @param string $phrase
4647
* @return array
4748
* @throws \Magento\Framework\Exception\LocalizedException
@@ -136,6 +137,9 @@ private function getSearchPattern(array $words): string
136137
{
137138
$patterns = [];
138139
for ($lastItem = count($words); $lastItem > 0; $lastItem--) {
140+
$words = array_map(function ($word) {
141+
return preg_quote($word, '/');
142+
}, $words);
139143
$phrase = implode("\s+", \array_slice($words, 0, $lastItem));
140144
$patterns[] = '^' . $phrase . ',';
141145
$patterns[] = ',' . $phrase . ',';

app/code/Magento/Search/Test/Unit/Model/SynonymAnalyzerTest.php

+2-2
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,9 @@ protected function setUp(): void
4949
*/
5050
public function testGetSynonymsForPhrase()
5151
{
52-
$phrase = 'Elizabeth is the british queen';
52+
$phrase = 'Elizabeth/Angela is the british queen';
5353
$expected = [
54-
0 => [ 0 => "Elizabeth" ],
54+
0 => [ 0 => "Elizabeth/Angela" ],
5555
1 => [ 0 => "is" ],
5656
2 => [ 0 => "the" ],
5757
3 => [ 0 => "british", 1 => "english" ],
+6-6
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
{
2-
"tests": [
3-
"actionGroupArguments",
4-
"deprecatedEntityUsage",
5-
"annotations",
6-
"pauseActionUsage"
7-
]
2+
"tests": [
3+
"actionGroupArguments",
4+
"deprecatedEntityUsage",
5+
"annotations",
6+
"pauseActionUsage"
7+
]
88
}

0 commit comments

Comments
 (0)