From 39e71b676ec925be49cd300975ce7b9833a20551 Mon Sep 17 00:00:00 2001 From: Nicolas MELONI Date: Fri, 3 Nov 2023 15:34:24 +0100 Subject: [PATCH 1/2] Import category position --- src/DependencyInjection/Configuration.php | 1 + .../SynoliaSyliusAkeneoExtension.php | 1 + .../Configuration/CategoryConfiguration.php | 19 ++++++++++-- .../CategoryConfigurationInterface.php | 8 +++-- .../Api/CategoryConfigurationProvider.php | 2 ++ src/Task/Category/CreateUpdateEntityTask.php | 29 +++++++++++++++++++ src/Task/Category/RetrieveCategoriesTask.php | 2 ++ 7 files changed, 58 insertions(+), 4 deletions(-) diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php index f69f7eac..83406e82 100644 --- a/src/DependencyInjection/Configuration.php +++ b/src/DependencyInjection/Configuration.php @@ -62,6 +62,7 @@ public function getConfigTreeBuilder() ->arrayNode('excluded_category_codes') ->scalarPrototype()->defaultValue([])->end() ->end() + ->booleanNode('use_akeneo_positions')->defaultFalse()->end() ->end() ->end() diff --git a/src/DependencyInjection/SynoliaSyliusAkeneoExtension.php b/src/DependencyInjection/SynoliaSyliusAkeneoExtension.php index 6457d4e6..6e97e757 100644 --- a/src/DependencyInjection/SynoliaSyliusAkeneoExtension.php +++ b/src/DependencyInjection/SynoliaSyliusAkeneoExtension.php @@ -126,6 +126,7 @@ private function processCategoryConfiguration(ContainerBuilder $container, array $categoryConfigurationProviderDefinition ->setArgument('$categoryCodesToImport', $config['category_configuration']['root_category_codes']) ->setArgument('$categoryCodesToExclude', $config['category_configuration']['excluded_category_codes']) + ->setArgument('$categoryCodesToExclude', $config['category_configuration']['use_akeneo_positions']) ; $container->setAlias(CategoryConfigurationProviderInterface::class, CategoryConfigurationProvider::class); diff --git a/src/Model/Configuration/CategoryConfiguration.php b/src/Model/Configuration/CategoryConfiguration.php index 4e84c426..c54298e3 100644 --- a/src/Model/Configuration/CategoryConfiguration.php +++ b/src/Model/Configuration/CategoryConfiguration.php @@ -6,8 +6,11 @@ class CategoryConfiguration implements CategoryConfigurationInterface { - public function __construct(private array $categoryCodesToImport, private array $categoryCodesToExclude) - { + public function __construct( + private array $categoryCodesToImport, + private array $categoryCodesToExclude, + private bool $useAkeneoPositions, + ) { } public function getCategoryCodesToImport(): array @@ -33,4 +36,16 @@ public function setCategoryCodesToExclude(array $categoryCodesToExclude): self return $this; } + + public function isUseAkeneoPositions(): bool + { + return $this->useAkeneoPositions; + } + + public function setUseAkeneoPositions(bool $useAkeneoPositions): self + { + $this->useAkeneoPositions = $useAkeneoPositions; + + return $this; + } } diff --git a/src/Model/Configuration/CategoryConfigurationInterface.php b/src/Model/Configuration/CategoryConfigurationInterface.php index f8f3e2a3..3ba73110 100644 --- a/src/Model/Configuration/CategoryConfigurationInterface.php +++ b/src/Model/Configuration/CategoryConfigurationInterface.php @@ -10,7 +10,11 @@ public function getCategoryCodesToImport(): array; public function getCategoryCodesToExclude(): array; - public function setCategoryCodesToImport(array $categoryCodesToImport): CategoryConfiguration; + public function setCategoryCodesToImport(array $categoryCodesToImport): self; - public function setCategoryCodesToExclude(array $categoryCodesToExclude): CategoryConfiguration; + public function setCategoryCodesToExclude(array $categoryCodesToExclude): self; + + public function isUseAkeneoPositions(): bool; + + public function setUseAkeneoPositions(bool $useAkeneoPositions): self; } diff --git a/src/Provider/Configuration/Api/CategoryConfigurationProvider.php b/src/Provider/Configuration/Api/CategoryConfigurationProvider.php index e6964d6f..ea80a498 100644 --- a/src/Provider/Configuration/Api/CategoryConfigurationProvider.php +++ b/src/Provider/Configuration/Api/CategoryConfigurationProvider.php @@ -14,6 +14,7 @@ class CategoryConfigurationProvider implements CategoryConfigurationProviderInte public function __construct( private array $categoryCodesToImport, private array $categoryCodesToExclude, + private bool $useAkeneoPositions, ) { } @@ -26,6 +27,7 @@ public function get(): CategoryConfigurationInterface return $this->configuration = new CategoryConfiguration( $this->categoryCodesToImport, $this->categoryCodesToExclude, + $this->useAkeneoPositions, ); } } diff --git a/src/Task/Category/CreateUpdateEntityTask.php b/src/Task/Category/CreateUpdateEntityTask.php index f6cd9395..668f6584 100644 --- a/src/Task/Category/CreateUpdateEntityTask.php +++ b/src/Task/Category/CreateUpdateEntityTask.php @@ -6,6 +6,7 @@ use Behat\Transliterator\Transliterator; use Doctrine\ORM\EntityManagerInterface; +use Gedmo\Sortable\SortableListener; use Psr\Log\LoggerInterface; use Sylius\Component\Core\Model\TaxonInterface; use Sylius\Component\Resource\Factory\FactoryInterface; @@ -24,6 +25,7 @@ use Synolia\SyliusAkeneoPlugin\Logger\Messages; use Synolia\SyliusAkeneoPlugin\Payload\Category\CategoryPayload; use Synolia\SyliusAkeneoPlugin\Payload\PipelinePayloadInterface; +use Synolia\SyliusAkeneoPlugin\Provider\Configuration\Api\CategoryConfigurationProviderInterface; use Synolia\SyliusAkeneoPlugin\Provider\SyliusAkeneoLocaleCodeProvider; use Synolia\SyliusAkeneoPlugin\Repository\TaxonRepository; use Synolia\SyliusAkeneoPlugin\Task\AkeneoTaskInterface; @@ -61,6 +63,7 @@ public function __construct( private FactoryInterface $taxonAttributeValueFactory, private TaxonAttributeTypeMatcher $taxonAttributeTypeMatcher, private TaxonAttributeValueBuilder $taxonAttributeValueBuilder, + private CategoryConfigurationProviderInterface $categoryConfigurationProvider, ) { } @@ -87,6 +90,12 @@ public function __invoke(PipelinePayloadInterface $payload): PipelinePayloadInte $this->assignParent($taxon, $taxons, $resource); + $this->logger->warning('SetPosition', [ + 'taxon_code' => $taxon->getCode(), + 'position' => $resource['position'], + 'positiond' => $taxon->getPosition(), + ]); + foreach ($this->syliusAkeneoLocaleCodeProvider->getUsedLocalesOnBothPlatforms() as $syliusLocale) { $akeneoLocale = $this->syliusAkeneoLocaleCodeProvider->getAkeneoLocale($syliusLocale); @@ -144,6 +153,26 @@ public function __invoke(PipelinePayloadInterface $payload): PipelinePayloadInte $this->dispatcher->dispatch(new AfterProcessingTaxonEvent($resource, $taxon)); + if ($this->categoryConfigurationProvider->get()->isUseAkeneoPositions()) { + $taxon->setPosition($resource['position']); + + foreach ($this->entityManager->getEventManager()->getAllListeners() as $listenerTypes) { + foreach ($listenerTypes as $listener) { + if (get_class($listener) === SortableListener::class) { + $this->entityManager->getEventManager()->removeEventListener([ + 'onFlush', + 'loadClassMetadata', + 'prePersist', + 'postPersist', + 'preUpdate', + 'postRemove', + 'postFlush', + ], $listener); + } + } + } + } + $this->entityManager->flush(); if ($this->entityManager->getConnection()->isTransactionActive()) { diff --git a/src/Task/Category/RetrieveCategoriesTask.php b/src/Task/Category/RetrieveCategoriesTask.php index 52746805..a3b0c858 100644 --- a/src/Task/Category/RetrieveCategoriesTask.php +++ b/src/Task/Category/RetrieveCategoriesTask.php @@ -45,7 +45,9 @@ public function __invoke(PipelinePayloadInterface $payload): PipelinePayloadInte } catch (CommandContextIsNullException) { $queryParameters = []; } + $queryParameters['with_enriched_attributes'] = true; + $queryParameters['with_position'] = true; $queryParameters = \array_merge_recursive($queryParameters, $payload->getCustomFilters()); From 6744eeec5146a9e2d4d47a54eb8f6e5afe49e3f7 Mon Sep 17 00:00:00 2001 From: Nicolas MELONI Date: Mon, 6 Nov 2023 10:09:28 +0100 Subject: [PATCH 2/2] Import category position from Akeneo --- docs/CONFIGURE_DETAIL.md | 3 + install/Application/config/services_test.yaml | 1 + .../SynoliaSyliusAkeneoExtension.php | 2 +- src/Entity/CategoryConfiguration.php | 16 ++ src/Fixture/CategoryConfigurationFixture.php | 4 + src/Form/Type/CategoriesConfigurationType.php | 5 + .../Doctrine/DoctrineSortableManager.php | 40 +++ src/Migrations/Version20231106080715.php | 29 ++ .../Configuration/CategoryConfiguration.php | 2 +- .../CategoryConfigurationInterface.php | 2 +- src/Processor/Category/AttributeProcessor.php | 150 +++++++++++ .../Category/CategoryProcessorChain.php | 37 +++ .../CategoryProcessorChainInterface.php | 12 + .../Category/CategoryProcessorInterface.php | 16 ++ src/Processor/Category/ParentProcessor.php | 40 +++ src/Processor/Category/PositionProcessor.php | 41 +++ .../Category/TranslationProcessor.php | 92 +++++++ src/Resources/config/processors.yaml | 6 + src/Resources/translations/messages.en.yml | 1 + src/Resources/translations/messages.fr.yml | 1 + src/SynoliaSyliusAkeneoPlugin.php | 5 + src/Task/Category/CreateUpdateEntityTask.php | 250 +----------------- src/Task/Category/RetrieveCategoriesTask.php | 5 +- ...onfigurationToApiConnectionTransformer.php | 1 + 24 files changed, 518 insertions(+), 243 deletions(-) create mode 100644 src/Manager/Doctrine/DoctrineSortableManager.php create mode 100644 src/Migrations/Version20231106080715.php create mode 100644 src/Processor/Category/AttributeProcessor.php create mode 100644 src/Processor/Category/CategoryProcessorChain.php create mode 100644 src/Processor/Category/CategoryProcessorChainInterface.php create mode 100644 src/Processor/Category/CategoryProcessorInterface.php create mode 100644 src/Processor/Category/ParentProcessor.php create mode 100644 src/Processor/Category/PositionProcessor.php create mode 100644 src/Processor/Category/TranslationProcessor.php diff --git a/docs/CONFIGURE_DETAIL.md b/docs/CONFIGURE_DETAIL.md index daa438a8..c5f71f42 100644 --- a/docs/CONFIGURE_DETAIL.md +++ b/docs/CONFIGURE_DETAIL.md @@ -53,6 +53,8 @@ The category import configuration contains two configurations. `excluded_category_codes` allows you to choose the categories that you want to exclude from the import. +`use_akeneo_positions` import category position from Akeneo, this will bypass the default sortable event. + **Selecting a parent will exclude the parent and its children**. ```yaml @@ -66,6 +68,7 @@ synolia_sylius_akeneo: - led_tvs - audio_video - mp3_players + use_akeneo_positions: true ``` diff --git a/install/Application/config/services_test.yaml b/install/Application/config/services_test.yaml index f3d5b4a2..feacac92 100644 --- a/install/Application/config/services_test.yaml +++ b/install/Application/config/services_test.yaml @@ -10,4 +10,5 @@ services: arguments: $categoryCodesToImport: [] $categoryCodesToExclude: [] + $useAkeneoPositions: false public: true diff --git a/src/DependencyInjection/SynoliaSyliusAkeneoExtension.php b/src/DependencyInjection/SynoliaSyliusAkeneoExtension.php index 6e97e757..40b30bbf 100644 --- a/src/DependencyInjection/SynoliaSyliusAkeneoExtension.php +++ b/src/DependencyInjection/SynoliaSyliusAkeneoExtension.php @@ -126,7 +126,7 @@ private function processCategoryConfiguration(ContainerBuilder $container, array $categoryConfigurationProviderDefinition ->setArgument('$categoryCodesToImport', $config['category_configuration']['root_category_codes']) ->setArgument('$categoryCodesToExclude', $config['category_configuration']['excluded_category_codes']) - ->setArgument('$categoryCodesToExclude', $config['category_configuration']['use_akeneo_positions']) + ->setArgument('$useAkeneoPositions', $config['category_configuration']['use_akeneo_positions']) ; $container->setAlias(CategoryConfigurationProviderInterface::class, CategoryConfigurationProvider::class); diff --git a/src/Entity/CategoryConfiguration.php b/src/Entity/CategoryConfiguration.php index d60dfc41..9534312c 100644 --- a/src/Entity/CategoryConfiguration.php +++ b/src/Entity/CategoryConfiguration.php @@ -48,6 +48,10 @@ class CategoryConfiguration implements ResourceInterface #[ORM\Column(type: Types::ARRAY)] private array $rootCategories = []; + /** @ORM\Column(type="boolean") */ + #[ORM\Column(type: Types::BOOLEAN)] + private bool $useAkeneoPositions = false; + public function getId(): int { return $this->id; @@ -88,4 +92,16 @@ public function setRootCategories(array $rootCategories): self return $this; } + + public function useAkeneoPositions(): bool + { + return $this->useAkeneoPositions; + } + + public function setUseAkeneoPositions(bool $useAkeneoPositions): self + { + $this->useAkeneoPositions = $useAkeneoPositions; + + return $this; + } } diff --git a/src/Fixture/CategoryConfigurationFixture.php b/src/Fixture/CategoryConfigurationFixture.php index 1b0cef21..110ad37d 100644 --- a/src/Fixture/CategoryConfigurationFixture.php +++ b/src/Fixture/CategoryConfigurationFixture.php @@ -23,6 +23,7 @@ public function load(array $options): void $categoryConfiguration = $this->categoriesConfigurationFactory->createNew(); $categoryConfiguration->setRootCategories($options['root_categories_to_import']); $categoryConfiguration->setNotImportCategories($options['categories_to_exclude']); + $categoryConfiguration->setUseAkeneoPositions($options['use_akeneo_positions']); $this->entityManager->persist($categoryConfiguration); $this->entityManager->flush(); @@ -46,6 +47,9 @@ protected function configureOptionsNode(ArrayNodeDefinition $optionsNode): void ->arrayNode('categories_to_exclude') ->scalarPrototype()->defaultValue([])->end() ->end() + ->arrayNode('use_akeneo_positions') + ->scalarPrototype()->defaultFalse()->end() + ->end() ->end() ; } diff --git a/src/Form/Type/CategoriesConfigurationType.php b/src/Form/Type/CategoriesConfigurationType.php index 01d3558f..019062a9 100644 --- a/src/Form/Type/CategoriesConfigurationType.php +++ b/src/Form/Type/CategoriesConfigurationType.php @@ -5,6 +5,7 @@ namespace Synolia\SyliusAkeneoPlugin\Form\Type; use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\Extension\Core\Type\CheckboxType; use Symfony\Component\Form\Extension\Core\Type\SubmitType; use Symfony\Component\Form\FormBuilderInterface; @@ -26,6 +27,10 @@ public function buildForm(FormBuilderInterface $builder, array $options): void 'required' => false, 'multiple' => true, ]) + ->add('use_akeneo_positions', CheckboxType::class, [ + 'label' => 'sylius.ui.admin.akeneo.categories.use_akeneo_positions', + 'required' => false, + ]) ->add('submit', SubmitType::class, [ 'attr' => ['class' => 'ui icon primary button'], 'label' => 'sylius.ui.admin.akeneo.save', diff --git a/src/Manager/Doctrine/DoctrineSortableManager.php b/src/Manager/Doctrine/DoctrineSortableManager.php new file mode 100644 index 00000000..e783fcc1 --- /dev/null +++ b/src/Manager/Doctrine/DoctrineSortableManager.php @@ -0,0 +1,40 @@ +entityManager->getEventManager()->getListeners() as $eventName => $listeners) { + foreach ($listeners as $listener) { + if ($listener instanceof SortableListener) { + $this->originalEventListeners[$eventName] = $listener; + $this->entityManager->getEventManager()->removeEventListener($eventName, $listener); + } + } + } + } + + public function enableSortableEventListener(): void + { + if ($this->originalEventListeners === []) { + return; + } + + foreach ($this->originalEventListeners as $eventName => $listener) { + $this->entityManager->getEventManager()->addEventListener($eventName, $listener); + } + } +} diff --git a/src/Migrations/Version20231106080715.php b/src/Migrations/Version20231106080715.php new file mode 100644 index 00000000..8e4532b3 --- /dev/null +++ b/src/Migrations/Version20231106080715.php @@ -0,0 +1,29 @@ +addSql('ALTER TABLE akeneo_api_configuration_categories ADD useAkeneoPositions TINYINT(1) NOT NULL'); + } + + public function down(Schema $schema): void + { + $this->addSql('ALTER TABLE akeneo_api_configuration_categories DROP useAkeneoPositions'); + } +} diff --git a/src/Model/Configuration/CategoryConfiguration.php b/src/Model/Configuration/CategoryConfiguration.php index c54298e3..ea5267c8 100644 --- a/src/Model/Configuration/CategoryConfiguration.php +++ b/src/Model/Configuration/CategoryConfiguration.php @@ -37,7 +37,7 @@ public function setCategoryCodesToExclude(array $categoryCodesToExclude): self return $this; } - public function isUseAkeneoPositions(): bool + public function useAkeneoPositions(): bool { return $this->useAkeneoPositions; } diff --git a/src/Model/Configuration/CategoryConfigurationInterface.php b/src/Model/Configuration/CategoryConfigurationInterface.php index 3ba73110..317cd361 100644 --- a/src/Model/Configuration/CategoryConfigurationInterface.php +++ b/src/Model/Configuration/CategoryConfigurationInterface.php @@ -14,7 +14,7 @@ public function setCategoryCodesToImport(array $categoryCodesToImport): self; public function setCategoryCodesToExclude(array $categoryCodesToExclude): self; - public function isUseAkeneoPositions(): bool; + public function useAkeneoPositions(): bool; public function setUseAkeneoPositions(bool $useAkeneoPositions): self; } diff --git a/src/Processor/Category/AttributeProcessor.php b/src/Processor/Category/AttributeProcessor.php new file mode 100644 index 00000000..7698476d --- /dev/null +++ b/src/Processor/Category/AttributeProcessor.php @@ -0,0 +1,150 @@ +getTaxonAttributes( + $attributeValue['attribute_code'], + $attributeValue['type'], + ); + + $taxonAttributeValue = $this->getTaxonAttributeValues( + $taxon, + $taxonAttribute, + $attributeValue['locale'], + ); + + $value = $this->taxonAttributeValueBuilder->build( + $attributeValue['attribute_code'], + $attributeValue['type'], + $attributeValue['locale'], + $attributeValue['channel'], + $attributeValue['data'], + ); + + $taxonAttributeValue->setValue($value); + } catch (UnsupportedAttributeTypeException $e) { + $this->logger->warning($e->getMessage(), [ + 'trace' => $e->getTrace(), + 'exception' => $e, + ]); + } + } + } + + public function support(TaxonInterface $taxon, array $resource): bool + { + return $taxon instanceof TaxonAttributeSubjectInterface && array_key_exists('values', $resource); + } + + private function getTaxonAttributes(string $attributeCode, string $type): TaxonAttributeInterface + { + if (array_key_exists($attributeCode, $this->taxonAttributes)) { + return $this->taxonAttributes[$attributeCode]; + } + + $taxonAttribute = $this->taxonAttributeRepository->findOneBy(['code' => $attributeCode]); + + if ($taxonAttribute instanceof TaxonAttribute) { + $this->taxonAttributes[$attributeCode] = $taxonAttribute; + + return $taxonAttribute; + } + + $matcher = $this->taxonAttributeTypeMatcher->match($type); + + /** @var TaxonAttributeInterface $taxonAttribute */ + $taxonAttribute = $this->taxonAttributeFactory->createNew(); + $taxonAttribute->setCode($attributeCode); + $taxonAttribute->setType($type); + $taxonAttribute->setStorageType($matcher->getAttributeType()->getStorageType()); + $taxonAttribute->setTranslatable(false); + + $this->entityManager->persist($taxonAttribute); + $this->taxonAttributes[$attributeCode] = $taxonAttribute; + + return $taxonAttribute; + } + + private function getTaxonAttributeValues( + TaxonInterface $taxon, + TaxonAttributeInterface $taxonAttribute, + ?string $locale, + ): TaxonAttributeValueInterface { + Assert::string($taxon->getCode()); + Assert::string($taxonAttribute->getCode()); + + if ( + array_key_exists($taxon->getCode(), $this->taxonAttributeValues) && + array_key_exists($taxonAttribute->getCode(), $this->taxonAttributeValues[$taxon->getCode()]) && + array_key_exists($locale ?? 'unknown', $this->taxonAttributeValues[$taxon->getCode()][$taxonAttribute->getCode()]) + ) { + return $this->taxonAttributeValues[$taxon->getCode()][$taxonAttribute->getCode()][$locale ?? 'unknown']; + } + + $taxonAttributeValue = $this->taxonAttributeValueRepository->findOneBy([ + 'subject' => $taxon, + 'attribute' => $taxonAttribute, + 'localeCode' => $locale, + ]); + + if ($taxonAttributeValue instanceof TaxonAttributeValueInterface) { + $this->taxonAttributeValues[$taxon->getCode()][$taxonAttribute->getCode()][$locale ?? 'unknown'] = $taxonAttributeValue; + + return $taxonAttributeValue; + } + + /** @var TaxonAttributeValueInterface $taxonAttributeValue */ + $taxonAttributeValue = $this->taxonAttributeValueFactory->createNew(); + $taxonAttributeValue->setAttribute($taxonAttribute); + $taxonAttributeValue->setTaxon($taxon); + $taxonAttributeValue->setLocaleCode($locale); + $this->entityManager->persist($taxonAttributeValue); + + $this->taxonAttributeValues[$taxon->getCode()][$taxonAttribute->getCode()][$locale ?? 'unknown'] = $taxonAttributeValue; + + return $taxonAttributeValue; + } +} diff --git a/src/Processor/Category/CategoryProcessorChain.php b/src/Processor/Category/CategoryProcessorChain.php new file mode 100644 index 00000000..5601ecdc --- /dev/null +++ b/src/Processor/Category/CategoryProcessorChain.php @@ -0,0 +1,37 @@ + */ + private array $categoryProcessors; + + public function __construct(Traversable $handlers, private LoggerInterface $logger) + { + $this->categoryProcessors = iterator_to_array($handlers); + } + + public function chain(TaxonInterface $taxon, array $resource): void + { + foreach ($this->categoryProcessors as $processor) { + if ($processor->support($taxon, $resource)) { + $this->logger->debug(sprintf('Begin %s', $processor::class), [ + 'taxon_code' => $taxon->getCode(), + ]); + + $processor->process($taxon, $resource); + + $this->logger->debug(sprintf('End %s', $processor::class), [ + 'taxon_code' => $taxon->getCode(), + ]); + } + } + } +} diff --git a/src/Processor/Category/CategoryProcessorChainInterface.php b/src/Processor/Category/CategoryProcessorChainInterface.php new file mode 100644 index 00000000..202376d3 --- /dev/null +++ b/src/Processor/Category/CategoryProcessorChainInterface.php @@ -0,0 +1,12 @@ +taxonRepository->findOneBy(['code' => $resource['parent']]); + + if (!$parent instanceof TaxonInterface) { + return; + } + + $taxon->setParent($parent); + } + + /** + * @inheritdoc + */ + public function support(TaxonInterface $taxon, array $resource): bool + { + return null !== $resource['parent']; + } +} diff --git a/src/Processor/Category/PositionProcessor.php b/src/Processor/Category/PositionProcessor.php new file mode 100644 index 00000000..80b71368 --- /dev/null +++ b/src/Processor/Category/PositionProcessor.php @@ -0,0 +1,41 @@ +setPosition($resource['position']); + + $this->logger->info('Update Taxon Position', [ + 'taxon_code' => $taxon->getCode(), + 'position' => $resource['position'], + ]); + } + + /** + * @inheritdoc + */ + public function support(TaxonInterface $taxon, array $resource): bool + { + return true === $this->categoryConfigurationProvider->get()->useAkeneoPositions() && array_key_exists('position', $resource); + } +} diff --git a/src/Processor/Category/TranslationProcessor.php b/src/Processor/Category/TranslationProcessor.php new file mode 100644 index 00000000..698483af --- /dev/null +++ b/src/Processor/Category/TranslationProcessor.php @@ -0,0 +1,92 @@ +syliusAkeneoLocaleCodeProvider->getUsedLocalesOnBothPlatforms() as $syliusLocale) { + $akeneoLocale = $this->syliusAkeneoLocaleCodeProvider->getAkeneoLocale($syliusLocale); + + $label = \sprintf('[%s]', $resource['code']); + + if (array_key_exists($akeneoLocale, $resource['labels']) && null !== $resource['labels'][$akeneoLocale]) { + $label = $resource['labels'][$akeneoLocale]; + } + + $taxonTranslation = $this->taxonTranslationRepository->findOneBy([ + 'translatable' => $taxon, + 'locale' => $syliusLocale, + ]); + + if (!$taxonTranslation instanceof TaxonTranslationInterface) { + /** @var TaxonTranslationInterface $taxonTranslation */ + $taxonTranslation = $this->taxonTranslationFactory->createNew(); + $taxonTranslation->setLocale($syliusLocale); + $taxonTranslation->setTranslatable($taxon); + $this->entityManager->persist($taxonTranslation); + + $this->logger->notice('Created TaxonTranslation', [ + 'taxon_id' => $taxon->getId() ?? 'unknown', + 'taxon_code' => $taxon->getCode(), + 'locale' => $syliusLocale, + 'akeneo_locale' => $akeneoLocale, + ]); + } + + $taxonTranslation->setName($label); + $slug = Transliterator::transliterate( + str_replace( + '\'', + '-', + sprintf( + '%s-%s', + $resource['code'], + $label, + ), + ), + ); + $taxonTranslation->setSlug($slug); + + $this->logger->notice('Update TaxonTranslation', [ + 'taxon_id' => $taxon->getId() ?? 'unknown', + 'taxon_code' => $taxon->getCode(), + 'locale' => $syliusLocale, + 'akeneo_locale' => $akeneoLocale, + 'name' => $label, + 'slug' => $slug, + ]); + } + } + + public function support(TaxonInterface $taxon, array $resource): bool + { + return true; + } +} diff --git a/src/Resources/config/processors.yaml b/src/Resources/config/processors.yaml index 16d5231c..4a9708f0 100644 --- a/src/Resources/config/processors.yaml +++ b/src/Resources/config/processors.yaml @@ -4,6 +4,12 @@ services: autoconfigure: true public: false + # Taxon Processors + Synolia\SyliusAkeneoPlugin\Processor\Category\CategoryProcessorChainInterface: + class: Synolia\SyliusAkeneoPlugin\Processor\Category\CategoryProcessorChain + arguments: + - !tagged_iterator { tag: !php/const Synolia\SyliusAkeneoPlugin\Processor\Category\CategoryProcessorInterface::TAG_ID, default_priority_method: getDefaultPriority } + # Product Processors Synolia\SyliusAkeneoPlugin\Processor\Product\ProductProcessorChainInterface: class: Synolia\SyliusAkeneoPlugin\Processor\Product\ProductProcessorChain diff --git a/src/Resources/translations/messages.en.yml b/src/Resources/translations/messages.en.yml index f4449496..5f92095a 100644 --- a/src/Resources/translations/messages.en.yml +++ b/src/Resources/translations/messages.en.yml @@ -65,6 +65,7 @@ sylius: main_category: Main category root_categories: Sylius Root categories empty_local_replace_by: Replace empty local by + use_akeneo_positions: Import category position from Akeneo products: title: Products subtitle: Products configuration diff --git a/src/Resources/translations/messages.fr.yml b/src/Resources/translations/messages.fr.yml index f81ea285..c23b1b14 100644 --- a/src/Resources/translations/messages.fr.yml +++ b/src/Resources/translations/messages.fr.yml @@ -65,6 +65,7 @@ sylius: main_category: Catégorie principale root_categories: Catégories racines de Sylius empty_local_replace_by: Remplacer la locale vide par + use_akeneo_positions: Importer la position des catégories d'Akeneo products: title: Produits subtitle: Configuration des produits diff --git a/src/SynoliaSyliusAkeneoPlugin.php b/src/SynoliaSyliusAkeneoPlugin.php index 77f1ecf0..d51f1727 100644 --- a/src/SynoliaSyliusAkeneoPlugin.php +++ b/src/SynoliaSyliusAkeneoPlugin.php @@ -21,6 +21,7 @@ use Synolia\SyliusAkeneoPlugin\DependencyInjection\Compiler\AkeneoTaskCompilerPass; use Synolia\SyliusAkeneoPlugin\DependencyInjection\Compiler\AkeneoTaxonAttributeTypeMatcherCompilerPass; use Synolia\SyliusAkeneoPlugin\DependencyInjection\Compiler\AkeneoTaxonAttributeValueBuilderCompilerPass; +use Synolia\SyliusAkeneoPlugin\Processor\Category\CategoryProcessorInterface; use Synolia\SyliusAkeneoPlugin\Processor\Product\ProductProcessorInterface; use Synolia\SyliusAkeneoPlugin\Processor\ProductAttribute\AkeneoAttributeProcessorInterface; use Synolia\SyliusAkeneoPlugin\Processor\ProductAttributeValue\Table\TableProductAttributeValueProcessorInterface; @@ -92,6 +93,10 @@ public function build(ContainerBuilder $container): void ->registerForAutoconfiguration(AkeneoAttributeProcessorInterface::class) ->addTag(AkeneoAttributeProcessorInterface::TAG_ID) ; + $container + ->registerForAutoconfiguration(CategoryProcessorInterface::class) + ->addTag(CategoryProcessorInterface::TAG_ID) + ; $container ->registerForAutoconfiguration(ProductProcessorInterface::class) ->addTag(ProductProcessorInterface::TAG_ID) diff --git a/src/Task/Category/CreateUpdateEntityTask.php b/src/Task/Category/CreateUpdateEntityTask.php index 668f6584..f24c4aaa 100644 --- a/src/Task/Category/CreateUpdateEntityTask.php +++ b/src/Task/Category/CreateUpdateEntityTask.php @@ -4,34 +4,22 @@ namespace Synolia\SyliusAkeneoPlugin\Task\Category; -use Behat\Transliterator\Transliterator; use Doctrine\ORM\EntityManagerInterface; -use Gedmo\Sortable\SortableListener; use Psr\Log\LoggerInterface; use Sylius\Component\Core\Model\TaxonInterface; -use Sylius\Component\Resource\Factory\FactoryInterface; -use Sylius\Component\Resource\Repository\RepositoryInterface; use Sylius\Component\Taxonomy\Factory\TaxonFactoryInterface; -use Sylius\Component\Taxonomy\Model\TaxonTranslationInterface; use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; -use Synolia\SyliusAkeneoPlugin\Builder\TaxonAttribute\TaxonAttributeValueBuilder; -use Synolia\SyliusAkeneoPlugin\Component\TaxonAttribute\Model\TaxonAttributeSubjectInterface; -use Synolia\SyliusAkeneoPlugin\Entity\TaxonAttribute; -use Synolia\SyliusAkeneoPlugin\Entity\TaxonAttributeInterface; -use Synolia\SyliusAkeneoPlugin\Entity\TaxonAttributeValueInterface; use Synolia\SyliusAkeneoPlugin\Event\Category\AfterProcessingTaxonEvent; use Synolia\SyliusAkeneoPlugin\Event\Category\BeforeProcessingTaxonEvent; -use Synolia\SyliusAkeneoPlugin\Exceptions\UnsupportedAttributeTypeException; use Synolia\SyliusAkeneoPlugin\Logger\Messages; +use Synolia\SyliusAkeneoPlugin\Manager\Doctrine\DoctrineSortableManager; use Synolia\SyliusAkeneoPlugin\Payload\Category\CategoryPayload; use Synolia\SyliusAkeneoPlugin\Payload\PipelinePayloadInterface; +use Synolia\SyliusAkeneoPlugin\Processor\Category\CategoryProcessorChainInterface; use Synolia\SyliusAkeneoPlugin\Provider\Configuration\Api\CategoryConfigurationProviderInterface; -use Synolia\SyliusAkeneoPlugin\Provider\SyliusAkeneoLocaleCodeProvider; use Synolia\SyliusAkeneoPlugin\Repository\TaxonRepository; use Synolia\SyliusAkeneoPlugin\Task\AkeneoTaskInterface; -use Synolia\SyliusAkeneoPlugin\TypeMatcher\TaxonAttribute\TaxonAttributeTypeMatcher; use Throwable; -use Webmozart\Assert\Assert; /** * @internal @@ -44,25 +32,14 @@ final class CreateUpdateEntityTask implements AkeneoTaskInterface private string $type; - private array $taxonAttributes = []; - - private array $taxonAttributeValues = []; - public function __construct( private TaxonFactoryInterface $taxonFactory, private EntityManagerInterface $entityManager, private TaxonRepository $taxonRepository, - private RepositoryInterface $taxonTranslationRepository, - private FactoryInterface $taxonTranslationFactory, private LoggerInterface $logger, private EventDispatcherInterface $dispatcher, - private SyliusAkeneoLocaleCodeProvider $syliusAkeneoLocaleCodeProvider, - private RepositoryInterface $taxonAttributeRepository, - private RepositoryInterface $taxonAttributeValueRepository, - private FactoryInterface $taxonAttributeFactory, - private FactoryInterface $taxonAttributeValueFactory, - private TaxonAttributeTypeMatcher $taxonAttributeTypeMatcher, - private TaxonAttributeValueBuilder $taxonAttributeValueBuilder, + private DoctrineSortableManager $sortableManager, + private CategoryProcessorChainInterface $processorChain, private CategoryConfigurationProviderInterface $categoryConfigurationProvider, ) { } @@ -76,6 +53,10 @@ public function __invoke(PipelinePayloadInterface $payload): PipelinePayloadInte $this->type = $payload->getType(); $this->logger->notice(Messages::createOrUpdate($this->type)); + if (true === $this->categoryConfigurationProvider->get()->useAkeneoPositions()) { + $this->sortableManager->disableSortableEventListener(); + } + foreach ($payload->getResources() as $resource) { try { $this->dispatcher->dispatch(new BeforeProcessingTaxonEvent($resource)); @@ -86,93 +67,10 @@ public function __invoke(PipelinePayloadInterface $payload): PipelinePayloadInte $taxon = $this->getOrCreateEntity($resource['code']); - $taxons[$resource['code']] = $taxon; - - $this->assignParent($taxon, $taxons, $resource); - - $this->logger->warning('SetPosition', [ - 'taxon_code' => $taxon->getCode(), - 'position' => $resource['position'], - 'positiond' => $taxon->getPosition(), - ]); - - foreach ($this->syliusAkeneoLocaleCodeProvider->getUsedLocalesOnBothPlatforms() as $syliusLocale) { - $akeneoLocale = $this->syliusAkeneoLocaleCodeProvider->getAkeneoLocale($syliusLocale); - - $label = \sprintf('[%s]', $resource['code']); - - if (array_key_exists($akeneoLocale, $resource['labels']) && null !== $resource['labels'][$akeneoLocale]) { - $label = $resource['labels'][$akeneoLocale]; - } - - $taxonTranslation = $this->taxonTranslationRepository->findOneBy([ - 'translatable' => $taxon, - 'locale' => $syliusLocale, - ]); - - if (!$taxonTranslation instanceof TaxonTranslationInterface) { - /** @var TaxonTranslationInterface $taxonTranslation */ - $taxonTranslation = $this->taxonTranslationFactory->createNew(); - $taxonTranslation->setLocale($syliusLocale); - $taxonTranslation->setTranslatable($taxon); - $this->entityManager->persist($taxonTranslation); - - $this->logger->notice('Created TaxonTranslation', [ - 'taxon_id' => $taxon->getId() ?? 'unknown', - 'taxon_code' => $taxon->getCode(), - 'locale' => $syliusLocale, - 'akeneo_locale' => $akeneoLocale, - ]); - } - - $taxonTranslation->setName($label); - $slug = Transliterator::transliterate( - str_replace( - '\'', - '-', - sprintf( - '%s-%s', - $resource['code'], - $label, - ), - ), - ); - $taxonTranslation->setSlug($slug); - - $this->logger->notice('Update TaxonTranslation', [ - 'taxon_id' => $taxon->getId() ?? 'unknown', - 'taxon_code' => $taxon->getCode(), - 'locale' => $syliusLocale, - 'akeneo_locale' => $akeneoLocale, - 'name' => $label, - 'slug' => $slug, - ]); - - $this->handleAttributes($taxon, $resource); - } + $this->processorChain->chain($taxon, $resource); $this->dispatcher->dispatch(new AfterProcessingTaxonEvent($resource, $taxon)); - if ($this->categoryConfigurationProvider->get()->isUseAkeneoPositions()) { - $taxon->setPosition($resource['position']); - - foreach ($this->entityManager->getEventManager()->getAllListeners() as $listenerTypes) { - foreach ($listenerTypes as $listener) { - if (get_class($listener) === SortableListener::class) { - $this->entityManager->getEventManager()->removeEventListener([ - 'onFlush', - 'loadClassMetadata', - 'prePersist', - 'postPersist', - 'preUpdate', - 'postRemove', - 'postFlush', - ], $listener); - } - } - } - } - $this->entityManager->flush(); if ($this->entityManager->getConnection()->isTransactionActive()) { @@ -186,25 +84,13 @@ public function __invoke(PipelinePayloadInterface $payload): PipelinePayloadInte } } + $this->sortableManager->enableSortableEventListener(); + $this->logger->notice(Messages::countCreateAndUpdate($this->type, $this->createCount, $this->updateCount)); return $payload; } - private function assignParent(TaxonInterface $taxon, array $taxons, array $resource): void - { - if (null === $resource['parent']) { - return; - } - - $parent = $taxons[$resource['parent']] ?? null; - - if (!$parent instanceof TaxonInterface) { - return; - } - $taxon->setParent($parent); - } - private function getOrCreateEntity(string $code): TaxonInterface { /** @var TaxonInterface $taxon */ @@ -226,118 +112,4 @@ private function getOrCreateEntity(string $code): TaxonInterface return $taxon; } - - private function handleAttributes(TaxonInterface $taxon, array $resource): void - { - if (!$taxon instanceof TaxonAttributeSubjectInterface) { - $this->logger->warning('Missing TaxonAttributeSubjectInterface implementation on Taxon', [ - 'taxon_code' => $taxon->getCode(), - ]); - - return; - } - - if (!array_key_exists('values', $resource)) { - return; - } - - foreach ($resource['values'] as $attributeValue) { - try { - $taxonAttribute = $this->getTaxonAttributes( - $attributeValue['attribute_code'], - $attributeValue['type'], - ); - - $taxonAttributeValue = $this->getTaxonAttributeValues( - $taxon, - $taxonAttribute, - $attributeValue['locale'], - ); - - $value = $this->taxonAttributeValueBuilder->build( - $attributeValue['attribute_code'], - $attributeValue['type'], - $attributeValue['locale'], - $attributeValue['channel'], - $attributeValue['data'], - ); - - $taxonAttributeValue->setValue($value); - } catch (UnsupportedAttributeTypeException $e) { - $this->logger->warning($e->getMessage(), [ - 'trace' => $e->getTrace(), - 'exception' => $e, - ]); - } - } - } - - private function getTaxonAttributes(string $attributeCode, string $type): TaxonAttributeInterface - { - if (array_key_exists($attributeCode, $this->taxonAttributes)) { - return $this->taxonAttributes[$attributeCode]; - } - - $taxonAttribute = $this->taxonAttributeRepository->findOneBy(['code' => $attributeCode]); - - if ($taxonAttribute instanceof TaxonAttribute) { - $this->taxonAttributes[$attributeCode] = $taxonAttribute; - - return $taxonAttribute; - } - - $matcher = $this->taxonAttributeTypeMatcher->match($type); - - /** @var TaxonAttributeInterface $taxonAttribute */ - $taxonAttribute = $this->taxonAttributeFactory->createNew(); - $taxonAttribute->setCode($attributeCode); - $taxonAttribute->setType($type); - $taxonAttribute->setStorageType($matcher->getAttributeType()->getStorageType()); - $taxonAttribute->setTranslatable(false); - - $this->entityManager->persist($taxonAttribute); - $this->taxonAttributes[$attributeCode] = $taxonAttribute; - - return $taxonAttribute; - } - - private function getTaxonAttributeValues( - TaxonInterface $taxon, - TaxonAttributeInterface $taxonAttribute, - ?string $locale, - ): TaxonAttributeValueInterface { - Assert::string($taxon->getCode()); - Assert::string($taxonAttribute->getCode()); - - if ( - array_key_exists($taxon->getCode(), $this->taxonAttributeValues) && - array_key_exists($taxonAttribute->getCode(), $this->taxonAttributeValues[$taxon->getCode()]) && - array_key_exists($locale ?? 'unknown', $this->taxonAttributeValues[$taxon->getCode()][$taxonAttribute->getCode()]) - ) { - return $this->taxonAttributeValues[$taxon->getCode()][$taxonAttribute->getCode()][$locale ?? 'unknown']; - } - - $taxonAttributeValue = $this->taxonAttributeValueRepository->findOneBy([ - 'subject' => $taxon, - 'attribute' => $taxonAttribute, - 'localeCode' => $locale, - ]); - - if ($taxonAttributeValue instanceof TaxonAttributeValueInterface) { - $this->taxonAttributeValues[$taxon->getCode()][$taxonAttribute->getCode()][$locale ?? 'unknown'] = $taxonAttributeValue; - - return $taxonAttributeValue; - } - - /** @var TaxonAttributeValueInterface $taxonAttributeValue */ - $taxonAttributeValue = $this->taxonAttributeValueFactory->createNew(); - $taxonAttributeValue->setAttribute($taxonAttribute); - $taxonAttributeValue->setTaxon($taxon); - $taxonAttributeValue->setLocaleCode($locale); - $this->entityManager->persist($taxonAttributeValue); - - $this->taxonAttributeValues[$taxon->getCode()][$taxonAttribute->getCode()][$locale ?? 'unknown'] = $taxonAttributeValue; - - return $taxonAttributeValue; - } } diff --git a/src/Task/Category/RetrieveCategoriesTask.php b/src/Task/Category/RetrieveCategoriesTask.php index a3b0c858..14b5f88a 100644 --- a/src/Task/Category/RetrieveCategoriesTask.php +++ b/src/Task/Category/RetrieveCategoriesTask.php @@ -47,7 +47,10 @@ public function __invoke(PipelinePayloadInterface $payload): PipelinePayloadInte } $queryParameters['with_enriched_attributes'] = true; - $queryParameters['with_position'] = true; + + if ($this->categoryConfigurationProvider->get()->useAkeneoPositions()) { + $queryParameters['with_position'] = true; + } $queryParameters = \array_merge_recursive($queryParameters, $payload->getCustomFilters()); diff --git a/src/Transformer/Configuration/DatabaseCategoryConfigurationToApiConnectionTransformer.php b/src/Transformer/Configuration/DatabaseCategoryConfigurationToApiConnectionTransformer.php index d4e88670..3724491d 100644 --- a/src/Transformer/Configuration/DatabaseCategoryConfigurationToApiConnectionTransformer.php +++ b/src/Transformer/Configuration/DatabaseCategoryConfigurationToApiConnectionTransformer.php @@ -14,6 +14,7 @@ public function transform(CategoryConfiguration $configuration): CategoryConfigu return new \Synolia\SyliusAkeneoPlugin\Model\Configuration\CategoryConfiguration( $configuration->getRootCategories(), $configuration->getNotImportCategories(), + $configuration->useAkeneoPositions(), ); } }