Skip to content

Commit 8c2bd2b

Browse files
merge magento/2.4-develop into magento-engcom/login-as-customer-150
2 parents 88b844e + da50b9b commit 8c2bd2b

File tree

22 files changed

+1204
-119
lines changed

22 files changed

+1204
-119
lines changed

app/code/Magento/Eav/Model/Config.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -839,6 +839,7 @@ protected function _createAttribute($entityType, $attributeData)
839839
}
840840
/** @var AbstractAttribute $attribute */
841841
$attribute = $this->createAttribute($model)->setData($attributeData);
842+
$attribute->setOrigData('entity_type_id', $attribute->getEntityTypeId());
842843
$this->_addAttributeReference(
843844
$attributeData['attribute_id'],
844845
$code,

app/code/Magento/Eav/Test/Unit/Model/ConfigTest.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -223,11 +223,13 @@ public function testGetAttributes($cacheEnabled)
223223
->method('getData')
224224
->willReturn([$attributeData]);
225225
$entityAttributeMock = $this->getMockBuilder(Attribute::class)
226-
->setMethods(['setData', 'load', 'toArray'])
226+
->setMethods(['setData', 'setOrigData', 'load', 'toArray'])
227227
->disableOriginalConstructor()
228228
->getMock();
229229
$entityAttributeMock->method('setData')
230230
->willReturnSelf();
231+
$entityAttributeMock->method('setOrigData')
232+
->willReturn($attributeData);
231233
$entityAttributeMock->method('load')
232234
->willReturnSelf();
233235
$entityAttributeMock->method('toArray')

app/code/Magento/Elasticsearch/Elasticsearch5/Model/Client/Elasticsearch.php

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,23 @@ public function createIndex($index, $settings)
166166
);
167167
}
168168

169+
/**
170+
* Add/update an Elasticsearch index settings.
171+
*
172+
* @param string $index
173+
* @param array $settings
174+
* @return void
175+
*/
176+
public function putIndexSettings(string $index, array $settings): void
177+
{
178+
$this->getClient()->indices()->putSettings(
179+
[
180+
'index' => $index,
181+
'body' => $settings,
182+
]
183+
);
184+
}
185+
169186
/**
170187
* Delete an Elasticsearch index.
171188
*
@@ -348,6 +365,17 @@ private function prepareFieldInfo($fieldInfo)
348365
return $fieldInfo;
349366
}
350367

368+
/**
369+
* Get mapping from Elasticsearch index.
370+
*
371+
* @param array $params
372+
* @return array
373+
*/
374+
public function getMapping(array $params): array
375+
{
376+
return $this->getClient()->indices()->getMapping($params);
377+
}
378+
351379
/**
352380
* Delete mapping in Elasticsearch index
353381
*

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

Lines changed: 132 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,12 @@
66

77
namespace Magento\Elasticsearch\Model\Adapter;
88

9+
use Magento\Catalog\Api\Data\ProductAttributeInterface;
10+
use Magento\Catalog\Api\ProductAttributeRepositoryInterface;
11+
use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\StaticField;
12+
use Magento\Framework\App\ObjectManager;
13+
use Magento\Framework\Stdlib\ArrayManager;
14+
915
/**
1016
* Elasticsearch adapter
1117
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
@@ -69,6 +75,31 @@ class Elasticsearch
6975
*/
7076
private $batchDocumentDataMapper;
7177

78+
/**
79+
* @var array
80+
*/
81+
private $mappedAttributes = [];
82+
83+
/**
84+
* @var string[]
85+
*/
86+
private $indexByCode = [];
87+
88+
/**
89+
* @var ProductAttributeRepositoryInterface
90+
*/
91+
private $productAttributeRepository;
92+
93+
/**
94+
* @var StaticField
95+
*/
96+
private $staticFieldProvider;
97+
98+
/**
99+
* @var ArrayManager
100+
*/
101+
private $arrayManager;
102+
72103
/**
73104
* @param \Magento\Elasticsearch\SearchAdapter\ConnectionManager $connectionManager
74105
* @param FieldMapperInterface $fieldMapper
@@ -78,7 +109,11 @@ class Elasticsearch
78109
* @param Index\IndexNameResolver $indexNameResolver
79110
* @param BatchDataMapperInterface $batchDocumentDataMapper
80111
* @param array $options
112+
* @param ProductAttributeRepositoryInterface|null $productAttributeRepository
113+
* @param StaticField|null $staticFieldProvider
114+
* @param ArrayManager|null $arrayManager
81115
* @throws \Magento\Framework\Exception\LocalizedException
116+
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
82117
*/
83118
public function __construct(
84119
\Magento\Elasticsearch\SearchAdapter\ConnectionManager $connectionManager,
@@ -88,7 +123,10 @@ public function __construct(
88123
\Psr\Log\LoggerInterface $logger,
89124
\Magento\Elasticsearch\Model\Adapter\Index\IndexNameResolver $indexNameResolver,
90125
BatchDataMapperInterface $batchDocumentDataMapper,
91-
$options = []
126+
$options = [],
127+
ProductAttributeRepositoryInterface $productAttributeRepository = null,
128+
StaticField $staticFieldProvider = null,
129+
ArrayManager $arrayManager = null
92130
) {
93131
$this->connectionManager = $connectionManager;
94132
$this->fieldMapper = $fieldMapper;
@@ -97,6 +135,12 @@ public function __construct(
97135
$this->logger = $logger;
98136
$this->indexNameResolver = $indexNameResolver;
99137
$this->batchDocumentDataMapper = $batchDocumentDataMapper;
138+
$this->productAttributeRepository = $productAttributeRepository ?:
139+
ObjectManager::getInstance()->get(ProductAttributeRepositoryInterface::class);
140+
$this->staticFieldProvider = $staticFieldProvider ?:
141+
ObjectManager::getInstance()->get(StaticField::class);
142+
$this->arrayManager = $arrayManager ?:
143+
ObjectManager::getInstance()->get(ArrayManager::class);
100144

101145
try {
102146
$this->client = $this->connectionManager->getConnection($options);
@@ -322,6 +366,93 @@ public function updateAlias($storeId, $mappedIndexerId)
322366
// remove obsolete index
323367
if ($oldIndex) {
324368
$this->client->deleteIndex($oldIndex);
369+
unset($this->indexByCode[$mappedIndexerId . '_' . $storeId]);
370+
}
371+
372+
return $this;
373+
}
374+
375+
/**
376+
* Update Elasticsearch mapping for index.
377+
*
378+
* @param int $storeId
379+
* @param string $mappedIndexerId
380+
* @param string $attributeCode
381+
* @return $this
382+
*/
383+
public function updateIndexMapping(int $storeId, string $mappedIndexerId, string $attributeCode): self
384+
{
385+
$indexName = $this->getIndexFromAlias($storeId, $mappedIndexerId);
386+
if (empty($indexName)) {
387+
return $this;
388+
}
389+
390+
$attribute = $this->productAttributeRepository->get($attributeCode);
391+
$newAttributeMapping = $this->staticFieldProvider->getField($attribute);
392+
$mappedAttributes = $this->getMappedAttributes($indexName);
393+
394+
$attrToUpdate = array_diff_key($newAttributeMapping, $mappedAttributes);
395+
if (!empty($attrToUpdate)) {
396+
$settings['index']['mapping']['total_fields']['limit'] = $this
397+
->getMappingTotalFieldsLimit(array_merge($mappedAttributes, $attrToUpdate));
398+
$this->client->putIndexSettings($indexName, ['settings' => $settings]);
399+
400+
$this->client->addFieldsMapping(
401+
$attrToUpdate,
402+
$indexName,
403+
$this->clientConfig->getEntityType()
404+
);
405+
$this->setMappedAttributes($indexName, $attrToUpdate);
406+
}
407+
408+
return $this;
409+
}
410+
411+
/**
412+
* Retrieve index definition from class.
413+
*
414+
* @param int $storeId
415+
* @param string $mappedIndexerId
416+
* @return string
417+
*/
418+
private function getIndexFromAlias(int $storeId, string $mappedIndexerId): string
419+
{
420+
$indexCode = $mappedIndexerId . '_' . $storeId;
421+
if (!isset($this->indexByCode[$indexCode])) {
422+
$this->indexByCode[$indexCode] = $this->indexNameResolver->getIndexFromAlias($storeId, $mappedIndexerId);
423+
}
424+
425+
return $this->indexByCode[$indexCode];
426+
}
427+
428+
/**
429+
* Retrieve mapped attributes from class.
430+
*
431+
* @param string $indexName
432+
* @return array
433+
*/
434+
private function getMappedAttributes(string $indexName): array
435+
{
436+
if (empty($this->mappedAttributes[$indexName])) {
437+
$mappedAttributes = $this->client->getMapping(['index' => $indexName]);
438+
$pathField = $this->arrayManager->findPath('properties', $mappedAttributes);
439+
$this->mappedAttributes[$indexName] = $this->arrayManager->get($pathField, $mappedAttributes, []);
440+
}
441+
442+
return $this->mappedAttributes[$indexName];
443+
}
444+
445+
/**
446+
* Set mapped attributes to class.
447+
*
448+
* @param string $indexName
449+
* @param array $mappedAttributes
450+
* @return $this
451+
*/
452+
private function setMappedAttributes(string $indexName, array $mappedAttributes): self
453+
{
454+
foreach ($mappedAttributes as $attributeCode => $attributeParams) {
455+
$this->mappedAttributes[$indexName][$attributeCode] = $attributeParams;
325456
}
326457

327458
return $this;

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

Lines changed: 69 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,9 @@
77

88
namespace Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider;
99

10-
use Magento\Eav\Model\Config;
1110
use Magento\Catalog\Api\Data\ProductAttributeInterface;
11+
use Magento\Eav\Model\Config;
12+
use Magento\Eav\Model\Entity\Attribute\AbstractAttribute;
1213
use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\AttributeProvider;
1314
use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldIndex\ConverterInterface
1415
as IndexTypeConverterInterface;
@@ -109,67 +110,82 @@ public function getFields(array $context = []): array
109110
$allAttributes = [];
110111

111112
foreach ($attributes as $attribute) {
112-
if (in_array($attribute->getAttributeCode(), $this->excludedAttributes, true)) {
113-
continue;
114-
}
115-
$attributeAdapter = $this->attributeAdapterProvider->getByAttributeCode($attribute->getAttributeCode());
116-
$fieldName = $this->fieldNameResolver->getFieldName($attributeAdapter);
113+
$allAttributes += $this->getField($attribute);
114+
}
117115

118-
$allAttributes[$fieldName] = [
119-
'type' => $this->fieldTypeResolver->getFieldType($attributeAdapter),
120-
];
116+
$allAttributes['store_id'] = [
117+
'type' => $this->fieldTypeConverter->convert(FieldTypeConverterInterface::INTERNAL_DATA_TYPE_STRING),
118+
'index' => $this->indexTypeConverter->convert(IndexTypeConverterInterface::INTERNAL_NO_INDEX_VALUE),
119+
];
121120

122-
$index = $this->fieldIndexResolver->getFieldIndex($attributeAdapter);
123-
if (null !== $index) {
124-
$allAttributes[$fieldName]['index'] = $index;
125-
}
121+
return $allAttributes;
122+
}
126123

127-
if ($attributeAdapter->isSortable()) {
128-
$sortFieldName = $this->fieldNameResolver->getFieldName(
129-
$attributeAdapter,
130-
['type' => FieldMapperInterface::TYPE_SORT]
131-
);
132-
$allAttributes[$fieldName]['fields'][$sortFieldName] = [
133-
'type' => $this->fieldTypeConverter->convert(
134-
FieldTypeConverterInterface::INTERNAL_DATA_TYPE_KEYWORD
135-
),
136-
'index' => $this->indexTypeConverter->convert(
137-
IndexTypeConverterInterface::INTERNAL_NO_ANALYZE_VALUE
138-
)
139-
];
140-
}
124+
/**
125+
* Get field mapping for specific attribute.
126+
*
127+
* @param AbstractAttribute $attribute
128+
* @return array
129+
*/
130+
public function getField(AbstractAttribute $attribute): array
131+
{
132+
$fieldMapping = [];
133+
if (in_array($attribute->getAttributeCode(), $this->excludedAttributes, true)) {
134+
return $fieldMapping;
135+
}
136+
137+
$attributeAdapter = $this->attributeAdapterProvider->getByAttributeCode($attribute->getAttributeCode());
138+
$fieldName = $this->fieldNameResolver->getFieldName($attributeAdapter);
141139

142-
if ($attributeAdapter->isTextType()) {
143-
$keywordFieldName = FieldTypeConverterInterface::INTERNAL_DATA_TYPE_KEYWORD;
144-
$index = $this->indexTypeConverter->convert(
140+
$fieldMapping[$fieldName] = [
141+
'type' => $this->fieldTypeResolver->getFieldType($attributeAdapter),
142+
];
143+
144+
$index = $this->fieldIndexResolver->getFieldIndex($attributeAdapter);
145+
if (null !== $index) {
146+
$fieldMapping[$fieldName]['index'] = $index;
147+
}
148+
149+
if ($attributeAdapter->isSortable()) {
150+
$sortFieldName = $this->fieldNameResolver->getFieldName(
151+
$attributeAdapter,
152+
['type' => FieldMapperInterface::TYPE_SORT]
153+
);
154+
$fieldMapping[$fieldName]['fields'][$sortFieldName] = [
155+
'type' => $this->fieldTypeConverter->convert(
156+
FieldTypeConverterInterface::INTERNAL_DATA_TYPE_KEYWORD
157+
),
158+
'index' => $this->indexTypeConverter->convert(
145159
IndexTypeConverterInterface::INTERNAL_NO_ANALYZE_VALUE
146-
);
147-
$allAttributes[$fieldName]['fields'][$keywordFieldName] = [
148-
'type' => $this->fieldTypeConverter->convert(
149-
FieldTypeConverterInterface::INTERNAL_DATA_TYPE_KEYWORD
150-
)
151-
];
152-
if ($index) {
153-
$allAttributes[$fieldName]['fields'][$keywordFieldName]['index'] = $index;
154-
}
155-
}
160+
)
161+
];
162+
}
156163

157-
if ($attributeAdapter->isComplexType()) {
158-
$childFieldName = $this->fieldNameResolver->getFieldName(
159-
$attributeAdapter,
160-
['type' => FieldMapperInterface::TYPE_QUERY]
161-
);
162-
$allAttributes[$childFieldName] = [
163-
'type' => $this->fieldTypeConverter->convert(FieldTypeConverterInterface::INTERNAL_DATA_TYPE_STRING)
164-
];
164+
if ($attributeAdapter->isTextType()) {
165+
$keywordFieldName = FieldTypeConverterInterface::INTERNAL_DATA_TYPE_KEYWORD;
166+
$index = $this->indexTypeConverter->convert(
167+
IndexTypeConverterInterface::INTERNAL_NO_ANALYZE_VALUE
168+
);
169+
$fieldMapping[$fieldName]['fields'][$keywordFieldName] = [
170+
'type' => $this->fieldTypeConverter->convert(
171+
FieldTypeConverterInterface::INTERNAL_DATA_TYPE_KEYWORD
172+
)
173+
];
174+
if ($index) {
175+
$fieldMapping[$fieldName]['fields'][$keywordFieldName]['index'] = $index;
165176
}
166177
}
167178

168-
$allAttributes['store_id'] = [
169-
'type' => $this->fieldTypeConverter->convert(FieldTypeConverterInterface::INTERNAL_DATA_TYPE_STRING),
170-
'index' => $this->indexTypeConverter->convert(IndexTypeConverterInterface::INTERNAL_NO_INDEX_VALUE),
171-
];
179+
if ($attributeAdapter->isComplexType()) {
180+
$childFieldName = $this->fieldNameResolver->getFieldName(
181+
$attributeAdapter,
182+
['type' => FieldMapperInterface::TYPE_QUERY]
183+
);
184+
$fieldMapping[$childFieldName] = [
185+
'type' => $this->fieldTypeConverter->convert(FieldTypeConverterInterface::INTERNAL_DATA_TYPE_STRING)
186+
];
187+
}
172188

173-
return $allAttributes;
189+
return $fieldMapping;
174190
}
175191
}

0 commit comments

Comments
 (0)