Skip to content

Commit c893121

Browse files
authored
Merge pull request #8874 from adobe-commerce-tier-4/T4-PR-04-08-2024
[Support Tier-4 chittima] 04-08-2024 Regular delivery of bugfixes and improvements
2 parents c6bc830 + e267eaa commit c893121

File tree

15 files changed

+745
-13
lines changed

15 files changed

+745
-13
lines changed
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
<?php
2+
/************************************************************************
3+
*
4+
* Copyright 2024 Adobe
5+
* All Rights Reserved.
6+
*
7+
* NOTICE: All information contained herein is, and remains
8+
* the property of Adobe and its suppliers, if any. The intellectual
9+
* and technical concepts contained herein are proprietary to Adobe
10+
* and its suppliers and are protected by all applicable intellectual
11+
* property laws, including trade secret and copyright laws.
12+
* Dissemination of this information or reproduction of this material
13+
* is strictly forbidden unless prior written permission is obtained
14+
* from Adobe.
15+
* ************************************************************************
16+
*/
17+
declare(strict_types=1);
18+
19+
namespace Magento\Catalog\Model\ResourceModel;
20+
21+
use Exception;
22+
use Magento\Catalog\Api\Data\ProductInterface;
23+
use Magento\Eav\Api\Data\AttributeInterface;
24+
use Magento\Framework\App\ResourceConnection;
25+
use Magento\Framework\EntityManager\MetadataPool;
26+
use Magento\Framework\Exception\CouldNotSaveException;
27+
use Magento\Framework\Exception\LocalizedException;
28+
use Magento\Store\Model\Store;
29+
30+
/**
31+
* Migrate related catalog category and product tables for single store view mode
32+
*/
33+
class CatalogCategoryAndProductResolverOnSingleStoreMode
34+
{
35+
/**
36+
* @param ResourceConnection $resourceConnection
37+
* @param MetadataPool $metadataPool
38+
*/
39+
public function __construct(
40+
private readonly ResourceConnection $resourceConnection,
41+
private readonly MetadataPool $metadataPool
42+
) {
43+
}
44+
45+
/**
46+
* Process the Catalog and Product tables and migrate to single store view mode
47+
*
48+
* @param int $storeId
49+
* @param string $table
50+
* @return void
51+
* @throws CouldNotSaveException
52+
*/
53+
private function process(int $storeId, string $table): void
54+
{
55+
$metadata = $this->metadataPool->getMetadata(ProductInterface::class);
56+
$linkField = $metadata->getLinkField();
57+
$catalogProductTable = $this->resourceConnection->getTableName($table);
58+
59+
$catalogProducts = $this->getCatalogProducts($table, $linkField, $storeId);
60+
$linkFieldIds = [];
61+
$attributeIds = [];
62+
$valueIds = [];
63+
try {
64+
if ($catalogProducts) {
65+
foreach ($catalogProducts as $catalogProduct) {
66+
$linkFieldIds[] = $catalogProduct[$linkField];
67+
$attributeIds[] = $catalogProduct[AttributeInterface::ATTRIBUTE_ID];
68+
$valueIds[] = $catalogProduct['value_id'];
69+
}
70+
$this->massDelete($catalogProductTable, $linkField, $attributeIds, $linkFieldIds);
71+
$this->massUpdate($catalogProductTable, $valueIds);
72+
}
73+
} catch (LocalizedException $e) {
74+
throw new CouldNotSaveException(
75+
__($e->getMessage()),
76+
$e
77+
);
78+
}
79+
}
80+
81+
/**
82+
* Migrate catalog category and product tables
83+
*
84+
* @param int $storeId
85+
* @throws Exception
86+
*/
87+
public function migrateCatalogCategoryAndProductTables(int $storeId): void
88+
{
89+
$connection = $this->resourceConnection->getConnection();
90+
$tables = [
91+
'catalog_category_entity_datetime',
92+
'catalog_category_entity_decimal',
93+
'catalog_category_entity_int',
94+
'catalog_category_entity_text',
95+
'catalog_category_entity_varchar',
96+
'catalog_product_entity_datetime',
97+
'catalog_product_entity_decimal',
98+
'catalog_product_entity_gallery',
99+
'catalog_product_entity_int',
100+
'catalog_product_entity_text',
101+
'catalog_product_entity_varchar',
102+
];
103+
try {
104+
$connection->beginTransaction();
105+
foreach ($tables as $table) {
106+
$this->process($storeId, $table);
107+
}
108+
$connection->commit();
109+
} catch (Exception $exception) {
110+
$connection->rollBack();
111+
}
112+
}
113+
114+
/**
115+
* Delete default store related products
116+
*
117+
* @param string $catalogProductTable
118+
* @param string $linkField
119+
* @param array $attributeIds
120+
* @param array $linkFieldIds
121+
* @return void
122+
*/
123+
private function massDelete(
124+
string $catalogProductTable,
125+
string $linkField,
126+
array $attributeIds,
127+
array $linkFieldIds
128+
): void {
129+
$connection = $this->resourceConnection->getConnection();
130+
131+
$connection->delete(
132+
$catalogProductTable,
133+
[
134+
'store_id = ?' => Store::DEFAULT_STORE_ID,
135+
AttributeInterface::ATTRIBUTE_ID. ' IN(?)' => $attributeIds,
136+
$linkField.' IN(?)' => $linkFieldIds
137+
]
138+
);
139+
}
140+
141+
/**
142+
* Update default store related products
143+
*
144+
* @param string $catalogProductTable
145+
* @param array $valueIds
146+
* @return void
147+
*/
148+
private function massUpdate(string $catalogProductTable, array $valueIds): void
149+
{
150+
$connection = $this->resourceConnection->getConnection();
151+
152+
$connection->update(
153+
$catalogProductTable,
154+
['store_id' => Store::DEFAULT_STORE_ID],
155+
['value_id IN(?)' => $valueIds]
156+
);
157+
}
158+
159+
/**
160+
* Get list of products
161+
*
162+
* @param string $table
163+
* @param string $linkField
164+
* @param int $storeId
165+
* @return array
166+
*/
167+
private function getCatalogProducts(string $table, string $linkField, int $storeId): array
168+
{
169+
$connection = $this->resourceConnection->getConnection();
170+
$catalogProductTable = $this->resourceConnection->getTableName($table);
171+
$select = $connection->select()
172+
->from($catalogProductTable, ['value_id', AttributeInterface::ATTRIBUTE_ID, $linkField])
173+
->where('store_id = ?', $storeId);
174+
return $connection->fetchAll($select);
175+
}
176+
}

app/code/Magento/Catalog/Model/ResourceModel/MediaImageDeleteProcessor.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ public function execute(DataObject $product): void
101101
*/
102102
private function canDeleteImage(string $file): bool
103103
{
104-
return $this->productGallery->countImageUses($file) <= 1;
104+
return $this->productGallery->countImageUses($file) < 1;
105105
}
106106

107107
/**
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
<?php
2+
/************************************************************************
3+
*
4+
* Copyright 2024 Adobe
5+
* All Rights Reserved.
6+
*
7+
* NOTICE: All information contained herein is, and remains
8+
* the property of Adobe and its suppliers, if any. The intellectual
9+
* and technical concepts contained herein are proprietary to Adobe
10+
* and its suppliers and are protected by all applicable intellectual
11+
* property laws, including trade secret and copyright laws.
12+
* Dissemination of this information or reproduction of this material
13+
* is strictly forbidden unless prior written permission is obtained
14+
* from Adobe.
15+
* ************************************************************************
16+
*/
17+
declare(strict_types=1);
18+
19+
namespace Magento\Catalog\Observer;
20+
21+
use Magento\Catalog\Model\Indexer\Category\Product;
22+
use Magento\Catalog\Model\Indexer\Product\Category as ProductCategoryIndexer;
23+
use Magento\Catalog\Model\Indexer\Product\Price\Processor as PriceIndexProcessor;
24+
use Magento\Catalog\Model\ResourceModel\CatalogCategoryAndProductResolverOnSingleStoreMode as Resolver;
25+
use Magento\CatalogRule\Model\Indexer\Rule\RuleProductProcessor;
26+
use Magento\Framework\App\Config\ScopeConfigInterface;
27+
use Magento\Framework\Event\Observer;
28+
use Magento\Framework\Event\ObserverInterface;
29+
use Magento\Framework\Indexer\IndexerRegistry;
30+
use Magento\Store\Model\StoreManager;
31+
use Magento\Store\Model\StoreManagerInterface;
32+
33+
/**
34+
* Move and migrate store level catalog product and category to website level
35+
*/
36+
class MoveStoreLevelCatalogDataToWebsiteScopeOnSingleStoreMode implements ObserverInterface
37+
{
38+
/**
39+
* @param IndexerRegistry $indexerRegistry
40+
* @param ScopeConfigInterface $scopeConfig
41+
* @param StoreManagerInterface $storeManager
42+
* @param Resolver $categoryAndProductResolver
43+
*/
44+
public function __construct(
45+
private readonly IndexerRegistry $indexerRegistry,
46+
private readonly ScopeConfigInterface $scopeConfig,
47+
private readonly StoreManagerInterface $storeManager,
48+
private readonly Resolver $categoryAndProductResolver
49+
) {
50+
}
51+
52+
/**
53+
* @inheritDoc
54+
*/
55+
public function execute(Observer $observer)
56+
{
57+
$changedPaths = (array)$observer->getEvent()->getChangedPaths();
58+
if (in_array(StoreManager::XML_PATH_SINGLE_STORE_MODE_ENABLED, $changedPaths, true)
59+
&& $this->scopeConfig->getValue(StoreManager::XML_PATH_SINGLE_STORE_MODE_ENABLED)
60+
&& $this->storeManager->hasSingleStore()
61+
) {
62+
$store = $this->storeManager->getDefaultStoreView();
63+
if ($store) {
64+
$storeId = $store->getId();
65+
$this->categoryAndProductResolver->migrateCatalogCategoryAndProductTables((int) $storeId);
66+
$this->invalidateIndexer();
67+
}
68+
}
69+
}
70+
71+
/**
72+
* Invalidate related indexer
73+
*/
74+
private function invalidateIndexer(): void
75+
{
76+
$productIndexer = $this->indexerRegistry->get(Product::INDEXER_ID);
77+
$categoryProductIndexer = $this->indexerRegistry->get(ProductCategoryIndexer::INDEXER_ID);
78+
$priceIndexer = $this->indexerRegistry->get(PriceIndexProcessor::INDEXER_ID);
79+
$ruleIndexer = $this->indexerRegistry->get(RuleProductProcessor::INDEXER_ID);
80+
$productIndexer->invalidate();
81+
$categoryProductIndexer->invalidate();
82+
$priceIndexer->invalidate();
83+
$ruleIndexer->invalidate();
84+
}
85+
}

0 commit comments

Comments
 (0)