<?php declare(strict_types=1);
namespace Swag\EnterpriseSearch\Indexing;
use Shopware\Core\Framework\Context;
use Shopware\Core\Framework\DataAbstractionLayer\Dbal\EntityDefinitionQueryHelper;
use Shopware\Core\Framework\DataAbstractionLayer\DefinitionInstanceRegistry;
use Shopware\Core\Framework\DataAbstractionLayer\EntityCollection;
use Shopware\Core\Framework\DataAbstractionLayer\EntityDefinition;
use Shopware\Core\Framework\DataAbstractionLayer\EntityRepositoryInterface;
use Shopware\Core\Framework\DataAbstractionLayer\Field\AssociationField;
use Shopware\Core\Framework\DataAbstractionLayer\Field\BoolField;
use Shopware\Core\Framework\DataAbstractionLayer\Field\Field;
use Shopware\Core\Framework\DataAbstractionLayer\Field\Flag\PrimaryKey;
use Shopware\Core\Framework\DataAbstractionLayer\Field\ManyToManyAssociationField;
use Shopware\Core\Framework\DataAbstractionLayer\Field\StringField;
use Shopware\Core\Framework\DataAbstractionLayer\Field\TranslatedField;
use Shopware\Core\Framework\DataAbstractionLayer\Field\TreePathField;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
use Shopware\Core\System\NumberRange\DataAbstractionLayer\NumberRangeField;
use Swag\EnterpriseSearch\Boosting\BoostingDefinition;
use Swag\EnterpriseSearch\Boosting\BoostingEntity;
use Swag\EnterpriseSearch\Boosting\BoostingProvider;
use Swag\EnterpriseSearch\Boosting\BoostingQueryBuilder;
use Swag\EnterpriseSearch\Relevance\RelevanceBluePrintCollection;
use Swag\EnterpriseSearch\Relevance\RelevanceDefinition;
use Swag\EnterpriseSearch\Relevance\RelevanceRepository;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class FieldProvider implements EventSubscriberInterface
{
/**
* @var array<string, DefinitionFieldCollection>
*/
public $cache = [];
/**
* @var EntityRepositoryInterface
*/
private $salesChannelRepository;
/**
* @var RelevanceRepository
*/
private $relevanceRepository;
/**
* @var BoostingProvider
*/
private $boostingProvider;
/**
* @var DefinitionInstanceRegistry
*/
private $definitionInstanceRegistry;
public function __construct(
RelevanceRepository $relevanceRepository,
BoostingProvider $boostingProvider,
EntityRepositoryInterface $salesChannelRepository,
DefinitionInstanceRegistry $definitionInstanceRegistry
) {
$this->salesChannelRepository = $salesChannelRepository;
$this->relevanceRepository = $relevanceRepository;
$this->boostingProvider = $boostingProvider;
$this->definitionInstanceRegistry = $definitionInstanceRegistry;
}
public function getFieldsForEntityDefinition(EntityDefinition $entityDefinition): DefinitionFieldCollection
{
if (isset($this->cache[$entityDefinition->getEntityName()])) {
return $this->cache[$entityDefinition->getEntityName()];
}
return $this->cache[$entityDefinition->getEntityName()] = $this->getFieldsSettings($entityDefinition);
}
public static function getDefinitionFieldsOfAccessor(EntityDefinition $definition, string $accessor): array
{
$parts = explode('.', $accessor);
if ($definition->getEntityName() === $parts[0]) {
array_shift($parts);
}
$accessorFields = [];
$source = $definition;
foreach ($parts as $part) {
$fields = $source->getFields();
if ($part === 'extensions') {
continue;
}
$field = $fields->get($part);
if ($field instanceof TranslatedField) {
$oldSource = $source;
$source = $source->getTranslationDefinition();
$accessorFields[] = new DefinitionField($oldSource, $field);
$accessorFields[] = new DefinitionField($source, $source->getFields()->get($field->getPropertyName()));
continue;
}
if ($field) {
$accessorFields[] = new DefinitionField($source, $field);
}
if (!$field instanceof AssociationField) {
break;
}
$source = $field->getReferenceDefinition();
if ($field instanceof ManyToManyAssociationField) {
$source = $field->getToManyReferenceDefinition();
}
}
return $accessorFields;
}
public static function getSubscribedEvents(): array
{
return [
RelevanceDefinition::ENTITY_NAME . '.written' => 'clear',
BoostingDefinition::ENTITY_NAME . '.written' => 'clear',
];
}
public function clear(): void
{
$this->cache = [];
}
private function getFieldsSettings(EntityDefinition $definition): DefinitionFieldCollection
{
$salesChannels = $this->getSalesChannels();
$context = Context::createDefaultContext();
$salesChannelIds = array_map(function ($salesChannel) {
return $salesChannel->getId();
}, $salesChannels->getElements());
$relevances = $this->relevanceRepository->getRelevancesBySalesChannelIds($definition, $context, $salesChannelIds);
$boostings = $this->boostingProvider->getActiveBoostingsBySalesChannelIds($definition, $context, $salesChannelIds);
$settingsFields = $this->getFields($definition, $boostings, $relevances);
$fullTextFields = $this->getDefinitionFields($definition);
return new DefinitionFieldCollection(array_merge($settingsFields->getElements(), $fullTextFields->getElements()));
}
private function getDefinitionFields(EntityDefinition $definition): DefinitionFieldCollection
{
$fieldCollection = new DefinitionFieldCollection();
foreach ($definition->getFields() as $field) {
$real = $field;
$isTranslated = $field instanceof TranslatedField;
if ($isTranslated && $definition->getTranslationDefinition()) {
$translationField = EntityDefinitionQueryHelper::getTranslatedField($definition, $real);
$field = $this->handleField($translationField);
} else {
$field = $this->handleField($real);
}
if (!$field) {
continue;
}
$fieldCollection->add(new DefinitionField($definition, $real));
if ($isTranslated && $definition->getTranslationDefinition()) {
$translationDefinition = $definition->getTranslationDefinition();
$fieldCollection->add(new DefinitionField($translationDefinition, $translationDefinition->getFields()->get($real->getPropertyName())));
}
}
return $fieldCollection;
}
private function handleField(Field $field): bool
{
switch (true) {
case $field instanceof StringField:
case $field instanceof NumberRangeField:
case $field instanceof BoolField:
case $field instanceof TreePathField:
case $field instanceof TranslatedField:
case $field->is(PrimaryKey::class):
return true;
default:
return false;
}
}
private function getFields(
EntityDefinition $definition,
array $boostings,
RelevanceBluePrintCollection $relevanceBluePrintCollection
): DefinitionFieldCollection {
$definitionFieldCollection = new DefinitionFieldCollection();
foreach ($relevanceBluePrintCollection as $relevanceEntity) {
$definition = $this->definitionInstanceRegistry->getByEntityName($relevanceEntity->getEntityName());
/** @var Field $field */
foreach (self::getDefinitionFieldsOfAccessor($definition, $relevanceEntity->getFieldName()) as $field) {
$definitionFieldCollection->add($field);
}
}
foreach ($boostings as $boosting) {
$filters = $boosting[BoostingQueryBuilder::FILTER];
/** @var BoostingEntity $boostingEntity */
$boostingEntity = $boosting[BoostingQueryBuilder::BOOSTING];
if (empty($filters)) {
continue;
}
if (!$boostingEntity->isActive() || !$boostingEntity->isValid()) {
continue;
}
$this->addFieldsFilterToCollection($definition, $definitionFieldCollection, $filters);
}
return $definitionFieldCollection;
}
private function addFieldsFilterToCollection(EntityDefinition $definition, DefinitionFieldCollection $collection, array $filters): void
{
foreach ($filters as $key => $value) {
if (\is_array($value)) {
$this->addFieldsFilterToCollection($definition, $collection, $value);
}
if ($key === 'field') {
foreach (self::getDefinitionFieldsOfAccessor($definition, $key) as $field) {
$collection->add($field);
}
}
}
}
private function getSalesChannels(): EntityCollection
{
return $this->salesChannelRepository->search(new Criteria(), Context::createDefaultContext())->getEntities();
}
}