From 7bdac1387322dc0d183c9a576d89cd94ce7a7296 Mon Sep 17 00:00:00 2001 From: Lorenzo Ruozzi Date: Wed, 13 Jul 2022 16:40:18 +0200 Subject: [PATCH 1/2] Skip locales not used in any product channels (#137) --- .../TranslatablePropertyValueHandlerSpec.php | 51 ++++++++++++++----- .../TranslatablePropertyValueHandler.php | 17 +++++++ tests/Integration/Product/ImporterTest.php | 2 + 3 files changed, 56 insertions(+), 14 deletions(-) diff --git a/spec/ValueHandler/TranslatablePropertyValueHandlerSpec.php b/spec/ValueHandler/TranslatablePropertyValueHandlerSpec.php index 9dd3912b..85b3fca1 100644 --- a/spec/ValueHandler/TranslatablePropertyValueHandlerSpec.php +++ b/spec/ValueHandler/TranslatablePropertyValueHandlerSpec.php @@ -10,10 +10,12 @@ use Prophecy\Argument; use RuntimeException; use stdClass; -use Sylius\Component\Core\Model\Channel; +use Sylius\Component\Core\Model\ChannelInterface; use Sylius\Component\Core\Model\ProductInterface; use Sylius\Component\Core\Model\ProductTranslationInterface; use Sylius\Component\Core\Model\ProductVariantInterface; +use Sylius\Component\Locale\Model\LocaleInterface; +use Sylius\Component\Locale\Provider\LocaleProviderInterface; use Sylius\Component\Product\Model\ProductVariantTranslationInterface; use Sylius\Component\Resource\Factory\FactoryInterface; use Sylius\Component\Resource\Translation\Provider\TranslationLocaleProviderInterface; @@ -36,7 +38,11 @@ public function let( ProductVariantTranslationInterface $italianProductVariantTranslation, ProductInterface $product, ProductTranslationInterface $englishProductTranslation, - ProductTranslationInterface $italianProductTranslation + ProductTranslationInterface $italianProductTranslation, + LocaleInterface $italianLocale, + LocaleInterface $englishLocale, + ChannelInterface $commerceChannel, + ChannelInterface $supportChannel ): void { $propertyAccessor->isWritable($englishProductVariantTranslation, self::TRANSLATION_PROPERTY_PATH)->willReturn(true); $propertyAccessor->isWritable($italianProductVariantTranslation, self::TRANSLATION_PROPERTY_PATH)->willReturn(true); @@ -44,27 +50,29 @@ public function let( $propertyAccessor->isWritable($italianProductTranslation, self::TRANSLATION_PROPERTY_PATH)->willReturn(true); $localeProvider->getDefinedLocalesCodes()->willReturn(['en_US', 'it_IT']); + $italianLocale->getCode()->willReturn('it_IT'); + $englishLocale->getCode()->willReturn('en_US'); - $productVariant->getProduct()->willReturn($product); - $commerceChannel = new Channel(); - $commerceChannel->setCode('ecommerce'); - $supportChannel = new Channel(); - $supportChannel->setCode('support'); - $product->getChannels()->willReturn(new ArrayCollection([$commerceChannel, $supportChannel])); - $localeProvider->getDefinedLocalesCodes()->willReturn(['en_US', 'it_IT']); + $commerceChannel->getCode()->willReturn('ecommerce'); + $commerceChannel->getLocales()->willReturn(new ArrayCollection([$italianLocale->getWrappedObject(), $englishLocale->getWrappedObject()])); + $supportChannel->getCode()->willReturn('support'); + $supportChannel->getLocales()->willReturn(new ArrayCollection([$italianLocale->getWrappedObject(), $englishLocale->getWrappedObject()])); - $productVariant->getProduct()->willReturn($product); $englishProductVariantTranslation->getLocale()->willReturn('en_US'); $italianProductVariantTranslation->getLocale()->willReturn('it_IT'); $englishProductVariantTranslation->getTranslatable()->willReturn($productVariant); $italianProductVariantTranslation->getTranslatable()->willReturn($productVariant); - $productVariant->getTranslation('en_US')->willReturn($englishProductVariantTranslation); - $productVariant->getTranslation('it_IT')->willReturn($italianProductVariantTranslation); $englishProductTranslation->getLocale()->willReturn('en_US'); $italianProductTranslation->getLocale()->willReturn('it_IT'); + + $product->getChannels()->willReturn(new ArrayCollection([$commerceChannel->getWrappedObject(), $supportChannel->getWrappedObject()])); $product->getTranslation('en_US')->willReturn($englishProductTranslation); $product->getTranslation('it_IT')->willReturn($italianProductTranslation); + $productVariant->getProduct()->willReturn($product); + $productVariant->getTranslation('en_US')->willReturn($englishProductVariantTranslation); + $productVariant->getTranslation('it_IT')->willReturn($italianProductVariantTranslation); + $this->beConstructedWith( $propertyAccessor, $productTranslationFactory, @@ -182,6 +190,21 @@ public function it_skips_locales_not_specified_in_sylius( $propertyAccessor->setValue($productTranslation, self::TRANSLATION_PROPERTY_PATH, 'New value')->shouldNotHaveBeenCalled(); } + public function it_skips_locales_not_used_in_any_product_channels( + ProductVariantInterface $productVariant, + ProductVariantTranslationInterface $productVariantTranslation, + ProductTranslationInterface $productTranslation, + PropertyAccessorInterface $propertyAccessor, + LocaleProviderInterface $localeProvider + ): void { + $localeProvider->getDefinedLocalesCodes()->willReturn(['en_US', 'it_IT', 'es_ES']); + + $this->handle($productVariant, self::AKENEO_ATTRIBUTE_CODE, [['locale' => 'es_ES', 'scope' => null, 'data' => 'New value']]); + + $propertyAccessor->setValue($productVariantTranslation, self::TRANSLATION_PROPERTY_PATH, 'New value')->shouldNotHaveBeenCalled(); + $propertyAccessor->setValue($productTranslation, self::TRANSLATION_PROPERTY_PATH, 'New value')->shouldNotHaveBeenCalled(); + } + public function it_sets_value_on_all_product_translations_when_locale_not_specified( ProductVariantInterface $productVariant, ProductVariantTranslationInterface $englishProductVariantTranslation, @@ -321,14 +344,14 @@ public function it_skips_values_related_to_channels_that_are_not_associated_to_t public function it_throws_when_data_is_not_an_array(ProductVariantInterface $productVariant): void { $this - ->shouldThrow(new \InvalidArgumentException('Invalid Akeneo value data: expected an array, "NULL" given.',)) + ->shouldThrow(new InvalidArgumentException('Invalid Akeneo value data: expected an array, "NULL" given.',)) ->during('handle', [$productVariant, self::AKENEO_ATTRIBUTE_CODE, [null]]); } public function it_throws_when_data_doesnt_contain_scope_info(ProductVariantInterface $productVariant): void { $this - ->shouldThrow(new \InvalidArgumentException('Invalid Akeneo value data: required "scope" information was not found.',)) + ->shouldThrow(new InvalidArgumentException('Invalid Akeneo value data: required "scope" information was not found.',)) ->during( 'handle', [ diff --git a/src/ValueHandler/TranslatablePropertyValueHandler.php b/src/ValueHandler/TranslatablePropertyValueHandler.php index 59699b37..5101de21 100644 --- a/src/ValueHandler/TranslatablePropertyValueHandler.php +++ b/src/ValueHandler/TranslatablePropertyValueHandler.php @@ -104,6 +104,9 @@ public function handle($subject, string $attribute, array $value): void if (!in_array($localeCode, $availableLocalesCodes, true)) { continue; } + if (!$this->isLocaleUsedInAtLeastOneChannelForTheProduct($product, $localeCode)) { + continue; + } $this->setValueOnProductVariantAndProductTranslation($subject, $localeCode, $valueData['data']); } @@ -217,4 +220,18 @@ private function setNullOnExistingProductVariantAndProductTranslation( } } } + + private function isLocaleUsedInAtLeastOneChannelForTheProduct(ProductInterface $product, string $localeCode): bool + { + foreach ($product->getChannels() as $channel) { + Assert::isInstanceOf($channel, \Sylius\Component\Core\Model\ChannelInterface::class); + foreach ($channel->getLocales() as $locale) { + if ($locale->getCode() === $localeCode) { + return true; + } + } + } + + return false; + } } diff --git a/tests/Integration/Product/ImporterTest.php b/tests/Integration/Product/ImporterTest.php index 825dce5e..5cade8c1 100644 --- a/tests/Integration/Product/ImporterTest.php +++ b/tests/Integration/Product/ImporterTest.php @@ -681,8 +681,10 @@ public function it_does_not_fail_with_empty_translations() { $this->fixtureLoader->load( [ + __DIR__ . '/../DataFixtures/ORM/resources/Currency/USD.yaml', __DIR__ . '/../DataFixtures/ORM/resources/Locale/en_US.yaml', __DIR__ . '/../DataFixtures/ORM/resources/Locale/it_IT.yaml', + __DIR__ . '/../DataFixtures/ORM/resources/Channel/usa.yaml', ], [], [], From d2f2ab469a0ca9285894d3899cff8f34dc5ce451 Mon Sep 17 00:00:00 2001 From: Lorenzo Ruozzi Date: Wed, 3 Aug 2022 11:50:10 +0200 Subject: [PATCH 2/2] Skip locales not used in any channels also for immutable slug value handler (#137) --- .../ImmutableSlugValueHandlerSpec.php | 35 ++++++++++++++++++- .../ImmutableSlugValueHandler.php | 18 ++++++++++ 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/spec/ValueHandler/ImmutableSlugValueHandlerSpec.php b/spec/ValueHandler/ImmutableSlugValueHandlerSpec.php index 0b5cbd7d..d1e30952 100644 --- a/spec/ValueHandler/ImmutableSlugValueHandlerSpec.php +++ b/spec/ValueHandler/ImmutableSlugValueHandlerSpec.php @@ -5,11 +5,14 @@ namespace spec\Webgriffe\SyliusAkeneoPlugin\ValueHandler; use Cocur\Slugify\SlugifyInterface; +use Doctrine\Common\Collections\ArrayCollection; use PhpSpec\ObjectBehavior; use Prophecy\Argument; +use Sylius\Component\Core\Model\ChannelInterface; use Sylius\Component\Core\Model\ProductInterface; use Sylius\Component\Core\Model\ProductTranslationInterface; use Sylius\Component\Core\Model\ProductVariantInterface; +use Sylius\Component\Locale\Model\LocaleInterface; use Sylius\Component\Resource\Factory\FactoryInterface; use Sylius\Component\Resource\Repository\RepositoryInterface; use Sylius\Component\Resource\Translation\Provider\TranslationLocaleProviderInterface; @@ -28,12 +31,28 @@ function let( SlugifyInterface $slugify, FactoryInterface $productTranslationFactory, TranslationLocaleProviderInterface $translationLocaleProvider, - RepositoryInterface $productTranslationRepository + RepositoryInterface $productTranslationRepository, + LocaleInterface $italianLocale, + LocaleInterface $englishLocale, + ChannelInterface $commerceChannel, + ChannelInterface $supportChannel, + ProductInterface $product ) { $slugify->slugify(self::VALUE_TO_SLUGIFY)->willReturn(self::SLUGIFIED_VALUE); $translationLocaleProvider->getDefinedLocalesCodes()->willReturn(['en_US', 'it_IT']); $productTranslationRepository->findOneBy(['slug' => self::SLUGIFIED_VALUE, 'locale' => 'en_US'])->willReturn(null); $productTranslationRepository->findOneBy(['slug' => self::SLUGIFIED_VALUE, 'locale' => 'it_IT'])->willReturn(null); + + $italianLocale->getCode()->willReturn('it_IT'); + $englishLocale->getCode()->willReturn('en_US'); + + $commerceChannel->getCode()->willReturn('ecommerce'); + $commerceChannel->getLocales()->willReturn(new ArrayCollection([$italianLocale->getWrappedObject()])); + $supportChannel->getCode()->willReturn('support'); + $supportChannel->getLocales()->willReturn(new ArrayCollection([$italianLocale->getWrappedObject(), $englishLocale->getWrappedObject()])); + + $product->getChannels()->willReturn(new ArrayCollection([$commerceChannel->getWrappedObject(), $supportChannel->getWrappedObject()])); + $this->beConstructedWith( $slugify, $productTranslationFactory, @@ -204,4 +223,18 @@ function it_skips_locales_not_specified_in_sylius( $productTranslation->setSlug(Argument::type('string'))->shouldNotHaveBeenCalled(); } + + public function it_skips_locales_not_used_in_any_product_channels( + ProductVariantInterface $productVariant, + ProductInterface $product, + ProductTranslationInterface $productTranslation, + ChannelInterface $commerceChannel + ): void { + $product->getChannels()->willReturn(new ArrayCollection([$commerceChannel->getWrappedObject()])); + $productVariant->getProduct()->willReturn($product); + + $this->handle($productVariant, self::AKENEO_ATTRIBUTE, [['locale' => 'en_US', 'scope' => null, 'data' => 'New value']]); + + $productTranslation->setSlug(Argument::type('string'))->shouldNotHaveBeenCalled(); + } } diff --git a/src/ValueHandler/ImmutableSlugValueHandler.php b/src/ValueHandler/ImmutableSlugValueHandler.php index 212f9d9f..413455ed 100644 --- a/src/ValueHandler/ImmutableSlugValueHandler.php +++ b/src/ValueHandler/ImmutableSlugValueHandler.php @@ -5,6 +5,7 @@ namespace Webgriffe\SyliusAkeneoPlugin\ValueHandler; use Cocur\Slugify\SlugifyInterface; +use Sylius\Component\Core\Model\ChannelInterface; use Sylius\Component\Core\Model\ProductInterface; use Sylius\Component\Core\Model\ProductTranslationInterface; use Sylius\Component\Core\Model\ProductVariantInterface; @@ -85,6 +86,9 @@ public function handle($subject, string $attribute, array $value): void if (!in_array($localeCode, $this->translationLocaleProvider->getDefinedLocalesCodes(), true)) { continue; } + if (!$this->isLocaleUsedInAtLeastOneChannelForTheProduct($product, $localeCode)) { + continue; + } $productTranslation = $this->getOrCreateNewProductTranslation($product, $localeCode); if ($productTranslation->getSlug() !== null) { @@ -150,4 +154,18 @@ private function getDeduplicatedSlug( return $deduplicatedSlug; } + + private function isLocaleUsedInAtLeastOneChannelForTheProduct(ProductInterface $product, string $localeCode): bool + { + foreach ($product->getChannels() as $channel) { + Assert::isInstanceOf($channel, ChannelInterface::class); + foreach ($channel->getLocales() as $locale) { + if ($locale->getCode() === $localeCode) { + return true; + } + } + } + + return false; + } }