Skip to content

Commit cbca039

Browse files
authored
Merge pull request #10172 from adobe-commerce-tier-4/PR_2025_11_04_flowers
[Support Tier-4 flowers] 11-04-2025 Regular delivery of bugfixes and improvements
2 parents 9a62604 + ce8ee2a commit cbca039

File tree

27 files changed

+1176
-35
lines changed

27 files changed

+1176
-35
lines changed
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?php
2+
/**
3+
* Copyright 2025 Adobe
4+
* All Rights Reserved.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\Bundle\Model\ResourceModel\Indexer;
9+
10+
use Zend_Db_Expr;
11+
12+
class OptionQtyExpressionProvider
13+
{
14+
/**
15+
* Get expression for calculating available quantity for bundle option.
16+
*
17+
* @return Zend_Db_Expr
18+
*/
19+
public function getExpression(): Zend_Db_Expr
20+
{
21+
return new Zend_Db_Expr('i.qty - cisi.min_qty');
22+
}
23+
}

app/code/Magento/Bundle/Model/ResourceModel/Indexer/Stock.php

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
namespace Magento\Bundle\Model\ResourceModel\Indexer;
99

10+
use Magento\Bundle\Model\Product\Type;
1011
use Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher;
1112
use Magento\CatalogInventory\Model\Indexer\Stock\Action\Full;
1213
use Magento\CatalogInventory\Model\ResourceModel\Indexer\Stock\DefaultStock;
@@ -45,6 +46,7 @@ class Stock extends DefaultStock
4546
* @param ActiveTableSwitcher $activeTableSwitcher
4647
* @param StockStatusSelectBuilder $stockStatusSelectBuilder
4748
* @param BundleOptionStockDataSelectBuilder $bundleOptionStockDataSelectBuilder
49+
* @param OptionQtyExpressionProvider $optionQtyExpressionProvider
4850
* @param string $connectionName
4951
*/
5052
public function __construct(
@@ -55,10 +57,14 @@ public function __construct(
5557
ActiveTableSwitcher $activeTableSwitcher,
5658
StockStatusSelectBuilder $stockStatusSelectBuilder,
5759
BundleOptionStockDataSelectBuilder $bundleOptionStockDataSelectBuilder,
60+
private readonly OptionQtyExpressionProvider $optionQtyExpressionProvider,
5861
$connectionName = null
5962
) {
6063
parent::__construct($context, $tableStrategy, $eavConfig, $scopeConfig, $connectionName);
6164

65+
$this->_typeId = Type::TYPE_CODE;
66+
$this->_isComposite = true;
67+
6268
$this->activeTableSwitcher = $activeTableSwitcher;
6369
$this->stockStatusSelectBuilder = $stockStatusSelectBuilder;
6470
$this->bundleOptionStockDataSelectBuilder = $bundleOptionStockDataSelectBuilder;
@@ -198,11 +204,14 @@ protected function _cleanBundleOptionStockData()
198204
private function getOptionsStatusExpression(): \Zend_Db_Expr
199205
{
200206
$connection = $this->getConnection();
207+
208+
$qtyExpr = $this->optionQtyExpressionProvider->getExpression();
201209
$isAvailableExpr = $connection->getCheckSql(
202-
'bs.selection_can_change_qty = 0 AND bs.selection_qty > i.qty',
210+
'bs.selection_can_change_qty = 0 AND bs.selection_qty > ' . $qtyExpr,
203211
'0',
204212
'i.stock_status'
205213
);
214+
206215
if ($this->stockConfiguration->getBackorders()) {
207216
$backordersExpr = $connection->getCheckSql(
208217
'cisi.use_config_backorders = 0 AND cisi.backorders = 0',
@@ -216,6 +225,7 @@ private function getOptionsStatusExpression(): \Zend_Db_Expr
216225
$isAvailableExpr
217226
);
218227
}
228+
219229
if ($this->stockConfiguration->getManageStock()) {
220230
$statusExpr = $connection->getCheckSql(
221231
'cisi.use_config_manage_stock = 0 AND cisi.manage_stock = 0',

app/code/Magento/CatalogInventory/Model/Adminhtml/Stock/Item.php

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,12 @@
1818
* Catalog Inventory Stock Model for adminhtml area
1919
* @method \Magento\CatalogInventory\Api\Data\StockItemExtensionInterface getExtensionAttributes()
2020
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
21+
* @SuppressWarnings(PHPMD.CookieAndSessionMisuse)
2122
* @api
2223
* @since 100.0.2
2324
*
2425
* @deprecated 100.3.0 Replaced with Multi Source Inventory
26+
* @see Nothing
2527
* @link https://developer.adobe.com/commerce/webapi/rest/inventory/index.html
2628
* @link https://developer.adobe.com/commerce/webapi/rest/inventory/inventory-api-reference.html
2729
*/
@@ -77,7 +79,9 @@ public function __construct(
7779
$resourceCollection,
7880
$data
7981
);
80-
82+
if (!empty($data)) {
83+
$this->setOrigData();
84+
}
8185
$this->groupManagement = $groupManagement;
8286
}
8387

@@ -115,6 +119,8 @@ protected function _hasDefaultNotificationMessage()
115119
}
116120

117121
/**
122+
* Check if model is used in admin area
123+
*
118124
* @return bool
119125
*/
120126
public function hasAdminArea()
@@ -123,6 +129,8 @@ public function hasAdminArea()
123129
}
124130

125131
/**
132+
* Flg to show default notification message
133+
*
126134
* @return bool
127135
* @SuppressWarnings(PHPMD.BooleanGetMethodName)
128136
*/
@@ -143,4 +151,15 @@ public function getIdentities()
143151

144152
return $tags;
145153
}
154+
155+
/**
156+
* @inheritDoc
157+
*/
158+
public function afterLoad()
159+
{
160+
parent::afterLoad();
161+
$this->setOrigData();
162+
163+
return $this;
164+
}
146165
}

app/code/Magento/CatalogSearch/Test/Mftf/Test/SearchEntityResultsTest/QuickSearchProductByAttributeNotSearchableUsedInLayeredNavigationTest.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,10 @@
7070
<argument name="useInLayeredNavigationValue" value="Filterable (no results)"/>
7171
</actionGroup>
7272
<actionGroup ref="AdminProductAttributeSaveActionGroup" stepKey="saveMultiSelectAttribute"/>
73+
<!-- Run reindex to index invalidated indices after changes to the attribute -->
74+
<actionGroup ref="CliIndexerReindexActionGroup" stepKey="reindex">
75+
<argument name="indices" value=""/>
76+
</actionGroup>
7377

7478
<!-- Perform search with attribute value -->
7579
<actionGroup ref="StorefrontOpenHomePageActionGroup" stepKey="goToFrontPage"/>

app/code/Magento/CatalogSearch/etc/search_request.xml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,10 @@
2020
<queryReference clause="must" ref="visibility"/>
2121
</query>
2222
<query xsi:type="matchQuery" value="$search_term$" name="search">
23-
<match field="name" matchCondition="match_phrase_prefix"/>
23+
<match field="*"/>
2424
</query>
2525
<query xsi:type="matchQuery" value="$search_term$" name="partial_search">
26+
<match field="*"/>
2627
<match field="name" matchCondition="match_phrase_prefix"/>
2728
<match field="sku" matchCondition="match_phrase_prefix"/>
2829
</query>

app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/CopySearchableFieldsToSearchField.php

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@ class CopySearchableFieldsToSearchField implements FieldsMappingPreprocessorInte
2020
* List of field types to copy
2121
*/
2222
private const FIELD_TYPES = ['text', 'keyword'];
23+
24+
/**
25+
* @var array
26+
*/
27+
private array $exclude = [];
2328
/**
2429
* Add "copy_to" parameter for default search field to index fields.
2530
*
@@ -31,24 +36,40 @@ class CopySearchableFieldsToSearchField implements FieldsMappingPreprocessorInte
3136
public function process(array $mapping): array
3237
{
3338
foreach ($mapping as $field => $definition) {
34-
if ($this->isSearchable($definition)) {
39+
if ($this->isSearchable((string) $field, $definition)) {
3540
$definition['copy_to'][] = '_search';
3641
$mapping[$field] = $definition;
3742
}
3843
}
44+
// Reset exclude list after processing
45+
$this->exclude = [];
3946
return $mapping;
4047
}
4148

49+
/**
50+
* Add fields to exclude from copying to search field
51+
*
52+
* @param array $fields
53+
* @return void
54+
*/
55+
public function addExclude(array $fields): void
56+
{
57+
$this->exclude += array_fill_keys($fields, true);
58+
}
59+
4260
/**
4361
* Determine if the field is searchable by mapping
4462
*
4563
* The field is searchable if it's indexed and its mapping type is either "text" or "keyword"
4664
*
65+
* @param string $field
4766
* @param array $mapping
4867
* @return bool
4968
*/
50-
private function isSearchable(array $mapping): bool
69+
private function isSearchable(string $field, array $mapping): bool
5170
{
52-
return in_array($mapping['type'] ?? null, self::FIELD_TYPES) && (($mapping['index'] ?? true) !== false);
71+
return in_array($mapping['type'] ?? null, self::FIELD_TYPES)
72+
&& (($mapping['index'] ?? true) !== false)
73+
&& !isset($this->exclude[$field]);
5374
}
5475
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
/**
3+
* Copyright 2025 Adobe
4+
* All Rights Reserved.
5+
*/
6+
7+
declare(strict_types=1);
8+
9+
namespace Magento\Elasticsearch\Model\Adapter\FieldMapper\Product;
10+
11+
interface AttributeFieldsMappingProcessorInterface
12+
{
13+
/**
14+
* Process attribute fields mapping
15+
*
16+
* @param string $attributeCode
17+
* @param array $mapping
18+
* @param array $context
19+
* @return array
20+
*/
21+
public function process(string $attributeCode, array $mapping, array $context = []): array;
22+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<?php
2+
/**
3+
* Copyright 2025 Adobe
4+
* All Rights Reserved.
5+
*/
6+
7+
declare(strict_types=1);
8+
9+
namespace Magento\Elasticsearch\Model\Adapter\FieldMapper\Product;
10+
11+
/**
12+
* Composite processor for attribute fields mapping
13+
*/
14+
class CompositeAttributeFieldsMappingProcessor implements AttributeFieldsMappingProcessorInterface
15+
{
16+
/**
17+
* @param AttributeFieldsMappingProcessorInterface[] $processors
18+
*/
19+
public function __construct(
20+
private readonly array $processors = []
21+
) {
22+
foreach ($processors as $processor) {
23+
if (!$processor instanceof AttributeFieldsMappingProcessorInterface) {
24+
throw new \InvalidArgumentException(
25+
sprintf(
26+
'Instance of %s is expected, got %s instead.',
27+
AttributeFieldsMappingProcessorInterface::class,
28+
get_class($processor)
29+
)
30+
);
31+
}
32+
}
33+
}
34+
35+
/**
36+
* @inheritDoc
37+
*/
38+
public function process(string $attributeCode, array $mapping, array $context = []): array
39+
{
40+
foreach ($this->processors as $processor) {
41+
$mapping = $processor->process($attributeCode, $mapping, $context);
42+
}
43+
return $mapping;
44+
}
45+
}

app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/StaticField.php

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
use Magento\Catalog\Api\Data\ProductAttributeInterface;
1111
use Magento\Eav\Model\Config;
1212
use Magento\Eav\Model\Entity\Attribute\AbstractAttribute;
13+
use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\AttributeFieldsMappingProcessorInterface;
1314
use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\AttributeProvider;
1415
use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldIndex\ConverterInterface
1516
as IndexTypeConverterInterface;
@@ -21,11 +22,13 @@
2122
as FieldTypeResolver;
2223
use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProviderInterface;
2324
use Magento\Elasticsearch\Model\Adapter\FieldMapperInterface;
25+
use Magento\Framework\App\ObjectManager;
2426

2527
/**
2628
* Provide static fields for mapping of product.
2729
* @deprecated Elasticsearch is no longer supported by Adobe
2830
* @see this class will be responsible for ES only
31+
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
2932
*/
3033
class StaticField implements FieldProviderInterface
3134
{
@@ -69,6 +72,11 @@ class StaticField implements FieldProviderInterface
6972
*/
7073
private $excludedAttributes;
7174

75+
/**
76+
* @var AttributeFieldsMappingProcessorInterface
77+
*/
78+
private $attributeFieldsMappingProcessor;
79+
7280
/**
7381
* @param Config $eavConfig
7482
* @param FieldTypeConverterInterface $fieldTypeConverter
@@ -78,6 +86,7 @@ class StaticField implements FieldProviderInterface
7886
* @param AttributeProvider $attributeAdapterProvider
7987
* @param FieldName\ResolverInterface $fieldNameResolver
8088
* @param array $excludedAttributes
89+
* @param AttributeFieldsMappingProcessorInterface|null $attributeFieldsMappingProcessor
8190
*/
8291
public function __construct(
8392
Config $eavConfig,
@@ -87,7 +96,8 @@ public function __construct(
8796
FieldIndexResolver $fieldIndexResolver,
8897
AttributeProvider $attributeAdapterProvider,
8998
FieldName\ResolverInterface $fieldNameResolver,
90-
array $excludedAttributes = []
99+
array $excludedAttributes = [],
100+
?AttributeFieldsMappingProcessorInterface $attributeFieldsMappingProcessor = null
91101
) {
92102
$this->eavConfig = $eavConfig;
93103
$this->fieldTypeConverter = $fieldTypeConverter;
@@ -97,6 +107,8 @@ public function __construct(
97107
$this->attributeAdapterProvider = $attributeAdapterProvider;
98108
$this->fieldNameResolver = $fieldNameResolver;
99109
$this->excludedAttributes = $excludedAttributes;
110+
$this->attributeFieldsMappingProcessor = $attributeFieldsMappingProcessor
111+
?? ObjectManager::getInstance()->get(AttributeFieldsMappingProcessorInterface::class);
100112
}
101113

102114
/**
@@ -211,7 +223,10 @@ public function getField(AbstractAttribute $attribute): array
211223
}
212224
}
213225

214-
return $fieldMapping;
226+
return $this->attributeFieldsMappingProcessor->process(
227+
$attribute->getAttributeCode(),
228+
$fieldMapping
229+
);
215230
}
216231

217232
/**

0 commit comments

Comments
 (0)