<?php declare(strict_types=1);
namespace Shopware\Elasticsearch\Product;
use Elasticsearch\Client;
use Shopware\Core\Framework\DataAbstractionLayer\Event\EntityWrittenContainerEvent;
use Shopware\Core\System\CustomField\CustomFieldDefinition;
use Shopware\Core\System\CustomField\CustomFieldTypes;
use Shopware\Elasticsearch\Framework\ElasticsearchHelper;
use Shopware\Elasticsearch\Framework\ElasticsearchOutdatedIndexDetector;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class CustomFieldUpdater implements EventSubscriberInterface
{
private ElasticsearchOutdatedIndexDetector $indexDetector;
private Client $client;
private ElasticsearchHelper $elasticsearchHelper;
/**
* @internal
*/
public function __construct(ElasticsearchOutdatedIndexDetector $indexDetector, Client $client, ElasticsearchHelper $elasticsearchHelper)
{
$this->indexDetector = $indexDetector;
$this->client = $client;
$this->elasticsearchHelper = $elasticsearchHelper;
}
public static function getSubscribedEvents(): array
{
return [
EntityWrittenContainerEvent::class => 'onNewCustomFieldCreated',
];
}
public function onNewCustomFieldCreated(EntityWrittenContainerEvent $containerEvent): void
{
$event = $containerEvent->getEventByEntityName(CustomFieldDefinition::ENTITY_NAME);
if ($event === null) {
return;
}
if (!$this->elasticsearchHelper->allowIndexing()) {
return;
}
$newCreatedFields = [];
foreach ($event->getWriteResults() as $writeResult) {
$existence = $writeResult->getExistence();
if ($existence && $existence->exists()) {
continue;
}
/** @var array<mixed> $esType */
$esType = self::getTypeFromCustomFieldType($writeResult->getProperty('type'));
$newCreatedFields[(string) $writeResult->getProperty('name')] = $esType;
}
if (\count($newCreatedFields) === 0) {
return;
}
$this->createNewFieldsInIndices($newCreatedFields);
}
/**
* @deprecated tag:v6.5.0 - Return type will be changed to not nullable - reason:return-type-change
*
* @return array<mixed>|null
*/
public static function getTypeFromCustomFieldType(string $type): ?array
{
switch ($type) {
case CustomFieldTypes::INT:
return [
'type' => 'long',
];
case CustomFieldTypes::FLOAT:
return [
'type' => 'double',
];
case CustomFieldTypes::BOOL:
return [
'type' => 'boolean',
];
case CustomFieldTypes::DATETIME:
return [
'type' => 'date',
'format' => 'yyyy-MM-dd HH:mm:ss.000',
'ignore_malformed' => true,
];
case CustomFieldTypes::JSON:
return [
'type' => 'object',
'dynamic' => true,
];
case CustomFieldTypes::HTML:
case CustomFieldTypes::TEXT:
return [
'type' => 'text',
];
case CustomFieldTypes::COLORPICKER:
case CustomFieldTypes::ENTITY:
case CustomFieldTypes::MEDIA:
case CustomFieldTypes::SELECT:
case CustomFieldTypes::SWITCH:
default:
return [
'type' => 'keyword',
];
}
}
/**
* @param array<string, array<mixed>> $newCreatedFields
*/
private function createNewFieldsInIndices(array $newCreatedFields): void
{
$indices = $this->indexDetector->getAllUsedIndices();
foreach ($indices as $indexName) {
$body = [
'properties' => [
'customFields' => [
'properties' => $newCreatedFields,
],
],
];
// For some reason, we need to include the includes to prevent merge conflicts.
// This error can happen for example after updating from version <6.4.
$current = $this->client->indices()->get(['index' => $indexName]);
$includes = $current[$indexName]['mappings']['_source']['includes'] ?? [];
if ($includes !== []) {
$body['_source'] = [
'includes' => $includes,
];
}
$this->client->indices()->putMapping([
'index' => $indexName,
'body' => $body,
]);
}
}
}