Skip to content

Commit

Permalink
Merge pull request #173 from synolia/feature/import-categorie-positions
Browse files Browse the repository at this point in the history
Import category position
  • Loading branch information
TheGrimmChester authored Nov 10, 2023
2 parents 95f5ac2 + 6744eee commit 21fcc19
Show file tree
Hide file tree
Showing 26 changed files with 545 additions and 216 deletions.
3 changes: 3 additions & 0 deletions docs/CONFIGURE_DETAIL.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -66,6 +68,7 @@ synolia_sylius_akeneo:
- led_tvs
- audio_video
- mp3_players
use_akeneo_positions: true
```


Expand Down
1 change: 1 addition & 0 deletions install/Application/config/services_test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ services:
arguments:
$categoryCodesToImport: []
$categoryCodesToExclude: []
$useAkeneoPositions: false
public: true
1 change: 1 addition & 0 deletions src/DependencyInjection/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ public function getConfigTreeBuilder()
->arrayNode('excluded_category_codes')
->scalarPrototype()->defaultValue([])->end()
->end()
->booleanNode('use_akeneo_positions')->defaultFalse()->end()
->end()
->end()

Expand Down
1 change: 1 addition & 0 deletions src/DependencyInjection/SynoliaSyliusAkeneoExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -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('$useAkeneoPositions', $config['category_configuration']['use_akeneo_positions'])
;

$container->setAlias(CategoryConfigurationProviderInterface::class, CategoryConfigurationProvider::class);
Expand Down
16 changes: 16 additions & 0 deletions src/Entity/CategoryConfiguration.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
}
4 changes: 4 additions & 0 deletions src/Fixture/CategoryConfigurationFixture.php
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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()
;
}
Expand Down
5 changes: 5 additions & 0 deletions src/Form/Type/CategoriesConfigurationType.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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',
Expand Down
40 changes: 40 additions & 0 deletions src/Manager/Doctrine/DoctrineSortableManager.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?php

declare(strict_types=1);

namespace Synolia\SyliusAkeneoPlugin\Manager\Doctrine;

use Doctrine\ORM\EntityManagerInterface;
use Gedmo\Sortable\SortableListener;

class DoctrineSortableManager
{
private array $originalEventListeners = [];

public function __construct(private EntityManagerInterface $entityManager)
{
}

public function disableSortableEventListener(): void
{
foreach ($this->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);
}
}
}
29 changes: 29 additions & 0 deletions src/Migrations/Version20231106080715.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

declare(strict_types=1);

namespace Synolia\SyliusAkeneoPlugin\Migrations;

use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;

/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20231106080715 extends AbstractMigration
{
public function getDescription(): string
{
return 'Added useAkeneoPositions column';
}

public function up(Schema $schema): void
{
$this->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');
}
}
19 changes: 17 additions & 2 deletions src/Model/Configuration/CategoryConfiguration.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -33,4 +36,16 @@ public function setCategoryCodesToExclude(array $categoryCodesToExclude): self

return $this;
}

public function useAkeneoPositions(): bool
{
return $this->useAkeneoPositions;
}

public function setUseAkeneoPositions(bool $useAkeneoPositions): self
{
$this->useAkeneoPositions = $useAkeneoPositions;

return $this;
}
}
8 changes: 6 additions & 2 deletions src/Model/Configuration/CategoryConfigurationInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 useAkeneoPositions(): bool;

public function setUseAkeneoPositions(bool $useAkeneoPositions): self;
}
150 changes: 150 additions & 0 deletions src/Processor/Category/AttributeProcessor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
<?php

declare(strict_types=1);

namespace Synolia\SyliusAkeneoPlugin\Processor\Category;

use Doctrine\ORM\EntityManagerInterface;
use Psr\Log\LoggerInterface;
use Sylius\Component\Core\Model\TaxonInterface;
use Sylius\Component\Resource\Factory\FactoryInterface;
use Sylius\Component\Resource\Repository\RepositoryInterface;
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\Exceptions\UnsupportedAttributeTypeException;
use Synolia\SyliusAkeneoPlugin\TypeMatcher\TaxonAttribute\TaxonAttributeTypeMatcher;
use Webmozart\Assert\Assert;

class AttributeProcessor implements CategoryProcessorInterface
{
private array $taxonAttributes = [];

private array $taxonAttributeValues = [];

public static function getDefaultPriority(): int
{
return 700;
}

public function __construct(
private LoggerInterface $logger,
private EntityManagerInterface $entityManager,
private RepositoryInterface $taxonAttributeRepository,
private RepositoryInterface $taxonAttributeValueRepository,
private FactoryInterface $taxonAttributeFactory,
private FactoryInterface $taxonAttributeValueFactory,
private TaxonAttributeTypeMatcher $taxonAttributeTypeMatcher,
private TaxonAttributeValueBuilder $taxonAttributeValueBuilder,
) {
}

public function process(TaxonInterface $taxon, array $resource): void
{
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,
]);
}
}
}

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;
}
}
Loading

0 comments on commit 21fcc19

Please sign in to comment.