Skip to content

Commit

Permalink
OP-543: Content elements translatable
Browse files Browse the repository at this point in the history
  • Loading branch information
jkindly committed Sep 13, 2024
1 parent 5917fc5 commit 1b21440
Show file tree
Hide file tree
Showing 16 changed files with 265 additions and 53 deletions.
95 changes: 47 additions & 48 deletions spec/Renderer/ContentElementRendererStrategySpec.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,79 +10,78 @@
use Sylius\CmsPlugin\Entity\ContentConfigurationInterface;
use Sylius\CmsPlugin\Entity\PageInterface;
use Sylius\CmsPlugin\Renderer\ContentElement\ContentElementRendererInterface;
use Sylius\CmsPlugin\Renderer\ContentElementRendererStrategy;
use Sylius\CmsPlugin\Renderer\ContentElementRendererStrategyInterface;
use Sylius\CmsPlugin\Twig\Parser\ContentParserInterface;
use Sylius\Component\Locale\Context\LocaleContextInterface;

final class ContentElementRendererStrategySpec extends ObjectBehavior
{
public function let(
ContentParserInterface $contentParser,
ContentElementRendererInterface $renderer1,
ContentElementRendererInterface $renderer2,
LocaleContextInterface $localeContext,
ContentElementRendererInterface $renderer
): void {
$this->beConstructedWith($contentParser, [$renderer1, $renderer2]);
}

public function it_is_initializable(): void
{
$this->shouldHaveType(ContentElementRendererStrategy::class);
$this->beConstructedWith($contentParser, $localeContext, [$renderer]);
}

public function it_implements_content_element_renderer_strategy_interface(): void
{
$this->shouldImplement(ContentElementRendererStrategyInterface::class);
}

public function it_renders_content_elements_using_registered_renderers(
ContentParserInterface $contentParser,
ContentElementRendererInterface $renderer1,
ContentElementRendererInterface $renderer2,
BlockInterface $block,
ContentConfigurationInterface $contentElement1,
ContentConfigurationInterface $contentElement2,
public function it_renders_a_page_content_element_correctly(
PageInterface $page,
ContentConfigurationInterface $contentElement,
LocaleContextInterface $localeContext,
ContentElementRendererInterface $renderer,
ContentParserInterface $contentParser
): void {
$block->getContentElements()->willReturn(
new ArrayCollection([$contentElement1->getWrappedObject(), $contentElement2->getWrappedObject()]),
);
$page->getContentElements()->willReturn(new ArrayCollection([$contentElement->getWrappedObject()]));
$localeContext->getLocaleCode()->willReturn('en_US');
$contentElement->getLocale()->willReturn('en_US');

$renderer1->supports($contentElement1)->willReturn(true);
$renderer1->supports($contentElement2)->willReturn(false);
$renderer1->render($contentElement1)->willReturn('Rendered content 1');
$renderer2->supports($contentElement2)->willReturn(true);
$renderer2->supports($contentElement1)->willReturn(false);
$renderer2->render($contentElement2)->willReturn('Rendered content 2');
$renderer->supports($contentElement)->willReturn(true);
$renderer->render($contentElement)->willReturn('<p>Hello World</p>');

$expectedParsedContent = 'Parsed content after rendering';
$contentParser->parse('<p>Hello World</p>')->willReturn('<p>Hello World</p>');

$contentParser->parse('Rendered content 1Rendered content 2')->willReturn($expectedParsedContent);

$this->render($block)->shouldReturn($expectedParsedContent);
$this->render($page)->shouldReturn('<p>Hello World</p>');
}

public function it_renders_content_elements_using_registered_renderers_for_page(
ContentParserInterface $contentParser,
ContentElementRendererInterface $renderer1,
ContentElementRendererInterface $renderer2,
PageInterface $page,
ContentConfigurationInterface $contentElement1,
ContentConfigurationInterface $contentElement2,
public function it_skips_content_element_with_non_matching_locale(
BlockInterface $block,
ContentConfigurationInterface $contentElement,
LocaleContextInterface $localeContext,
ContentParserInterface $contentParser
): void {
$page->getContentElements()->willReturn(
new ArrayCollection([$contentElement1->getWrappedObject(), $contentElement2->getWrappedObject()]),
);
$block->getContentElements()->willReturn(new ArrayCollection([$contentElement]));
$localeContext->getLocaleCode()->willReturn('en_US');
$contentElement->getLocale()->willReturn('fr_FR');

$renderer1->supports($contentElement1)->willReturn(true);
$renderer1->supports($contentElement2)->willReturn(false);
$renderer1->render($contentElement1)->willReturn('Rendered content 1');
$renderer2->supports($contentElement2)->willReturn(true);
$renderer2->supports($contentElement1)->willReturn(false);
$renderer2->render($contentElement2)->willReturn('Rendered content 2');
$contentParser->parse('')->willReturn('');

$this->render($block)->shouldReturn('');
}

public function it_renders_only_supported_content_elements(
BlockInterface $block,
ContentConfigurationInterface $supportedElement,
ContentConfigurationInterface $unsupportedElement,
LocaleContextInterface $localeContext,
ContentElementRendererInterface $renderer,
ContentParserInterface $contentParser
): void {
$block->getContentElements()->willReturn(new ArrayCollection([$supportedElement->getWrappedObject(), $unsupportedElement->getWrappedObject()]));
$localeContext->getLocaleCode()->willReturn('en_US');
$supportedElement->getLocale()->willReturn('en_US');
$unsupportedElement->getLocale()->willReturn('en_US');

$expectedParsedContent = 'Parsed content after rendering';
$renderer->supports($supportedElement)->willReturn(true);
$renderer->render($supportedElement)->willReturn('&lt;p&gt;Supported&lt;/p&gt;');
$renderer->supports($unsupportedElement)->willReturn(false);

$contentParser->parse('Rendered content 1Rendered content 2')->willReturn($expectedParsedContent);
$contentParser->parse('<p>Supported</p>')->willReturn('<p>Supported</p>');

$this->render($page)->shouldReturn($expectedParsedContent);
$this->render($block)->shouldReturn('<p>Supported</p>');
}
}
12 changes: 12 additions & 0 deletions src/Entity/ContentConfiguration.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ class ContentConfiguration implements ContentConfigurationInterface

protected array $configuration = [];

protected ?string $locale = null;

protected ?BlockInterface $block = null;

protected ?PageInterface $page = null;
Expand Down Expand Up @@ -41,6 +43,16 @@ public function setConfiguration(array $configuration): void
$this->configuration = $configuration;
}

public function getLocale(): ?string
{
return $this->locale;
}

public function setLocale(?string $locale): void
{
$this->locale = $locale;
}

public function getBlock(): ?BlockInterface
{
return $this->block;
Expand Down
4 changes: 4 additions & 0 deletions src/Entity/ContentConfigurationInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ public function getConfiguration(): array;

public function setConfiguration(array $configuration): void;

public function getLocale(): ?string;

public function setLocale(?string $locale): void;

public function getBlock(): ?BlockInterface;

public function setBlock(?BlockInterface $block): void;
Expand Down
35 changes: 35 additions & 0 deletions src/Form/Type/BlockType.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,32 @@
use Sylius\Bundle\ResourceBundle\Form\Type\AbstractResourceType;
use Sylius\Bundle\TaxonomyBundle\Form\Type\TaxonAutocompleteChoiceType;
use Sylius\CmsPlugin\Entity\BlockInterface;
use Sylius\Component\Locale\Model\LocaleInterface;
use Sylius\Component\Resource\Repository\RepositoryInterface;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;

final class BlockType extends AbstractResourceType
{
private array $locales = [];

public function __construct(
private RepositoryInterface $localeRepository,
string $dataClass,
array $validationGroups = [],
) {
parent::__construct($dataClass, $validationGroups);

/** @var LocaleInterface[] $locales */
$locales = $this->localeRepository->findAll();
foreach ($locales as $locale) {
$this->locales[$locale->getName()] = $locale->getCode();
}
}

public function buildForm(FormBuilderInterface $builder, array $options): void
{
/** @var BlockInterface $block */
Expand Down Expand Up @@ -50,6 +69,12 @@ public function buildForm(FormBuilderInterface $builder, array $options): void
'allow_delete' => true,
'by_reference' => false,
'required' => false,
'entry_options' => [
'label' => false,
],
'attr' => [
'class' => 'content-elements-container',
],
])
->add('products', ProductAutocompleteChoiceType::class, [
'label' => 'sylius_cms.ui.display_for_products.label',
Expand All @@ -70,7 +95,17 @@ public function buildForm(FormBuilderInterface $builder, array $options): void
'label' => false,
'mapped' => false,
])
->add('locale', ChoiceType::class, [
'choices' => $this->locales,
'mapped' => false,
'label' => 'sylius.ui.locale',
'attr' => [
'class' => 'locale-selector',
]
])
;

PageType::addContentElementLocaleListener($builder);
}

public function getBlockPrefix(): string
Expand Down
2 changes: 2 additions & 0 deletions src/Form/Type/ContentConfigurationType.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use Sylius\Bundle\ResourceBundle\Form\Type\AbstractResourceType;
use Sylius\CmsPlugin\Entity\ContentConfigurationInterface;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
Expand Down Expand Up @@ -38,6 +39,7 @@ public function buildForm(FormBuilderInterface $builder, array $options): void
$defaultActionConfigurationType = $this->actionConfigurationTypes[$defaultActionType];

$builder
->add('locale', HiddenType::class)
->add('type', ChoiceType::class, [
'label' => 'sylius.ui.type',
'choices' => $this->actionTypes,
Expand Down
55 changes: 55 additions & 0 deletions src/Form/Type/PageType.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,35 @@
use Sylius\Bundle\ResourceBundle\Form\Type\AbstractResourceType;
use Sylius\Bundle\ResourceBundle\Form\Type\ResourceTranslationsType;
use Sylius\CmsPlugin\Form\Type\Translation\PageTranslationType;
use Sylius\Component\Locale\Model\LocaleInterface;
use Sylius\Component\Resource\Repository\RepositoryInterface;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use Symfony\Component\Form\Extension\Core\Type\DateTimeType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;

final class PageType extends AbstractResourceType
{
private array $locales = [];

public function __construct(
private RepositoryInterface $localeRepository,
string $dataClass,
array $validationGroups = [],
) {
parent::__construct($dataClass, $validationGroups);

/** @var LocaleInterface[] $locales */
$locales = $this->localeRepository->findAll();
foreach ($locales as $locale) {
$this->locales[$locale->getName()] = $locale->getCode();
}
}

public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
Expand Down Expand Up @@ -57,12 +78,46 @@ public function buildForm(FormBuilderInterface $builder, array $options): void
'allow_delete' => true,
'by_reference' => false,
'required' => false,
'entry_options' => [
'label' => false,
],
'attr' => [
'class' => 'content-elements-container',
],
])
->add('template', TemplatePageAutocompleteChoiceType::class, [
'label' => false,
'mapped' => false,
])
->add('locale', ChoiceType::class, [
'choices' => $this->locales,
'mapped' => false,
'label' => 'sylius.ui.locale',
'attr' => [
'class' => 'locale-selector',
]
])
;

self::addContentElementLocaleListener($builder);
}

public static function addContentElementLocaleListener(FormBuilderInterface $builder): void
{
$builder->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) {
$data = $event->getData();
$selectedLocale = $data['locale'] ?? null;

if (isset($data['contentElements'])) {
foreach ($data['contentElements'] as &$contentElement) {
if (empty($contentElement['locale'])) {
$contentElement['locale'] = $selectedLocale;
}
}
}

$event->setData($data);
});
}

public function getBlockPrefix(): string
Expand Down
31 changes: 31 additions & 0 deletions src/Migrations/Version20240912094638.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

declare(strict_types=1);

namespace Sylius\CmsPlugin\Migrations;

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

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

public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql('ALTER TABLE sylius_cms_content_configuration ADD locale VARCHAR(255) NOT NULL');
}

public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('ALTER TABLE sylius_cms_content_configuration DROP locale');
}
}
6 changes: 6 additions & 0 deletions src/Renderer/ContentElementRendererStrategy.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use Sylius\CmsPlugin\Entity\PageInterface;
use Sylius\CmsPlugin\Renderer\ContentElement\ContentElementRendererInterface;
use Sylius\CmsPlugin\Twig\Parser\ContentParserInterface;
use Sylius\Component\Locale\Context\LocaleContextInterface;

final class ContentElementRendererStrategy implements ContentElementRendererStrategyInterface
{
Expand All @@ -16,6 +17,7 @@ final class ContentElementRendererStrategy implements ContentElementRendererStra
*/
public function __construct(
private ContentParserInterface $contentParser,
private LocaleContextInterface $localeContext,
private iterable $renderers,
) {
}
Expand All @@ -25,6 +27,10 @@ public function render(BlockInterface|PageInterface $item): string
$content = '';

foreach ($item->getContentElements() as $contentElement) {
if ($contentElement->getLocale() !== $this->localeContext->getLocaleCode()) {
continue;
}

foreach ($this->renderers as $renderer) {
if ($renderer->supports($contentElement)) {
$content .= html_entity_decode($renderer->render($contentElement), \ENT_QUOTES);
Expand Down
Loading

0 comments on commit 1b21440

Please sign in to comment.