From 58269d4aedba3b623c1b0aeb7c7a594b153a35b1 Mon Sep 17 00:00:00 2001
From: jkindly <kozupa.jakub@gmail.com>
Date: Tue, 23 Jul 2024 11:56:19 +0200
Subject: [PATCH 01/15] OP-441: Twig function to render Collection

---
 src/Form/Type/CollectionType.php              |  2 -
 .../Collection/CollectionBlocksRenderer.php   | 45 ++++++++++++++++++
 .../Collection/CollectionMediaRenderer.php    | 46 +++++++++++++++++++
 .../Collection/CollectionPagesRenderer.php    | 45 ++++++++++++++++++
 .../CollectionRendererInterface.php           | 20 ++++++++
 src/Renderer/CollectionRendererStrategy.php   | 35 ++++++++++++++
 .../CollectionRendererStrategyInterface.php   | 18 ++++++++
 src/Renderer/PageLinkRenderer.php             | 41 +++++++++++++++++
 src/Renderer/PageLinkRendererInterface.php    | 18 ++++++++
 src/Resolver/CollectionResourceResolver.php   | 40 ++++++++++++++++
 .../CollectionResourceResolverInterface.php   | 18 ++++++++
 .../js/bitbag/bitbag-content-configuration.js |  8 ++--
 src/Resources/config/services/renderer.xml    | 24 ++++++++++
 src/Resources/config/services/resolver.xml    |  5 ++
 src/Resources/config/services/twig.xml        | 12 +++++
 .../views/Shop/Collection/show.html.twig      |  3 ++
 .../views/Shop/Media/Show/file.html.twig      | 18 ++++----
 .../views/Shop/Media/Show/image.html.twig     | 14 +++---
 src/Resources/views/Shop/Page/link.html.twig  |  3 ++
 src/Sorter/SorterById.php                     | 27 +++++++++++
 .../Extension/RenderCollectionExtension.php   | 33 +++++++++++++
 src/Twig/Runtime/RenderCollectionRuntime.php  | 42 +++++++++++++++++
 .../RenderCollectionRuntimeInterface.php      | 16 +++++++
 .../SyliusShopBundle/Homepage/index.html.twig |  1 +
 24 files changed, 514 insertions(+), 20 deletions(-)
 create mode 100644 src/Renderer/Collection/CollectionBlocksRenderer.php
 create mode 100644 src/Renderer/Collection/CollectionMediaRenderer.php
 create mode 100644 src/Renderer/Collection/CollectionPagesRenderer.php
 create mode 100644 src/Renderer/Collection/CollectionRendererInterface.php
 create mode 100644 src/Renderer/CollectionRendererStrategy.php
 create mode 100644 src/Renderer/CollectionRendererStrategyInterface.php
 create mode 100644 src/Renderer/PageLinkRenderer.php
 create mode 100644 src/Renderer/PageLinkRendererInterface.php
 create mode 100755 src/Resolver/CollectionResourceResolver.php
 create mode 100755 src/Resolver/CollectionResourceResolverInterface.php
 create mode 100644 src/Resources/views/Shop/Collection/show.html.twig
 create mode 100644 src/Resources/views/Shop/Page/link.html.twig
 create mode 100644 src/Sorter/SorterById.php
 create mode 100644 src/Twig/Extension/RenderCollectionExtension.php
 create mode 100644 src/Twig/Runtime/RenderCollectionRuntime.php
 create mode 100644 src/Twig/Runtime/RenderCollectionRuntimeInterface.php

diff --git a/src/Form/Type/CollectionType.php b/src/Form/Type/CollectionType.php
index 73f802d9e..6478f89da 100755
--- a/src/Form/Type/CollectionType.php
+++ b/src/Form/Type/CollectionType.php
@@ -10,7 +10,6 @@
 
 namespace BitBag\SyliusCmsPlugin\Form\Type;
 
-use BitBag\SyliusCmsPlugin\Entity\MediaInterface;
 use Sylius\Bundle\ResourceBundle\Form\Type\AbstractResourceType;
 use Symfony\Component\Form\Event\PreSubmitEvent;
 use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
@@ -55,7 +54,6 @@ public function buildForm(FormBuilderInterface $builder, array $options): void
             ->add('media', MediaAutocompleteChoiceType::class, [
                 'label' => 'bitbag_sylius_cms_plugin.ui.media',
                 'multiple' => true,
-                'media_type' => MediaInterface::IMAGE_TYPE,
             ])
             ->addEventListener(FormEvents::PRE_SUBMIT, function (PreSubmitEvent $event): void {
                 $formData = $event->getData();
diff --git a/src/Renderer/Collection/CollectionBlocksRenderer.php b/src/Renderer/Collection/CollectionBlocksRenderer.php
new file mode 100644
index 000000000..584da2568
--- /dev/null
+++ b/src/Renderer/Collection/CollectionBlocksRenderer.php
@@ -0,0 +1,45 @@
+<?php
+
+/*
+ * This file was created by developers working at BitBag
+ * Do you need more information about us and what we do? Visit our https://bitbag.io website!
+ * We are hiring developers from all over the world. Join us and start your new, exciting adventure and become part of us: https://bitbag.io/career
+*/
+
+declare(strict_types=1);
+
+namespace BitBag\SyliusCmsPlugin\Renderer\Collection;
+
+use BitBag\SyliusCmsPlugin\Entity\BlockInterface;
+use BitBag\SyliusCmsPlugin\Entity\CollectionInterface;
+use BitBag\SyliusCmsPlugin\Renderer\ContentElementRendererStrategyInterface;
+use BitBag\SyliusCmsPlugin\Sorter\SorterById;
+use Webmozart\Assert\Assert;
+
+final class CollectionBlocksRenderer implements CollectionRendererInterface
+{
+    public function __construct(private ContentElementRendererStrategyInterface $contentElementRendererStrategy)
+    {
+    }
+
+    public function render(CollectionInterface $collection, ?int $countToRender = null): string
+    {
+        $content = '';
+        $iterator = 0;
+        Assert::notNull($collection->getBlocks());
+        /** @var BlockInterface $block */
+        foreach (SorterById::sort($collection->getBlocks()->toArray()) as $block) {
+            $content .= $this->contentElementRendererStrategy->render($block);
+            if (++$iterator === $countToRender) {
+                break;
+            }
+        }
+
+        return $content;
+    }
+
+    public function supports(CollectionInterface $collection): bool
+    {
+        return $collection->getBlocks()?->count() > 0;
+    }
+}
diff --git a/src/Renderer/Collection/CollectionMediaRenderer.php b/src/Renderer/Collection/CollectionMediaRenderer.php
new file mode 100644
index 000000000..b5c7ee8a3
--- /dev/null
+++ b/src/Renderer/Collection/CollectionMediaRenderer.php
@@ -0,0 +1,46 @@
+<?php
+
+/*
+ * This file was created by developers working at BitBag
+ * Do you need more information about us and what we do? Visit our https://bitbag.io website!
+ * We are hiring developers from all over the world. Join us and start your new, exciting adventure and become part of us: https://bitbag.io/career
+*/
+
+declare(strict_types=1);
+
+namespace BitBag\SyliusCmsPlugin\Renderer\Collection;
+
+use BitBag\SyliusCmsPlugin\Entity\CollectionInterface;
+use BitBag\SyliusCmsPlugin\Entity\MediaInterface;
+use BitBag\SyliusCmsPlugin\Sorter\SorterById;
+use BitBag\SyliusCmsPlugin\Twig\Runtime\RenderMediaRuntimeInterface;
+use Webmozart\Assert\Assert;
+
+final class CollectionMediaRenderer implements CollectionRendererInterface
+{
+    public function __construct(private RenderMediaRuntimeInterface $renderMediaRuntime)
+    {
+    }
+
+    public function render(CollectionInterface $collection, ?int $countToRender = null): string
+    {
+        $content = '';
+        $iterator = 0;
+        Assert::notNull($collection->getMedia());
+        /** @var MediaInterface $media */
+        foreach (SorterById::sort($collection->getMedia()->toArray()) as $media) {
+            Assert::notNull($media->getCode());
+            $content .= $this->renderMediaRuntime->renderMedia($media->getCode());
+            if (++$iterator === $countToRender) {
+                break;
+            }
+        }
+
+        return $content;
+    }
+
+    public function supports(CollectionInterface $collection): bool
+    {
+        return $collection->getMedia()?->count() > 0;
+    }
+}
diff --git a/src/Renderer/Collection/CollectionPagesRenderer.php b/src/Renderer/Collection/CollectionPagesRenderer.php
new file mode 100644
index 000000000..587434e17
--- /dev/null
+++ b/src/Renderer/Collection/CollectionPagesRenderer.php
@@ -0,0 +1,45 @@
+<?php
+
+/*
+ * This file was created by developers working at BitBag
+ * Do you need more information about us and what we do? Visit our https://bitbag.io website!
+ * We are hiring developers from all over the world. Join us and start your new, exciting adventure and become part of us: https://bitbag.io/career
+*/
+
+declare(strict_types=1);
+
+namespace BitBag\SyliusCmsPlugin\Renderer\Collection;
+
+use BitBag\SyliusCmsPlugin\Entity\CollectionInterface;
+use BitBag\SyliusCmsPlugin\Entity\PageInterface;
+use BitBag\SyliusCmsPlugin\Renderer\PageLinkRendererInterface;
+use BitBag\SyliusCmsPlugin\Sorter\SorterById;
+use Webmozart\Assert\Assert;
+
+final class CollectionPagesRenderer implements CollectionRendererInterface
+{
+    public function __construct(private PageLinkRendererInterface $pageLinkRenderer)
+    {
+    }
+
+    public function render(CollectionInterface $collection, ?int $countToRender = null): string
+    {
+        $content = '';
+        $iterator = 0;
+        Assert::notNull($collection->getPages());
+        /** @var PageInterface $page */
+        foreach (SorterById::sort($collection->getPages()->toArray(), 'desc') as $page) {
+            $content .= $this->pageLinkRenderer->render($page);
+            if (++$iterator === $countToRender) {
+                break;
+            }
+        }
+
+        return $content;
+    }
+
+    public function supports(CollectionInterface $collection): bool
+    {
+        return $collection->getPages()?->count() > 0;
+    }
+}
diff --git a/src/Renderer/Collection/CollectionRendererInterface.php b/src/Renderer/Collection/CollectionRendererInterface.php
new file mode 100644
index 000000000..4de98d3dc
--- /dev/null
+++ b/src/Renderer/Collection/CollectionRendererInterface.php
@@ -0,0 +1,20 @@
+<?php
+
+/*
+ * This file was created by developers working at BitBag
+ * Do you need more information about us and what we do? Visit our https://bitbag.io website!
+ * We are hiring developers from all over the world. Join us and start your new, exciting adventure and become part of us: https://bitbag.io/career
+*/
+
+declare(strict_types=1);
+
+namespace BitBag\SyliusCmsPlugin\Renderer\Collection;
+
+use BitBag\SyliusCmsPlugin\Entity\CollectionInterface;
+
+interface CollectionRendererInterface
+{
+    public function render(CollectionInterface $collection, ?int $countToRender = null): string;
+
+    public function supports(CollectionInterface $collection): bool;
+}
diff --git a/src/Renderer/CollectionRendererStrategy.php b/src/Renderer/CollectionRendererStrategy.php
new file mode 100644
index 000000000..5ec435386
--- /dev/null
+++ b/src/Renderer/CollectionRendererStrategy.php
@@ -0,0 +1,35 @@
+<?php
+
+/*
+ * This file was created by developers working at BitBag
+ * Do you need more information about us and what we do? Visit our https://bitbag.io website!
+ * We are hiring developers from all over the world. Join us and start your new, exciting adventure and become part of us: https://bitbag.io/career
+*/
+
+declare(strict_types=1);
+
+namespace BitBag\SyliusCmsPlugin\Renderer;
+
+use BitBag\SyliusCmsPlugin\Entity\CollectionInterface;
+use BitBag\SyliusCmsPlugin\Renderer\Collection\CollectionRendererInterface;
+
+final class CollectionRendererStrategy implements CollectionRendererStrategyInterface
+{
+    /**
+     * @param CollectionRendererInterface[] $renderers
+     */
+    public function __construct(private iterable $renderers)
+    {
+    }
+
+    public function render(CollectionInterface $collection, ?int $countToRender = null): string
+    {
+        foreach ($this->renderers as $renderer) {
+            if ($renderer->supports($collection)) {
+                return $renderer->render($collection, $countToRender);
+            }
+        }
+
+        return '';
+    }
+}
diff --git a/src/Renderer/CollectionRendererStrategyInterface.php b/src/Renderer/CollectionRendererStrategyInterface.php
new file mode 100644
index 000000000..d8c0c606f
--- /dev/null
+++ b/src/Renderer/CollectionRendererStrategyInterface.php
@@ -0,0 +1,18 @@
+<?php
+
+/*
+ * This file was created by developers working at BitBag
+ * Do you need more information about us and what we do? Visit our https://bitbag.io website!
+ * We are hiring developers from all over the world. Join us and start your new, exciting adventure and become part of us: https://bitbag.io/career
+*/
+
+declare(strict_types=1);
+
+namespace BitBag\SyliusCmsPlugin\Renderer;
+
+use BitBag\SyliusCmsPlugin\Entity\CollectionInterface;
+
+interface CollectionRendererStrategyInterface
+{
+    public function render(CollectionInterface $collection, ?int $countToRender = null): string;
+}
diff --git a/src/Renderer/PageLinkRenderer.php b/src/Renderer/PageLinkRenderer.php
new file mode 100644
index 000000000..c218462e6
--- /dev/null
+++ b/src/Renderer/PageLinkRenderer.php
@@ -0,0 +1,41 @@
+<?php
+
+/*
+ * This file was created by developers working at BitBag
+ * Do you need more information about us and what we do? Visit our https://bitbag.io website!
+ * We are hiring developers from all over the world. Join us and start your new, exciting adventure and become part of us: https://bitbag.io/career
+*/
+
+declare(strict_types=1);
+
+namespace BitBag\SyliusCmsPlugin\Renderer;
+
+use BitBag\SyliusCmsPlugin\Entity\PageInterface;
+use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
+use Twig\Environment;
+
+final class PageLinkRenderer implements PageLinkRendererInterface
+{
+    private const DEFAULT_TEMPLATE = '@BitBagSyliusCmsPlugin/Shop/Page/link.html.twig';
+
+    public function __construct(
+        private UrlGeneratorInterface $urlGenerator,
+        private Environment $twig,
+    ) {
+    }
+
+    public function render(PageInterface $page, ?string $template = null): string
+    {
+        return $this->twig->render(
+            $template ?? self::DEFAULT_TEMPLATE,
+            [
+                'link' => $this->urlGenerator->generate(
+                    'bitbag_sylius_cms_plugin_shop_page_show',
+                    ['slug' => $page->getSlug()],
+                    UrlGeneratorInterface::ABSOLUTE_URL,
+                ),
+                'name' => $page->getName(),
+            ],
+        );
+    }
+}
diff --git a/src/Renderer/PageLinkRendererInterface.php b/src/Renderer/PageLinkRendererInterface.php
new file mode 100644
index 000000000..a7a80001b
--- /dev/null
+++ b/src/Renderer/PageLinkRendererInterface.php
@@ -0,0 +1,18 @@
+<?php
+
+/*
+ * This file was created by developers working at BitBag
+ * Do you need more information about us and what we do? Visit our https://bitbag.io website!
+ * We are hiring developers from all over the world. Join us and start your new, exciting adventure and become part of us: https://bitbag.io/career
+*/
+
+declare(strict_types=1);
+
+namespace BitBag\SyliusCmsPlugin\Renderer;
+
+use BitBag\SyliusCmsPlugin\Entity\PageInterface;
+
+interface PageLinkRendererInterface
+{
+    public function render(PageInterface $page, ?string $template = null): string;
+}
diff --git a/src/Resolver/CollectionResourceResolver.php b/src/Resolver/CollectionResourceResolver.php
new file mode 100755
index 000000000..dd2a59b24
--- /dev/null
+++ b/src/Resolver/CollectionResourceResolver.php
@@ -0,0 +1,40 @@
+<?php
+
+/*
+ * This file was created by developers working at BitBag
+ * Do you need more information about us and what we do? Visit our https://bitbag.io website!
+ * We are hiring developers from all over the world. Join us and start your new, exciting adventure and become part of us: https://bitbag.io/career
+*/
+
+declare(strict_types=1);
+
+namespace BitBag\SyliusCmsPlugin\Resolver;
+
+use BitBag\SyliusCmsPlugin\Entity\CollectionInterface;
+use BitBag\SyliusCmsPlugin\Repository\CollectionRepositoryInterface;
+use Psr\Log\LoggerInterface;
+
+final class CollectionResourceResolver implements CollectionResourceResolverInterface
+{
+    public function __construct(
+        private CollectionRepositoryInterface $collectionRepository,
+        private LoggerInterface $logger,
+    ) {
+    }
+
+    public function findOrLog(string $code): ?CollectionInterface
+    {
+        $collection = $this->collectionRepository->findOneByCode($code);
+
+        if (false === $collection instanceof CollectionInterface) {
+            $this->logger->warning(sprintf(
+                'Collection with "%s" code was not found in the database.',
+                $code,
+            ));
+
+            return null;
+        }
+
+        return $collection;
+    }
+}
diff --git a/src/Resolver/CollectionResourceResolverInterface.php b/src/Resolver/CollectionResourceResolverInterface.php
new file mode 100755
index 000000000..4ea8827e0
--- /dev/null
+++ b/src/Resolver/CollectionResourceResolverInterface.php
@@ -0,0 +1,18 @@
+<?php
+
+/*
+ * This file was created by developers working at BitBag
+ * Do you need more information about us and what we do? Visit our https://bitbag.io website!
+ * We are hiring developers from all over the world. Join us and start your new, exciting adventure and become part of us: https://bitbag.io/career
+*/
+
+declare(strict_types=1);
+
+namespace BitBag\SyliusCmsPlugin\Resolver;
+
+use BitBag\SyliusCmsPlugin\Entity\CollectionInterface;
+
+interface CollectionResourceResolverInterface
+{
+    public function findOrLog(string $code): ?CollectionInterface;
+}
diff --git a/src/Resources/assets/admin/js/bitbag/bitbag-content-configuration.js b/src/Resources/assets/admin/js/bitbag/bitbag-content-configuration.js
index ee45066fa..b221f2ca4 100644
--- a/src/Resources/assets/admin/js/bitbag/bitbag-content-configuration.js
+++ b/src/Resources/assets/admin/js/bitbag/bitbag-content-configuration.js
@@ -5,6 +5,10 @@
 */
 
 $(document).ready(function() {
+    $('.bitbag-media-autocomplete, .sylius-autocomplete').each((index, element) => {
+        $(element).autoComplete();
+    });
+
     let pageElements = '#bitbag_sylius_cms_plugin_page_contentElements';
     let blockElements = '#bitbag_sylius_cms_plugin_block_contentElements';
 
@@ -69,10 +73,6 @@ $(document).ready(function() {
         }
     });
 
-    $('.bitbag-media-autocomplete, .sylius-autocomplete').each((index, element) => {
-        $(element).autoComplete();
-    });
-
     $(`${collectionHolder} [data-form-collection="item"]`).each((index, element) => {
         $(document).loadContentConfiguration(element);
     });
diff --git a/src/Resources/config/services/renderer.xml b/src/Resources/config/services/renderer.xml
index 2f2fba36b..a0943d659 100644
--- a/src/Resources/config/services/renderer.xml
+++ b/src/Resources/config/services/renderer.xml
@@ -64,5 +64,29 @@
             <argument type="service" id="sylius.repository.taxon" />
             <tag name="bitbag_sylius_cms_plugin.renderer.content_element" />
         </service>
+
+        <service id="bitbag_sylius_cms_plugin.collection_renderer_strategy" class="BitBag\SyliusCmsPlugin\Renderer\CollectionRendererStrategy">
+            <argument type="tagged_iterator" tag="bitbag_sylius_cms_plugin.renderer.collection" />
+        </service>
+
+        <service id="bitbag_sylius_cms_plugin.renderer.collection.blocks" class="BitBag\SyliusCmsPlugin\Renderer\Collection\CollectionBlocksRenderer">
+            <argument type="service" id="bitbag_sylius_cms_plugin.content_element_renderer_strategy" />
+            <tag name="bitbag_sylius_cms_plugin.renderer.collection" />
+        </service>
+
+        <service id="bitbag_sylius_cms_plugin.renderer.collection.media" class="BitBag\SyliusCmsPlugin\Renderer\Collection\CollectionMediaRenderer">
+            <argument type="service" id="bitbag_sylius_cms_plugin.twig.runtime.media" />
+            <tag name="bitbag_sylius_cms_plugin.renderer.collection" />
+        </service>
+
+        <service id="bitbag_sylius_cms_plugin.renderer.collection.pages" class="BitBag\SyliusCmsPlugin\Renderer\Collection\CollectionPagesRenderer">
+            <argument type="service" id="bitbag_sylius_cms_plugin.page_link_renderer" />
+            <tag name="bitbag_sylius_cms_plugin.renderer.collection" />
+        </service>
+
+        <service id="bitbag_sylius_cms_plugin.page_link_renderer" class="BitBag\SyliusCmsPlugin\Renderer\PageLinkRenderer">
+            <argument type="service" id="router.default" />
+            <argument type="service" id="twig" />
+        </service>
     </services>
 </container>
diff --git a/src/Resources/config/services/resolver.xml b/src/Resources/config/services/resolver.xml
index 2bfd80270..9d51b70b1 100644
--- a/src/Resources/config/services/resolver.xml
+++ b/src/Resources/config/services/resolver.xml
@@ -34,6 +34,11 @@
             <argument type="service" id="sylius.context.channel" />
         </service>
 
+        <service id="bitbag_sylius_cms_plugin.resolver.collection_resource" class="BitBag\SyliusCmsPlugin\Resolver\CollectionResourceResolver" public="true">
+            <argument type="service" id="bitbag_sylius_cms_plugin.repository.collection" />
+            <argument type="service" id="logger" />
+        </service>
+
         <service id="bitbag_sylius_cms_plugin.resolver.page_resource" class="BitBag\SyliusCmsPlugin\Resolver\PageResourceResolver" public="true">
             <argument type="service" id="bitbag_sylius_cms_plugin.repository.page" />
             <argument type="service" id="logger" />
diff --git a/src/Resources/config/services/twig.xml b/src/Resources/config/services/twig.xml
index 39d108966..d831b84ca 100644
--- a/src/Resources/config/services/twig.xml
+++ b/src/Resources/config/services/twig.xml
@@ -24,6 +24,18 @@
             <tag name="twig.runtime" />
         </service>
 
+        <service id="bitbag_sylius_cms_plugin.twig.extension.collection" class="BitBag\SyliusCmsPlugin\Twig\Extension\RenderCollectionExtension">
+            <argument type="service" id="bitbag_sylius_cms_plugin.twig.runtime.collection" />
+            <tag name="twig.extension" />
+        </service>
+
+        <service id="bitbag_sylius_cms_plugin.twig.runtime.collection" class="BitBag\SyliusCmsPlugin\Twig\Runtime\RenderCollectionRuntime">
+            <argument type="service" id="twig" />
+            <argument type="service" id="bitbag_sylius_cms_plugin.resolver.collection_resource" />
+            <argument type="service" id="bitbag_sylius_cms_plugin.collection_renderer_strategy" />
+            <tag name="twig.runtime" />
+        </service>
+
         <service id="bitbag_sylius_cms_plugin.twig.extension.media" class="BitBag\SyliusCmsPlugin\Twig\Extension\RenderMediaExtension">
             <argument type="service" id="bitbag_sylius_cms_plugin.twig.runtime.media" />
             <tag name="twig.extension" />
diff --git a/src/Resources/views/Shop/Collection/show.html.twig b/src/Resources/views/Shop/Collection/show.html.twig
new file mode 100644
index 000000000..c1e138d62
--- /dev/null
+++ b/src/Resources/views/Shop/Collection/show.html.twig
@@ -0,0 +1,3 @@
+<div class="bitbag-collection">
+    {{ content|raw }}
+</div>
diff --git a/src/Resources/views/Shop/Media/Show/file.html.twig b/src/Resources/views/Shop/Media/Show/file.html.twig
index 62a370885..2a03c5d81 100755
--- a/src/Resources/views/Shop/Media/Show/file.html.twig
+++ b/src/Resources/views/Shop/Media/Show/file.html.twig
@@ -1,10 +1,12 @@
-{% if null != media.name %}
-    <h2>{{ media.name|raw }}</h2>
-{% endif %}
+<div>
+    {% if null != media.name %}
+        <h2>{{ media.name|raw }}</h2>
+    {% endif %}
 
-<p>{{ bitbag_cms_render_content(media) }}</p>
+    <p>{{ bitbag_cms_render_content(media) }}</p>
 
-<a class="ui icon labeled primary button" href="{{ path('bitbag_sylius_cms_plugin_shop_media_download', {'code': media.code}) }}">
-    <i class="download icon"></i>
-    {{ 'bitbag_sylius_cms_plugin.ui.download'|trans }}
-</a>
+    <a class="ui icon labeled primary button" href="{{ path('bitbag_sylius_cms_plugin_shop_media_download', {'code': media.code}) }}">
+        <i class="download icon"></i>
+        {{ 'bitbag_sylius_cms_plugin.ui.download'|trans }}
+    </a>
+</div>
diff --git a/src/Resources/views/Shop/Media/Show/image.html.twig b/src/Resources/views/Shop/Media/Show/image.html.twig
index edf7565dc..0b13ea9f0 100755
--- a/src/Resources/views/Shop/Media/Show/image.html.twig
+++ b/src/Resources/views/Shop/Media/Show/image.html.twig
@@ -1,8 +1,10 @@
-<h2>{{ media.name|raw }}</h2>
+<div>
+    <h2>{{ media.name|raw }}</h2>
 
-<img class="ui fluid image" src="{{ media.path }}" alt="{{ media.alt }}"
-    {% if media.width is not null %} width="{{ media.width }}" {% endif %}
-    {% if media.height is not null %} height="{{ media.height }}" {% endif %}
-/>
+    <img class="ui fluid image" src="{{ media.path }}" alt="{{ media.alt }}"
+        {% if media.width is not null %} width="{{ media.width }}" {% endif %}
+        {% if media.height is not null %} height="{{ media.height }}" {% endif %}
+    />
 
-<p>{{ bitbag_cms_render_content(media) }}</p>
+    <p>{{ bitbag_cms_render_content(media) }}</p>
+</div>
diff --git a/src/Resources/views/Shop/Page/link.html.twig b/src/Resources/views/Shop/Page/link.html.twig
new file mode 100644
index 000000000..c55da6b38
--- /dev/null
+++ b/src/Resources/views/Shop/Page/link.html.twig
@@ -0,0 +1,3 @@
+<div>
+    <a href="{{ link }}">{{ name }}</a>
+</div>
diff --git a/src/Sorter/SorterById.php b/src/Sorter/SorterById.php
new file mode 100644
index 000000000..16757f288
--- /dev/null
+++ b/src/Sorter/SorterById.php
@@ -0,0 +1,27 @@
+<?php
+
+/*
+ * This file was created by developers working at BitBag
+ * Do you need more information about us and what we do? Visit our https://bitbag.io website!
+ * We are hiring developers from all over the world. Join us and start your new, exciting adventure and become part of us: https://bitbag.io/career
+*/
+
+declare(strict_types=1);
+
+namespace BitBag\SyliusCmsPlugin\Sorter;
+
+final class SorterById
+{
+    public static function sort(array $elements, string $direction = 'asc'): array
+    {
+        usort($elements, static function ($element1, $element2) use ($direction) {
+            if ($direction === 'asc') {
+                return $element1->getId() <=> $element2->getId();
+            }
+
+            return $element2->getId() <=> $element1->getId();
+        });
+
+        return $elements;
+    }
+}
diff --git a/src/Twig/Extension/RenderCollectionExtension.php b/src/Twig/Extension/RenderCollectionExtension.php
new file mode 100644
index 000000000..246be6897
--- /dev/null
+++ b/src/Twig/Extension/RenderCollectionExtension.php
@@ -0,0 +1,33 @@
+<?php
+
+/*
+ * This file was created by developers working at BitBag
+ * Do you need more information about us and what we do? Visit our https://bitbag.io website!
+ * We are hiring developers from all over the world. Join us and start your new, exciting adventure and become part of us: https://bitbag.io/career
+*/
+
+declare(strict_types=1);
+
+namespace BitBag\SyliusCmsPlugin\Twig\Extension;
+
+use BitBag\SyliusCmsPlugin\Twig\Runtime\RenderCollectionRuntimeInterface;
+use Twig\Extension\AbstractExtension;
+use Twig\TwigFunction;
+
+final class RenderCollectionExtension extends AbstractExtension
+{
+    public function __construct(private RenderCollectionRuntimeInterface $collectionRuntime)
+    {
+    }
+
+    public function getFunctions(): array
+    {
+        return [
+            new TwigFunction(
+                'bitbag_cms_render_collection',
+                [$this->collectionRuntime, 'renderCollection'],
+                ['is_safe' => ['html']],
+            ),
+        ];
+    }
+}
diff --git a/src/Twig/Runtime/RenderCollectionRuntime.php b/src/Twig/Runtime/RenderCollectionRuntime.php
new file mode 100644
index 000000000..45b9b5901
--- /dev/null
+++ b/src/Twig/Runtime/RenderCollectionRuntime.php
@@ -0,0 +1,42 @@
+<?php
+
+/*
+ * This file was created by developers working at BitBag
+ * Do you need more information about us and what we do? Visit our https://bitbag.io website!
+ * We are hiring developers from all over the world. Join us and start your new, exciting adventure and become part of us: https://bitbag.io/career
+*/
+
+declare(strict_types=1);
+
+namespace BitBag\SyliusCmsPlugin\Twig\Runtime;
+
+use BitBag\SyliusCmsPlugin\Renderer\CollectionRendererStrategyInterface;
+use BitBag\SyliusCmsPlugin\Resolver\CollectionResourceResolverInterface;
+use Twig\Environment;
+
+final class RenderCollectionRuntime implements RenderCollectionRuntimeInterface
+{
+    private const DEFAULT_TEMPLATE = '@BitBagSyliusCmsPlugin/Shop/Collection/show.html.twig';
+
+    public function __construct(
+        private Environment $twig,
+        private CollectionResourceResolverInterface $collectionResourceResolver,
+        private CollectionRendererStrategyInterface $collectionRenderer,
+    ) {
+    }
+
+    public function renderCollection(string $code, ?int $countToRender = null, ?string $template = null): string
+    {
+        $collection = $this->collectionResourceResolver->findOrLog($code);
+        if (null === $collection) {
+            return '';
+        }
+
+        return $this->twig->render(
+            $template ?? self::DEFAULT_TEMPLATE,
+            [
+                'content' => $this->collectionRenderer->render($collection, $countToRender),
+            ],
+        );
+    }
+}
diff --git a/src/Twig/Runtime/RenderCollectionRuntimeInterface.php b/src/Twig/Runtime/RenderCollectionRuntimeInterface.php
new file mode 100644
index 000000000..ac6f52a17
--- /dev/null
+++ b/src/Twig/Runtime/RenderCollectionRuntimeInterface.php
@@ -0,0 +1,16 @@
+<?php
+
+/*
+ * This file was created by developers working at BitBag
+ * Do you need more information about us and what we do? Visit our https://bitbag.io website!
+ * We are hiring developers from all over the world. Join us and start your new, exciting adventure and become part of us: https://bitbag.io/career
+*/
+
+declare(strict_types=1);
+
+namespace BitBag\SyliusCmsPlugin\Twig\Runtime;
+
+interface RenderCollectionRuntimeInterface
+{
+    public function renderCollection(string $code, ?int $countToRender = null, ?string $template = null): string;
+}
diff --git a/tests/Application/templates/bundles/SyliusShopBundle/Homepage/index.html.twig b/tests/Application/templates/bundles/SyliusShopBundle/Homepage/index.html.twig
index ab008b882..83f010ec7 100755
--- a/tests/Application/templates/bundles/SyliusShopBundle/Homepage/index.html.twig
+++ b/tests/Application/templates/bundles/SyliusShopBundle/Homepage/index.html.twig
@@ -1,6 +1,7 @@
 {% extends '@SyliusShop/layout.html.twig' %}
 
 {% block content %}
+    {{ bitbag_cms_render_collection('blog') }}
     <div class="top one">
         <div class="ui center aligned segment stackable grid cms-logo">
             <a href="https://github.com/BitBagCommerce/SyliusCmsPlugin">

From eb876f402b6eba2183c9a5d6153067293c1076a4 Mon Sep 17 00:00:00 2001
From: jkindly <kozupa.jakub@gmail.com>
Date: Tue, 23 Jul 2024 11:59:24 +0200
Subject: [PATCH 02/15] OP-441: ECS fixes

---
 src/Renderer/Collection/CollectionBlocksRenderer.php | 2 +-
 src/Renderer/Collection/CollectionMediaRenderer.php  | 2 +-
 src/Renderer/Collection/CollectionPagesRenderer.php  | 2 +-
 src/Sorter/SorterById.php                            | 2 +-
 4 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/src/Renderer/Collection/CollectionBlocksRenderer.php b/src/Renderer/Collection/CollectionBlocksRenderer.php
index 584da2568..250cf5709 100644
--- a/src/Renderer/Collection/CollectionBlocksRenderer.php
+++ b/src/Renderer/Collection/CollectionBlocksRenderer.php
@@ -40,6 +40,6 @@ public function render(CollectionInterface $collection, ?int $countToRender = nu
 
     public function supports(CollectionInterface $collection): bool
     {
-        return $collection->getBlocks()?->count() > 0;
+        return 0 < $collection->getBlocks()?->count();
     }
 }
diff --git a/src/Renderer/Collection/CollectionMediaRenderer.php b/src/Renderer/Collection/CollectionMediaRenderer.php
index b5c7ee8a3..ddb91be54 100644
--- a/src/Renderer/Collection/CollectionMediaRenderer.php
+++ b/src/Renderer/Collection/CollectionMediaRenderer.php
@@ -41,6 +41,6 @@ public function render(CollectionInterface $collection, ?int $countToRender = nu
 
     public function supports(CollectionInterface $collection): bool
     {
-        return $collection->getMedia()?->count() > 0;
+        return 0 < $collection->getMedia()?->count();
     }
 }
diff --git a/src/Renderer/Collection/CollectionPagesRenderer.php b/src/Renderer/Collection/CollectionPagesRenderer.php
index 587434e17..2b5578cf6 100644
--- a/src/Renderer/Collection/CollectionPagesRenderer.php
+++ b/src/Renderer/Collection/CollectionPagesRenderer.php
@@ -40,6 +40,6 @@ public function render(CollectionInterface $collection, ?int $countToRender = nu
 
     public function supports(CollectionInterface $collection): bool
     {
-        return $collection->getPages()?->count() > 0;
+        return 0 < $collection->getPages()?->count();
     }
 }
diff --git a/src/Sorter/SorterById.php b/src/Sorter/SorterById.php
index 16757f288..5ecdcd959 100644
--- a/src/Sorter/SorterById.php
+++ b/src/Sorter/SorterById.php
@@ -15,7 +15,7 @@ final class SorterById
     public static function sort(array $elements, string $direction = 'asc'): array
     {
         usort($elements, static function ($element1, $element2) use ($direction) {
-            if ($direction === 'asc') {
+            if ('asc' === $direction) {
                 return $element1->getId() <=> $element2->getId();
             }
 

From 0b53af17cd4fb961dfbe51e57defd939a67e771c Mon Sep 17 00:00:00 2001
From: jkindly <kozupa.jakub@gmail.com>
Date: Tue, 23 Jul 2024 14:12:36 +0200
Subject: [PATCH 03/15] OP-441: PHP Spec

---
 .../CollectionBlocksRendererSpec.php          | 100 ++++++++++++++++++
 .../CollectionMediaRendererSpec.php           |  78 ++++++++++++++
 .../CollectionPagesRendererSpec.php           |  92 ++++++++++++++++
 .../CollectionRendererStrategySpec.php        |  73 +++++++++++++
 spec/Renderer/PageLinkRendererSpec.php        |  88 +++++++++++++++
 5 files changed, 431 insertions(+)
 create mode 100644 spec/Renderer/Collection/CollectionBlocksRendererSpec.php
 create mode 100644 spec/Renderer/Collection/CollectionMediaRendererSpec.php
 create mode 100644 spec/Renderer/Collection/CollectionPagesRendererSpec.php
 create mode 100644 spec/Renderer/CollectionRendererStrategySpec.php
 create mode 100644 spec/Renderer/PageLinkRendererSpec.php

diff --git a/spec/Renderer/Collection/CollectionBlocksRendererSpec.php b/spec/Renderer/Collection/CollectionBlocksRendererSpec.php
new file mode 100644
index 000000000..d131e1cbf
--- /dev/null
+++ b/spec/Renderer/Collection/CollectionBlocksRendererSpec.php
@@ -0,0 +1,100 @@
+<?php
+
+/*
+ * This file was created by developers working at BitBag
+ * Do you need more information about us and what we do? Visit our https://bitbag.io website!
+ * We are hiring developers from all over the world. Join us and start your new, exciting adventure and become part of us: https://bitbag.io/career
+*/
+
+declare(strict_types=1);
+
+namespace spec\BitBag\SyliusCmsPlugin\Renderer\Collection;
+
+use BitBag\SyliusCmsPlugin\Entity\BlockInterface;
+use BitBag\SyliusCmsPlugin\Entity\CollectionInterface;
+use BitBag\SyliusCmsPlugin\Renderer\Collection\CollectionBlocksRenderer;
+use BitBag\SyliusCmsPlugin\Renderer\Collection\CollectionRendererInterface;
+use BitBag\SyliusCmsPlugin\Renderer\ContentElementRendererStrategyInterface;
+use Doctrine\Common\Collections\ArrayCollection;
+use PhpSpec\ObjectBehavior;
+
+final class CollectionBlocksRendererSpec extends ObjectBehavior
+{
+    public function let(ContentElementRendererStrategyInterface $contentElementRendererStrategy): void
+    {
+        $this->beConstructedWith($contentElementRendererStrategy);
+    }
+
+    public function it_is_initializable(): void
+    {
+        $this->shouldHaveType(CollectionBlocksRenderer::class);
+    }
+
+    public function it_implements_collection_renderer_interface(): void
+    {
+        $this->shouldImplement(CollectionRendererInterface::class);
+    }
+
+    public function it_renders_blocks_from_collection(
+        ContentElementRendererStrategyInterface $contentElementRendererStrategy,
+        CollectionInterface $collection,
+        BlockInterface $block1,
+        BlockInterface $block2
+    ): void
+    {
+        $blocks = new ArrayCollection([$block1->getWrappedObject(), $block2->getWrappedObject()]);
+        $collection->getBlocks()->willReturn($blocks);
+
+        $contentElementRendererStrategy->render($block1)->willReturn('block1_content');
+        $contentElementRendererStrategy->render($block2)->willReturn('block2_content');
+
+        $this->render($collection)->shouldReturn('block1_contentblock2_content');
+    }
+
+    public function it_limits_number_of_rendered_blocks(
+        ContentElementRendererStrategyInterface $contentElementRendererStrategy,
+        CollectionInterface $collection,
+        BlockInterface $block1,
+        BlockInterface $block2
+    ): void
+    {
+        $blocks = new ArrayCollection([$block1->getWrappedObject(), $block2->getWrappedObject()]);
+        $collection->getBlocks()->willReturn($blocks);
+
+        $contentElementRendererStrategy->render($block1)->willReturn('block1_content');
+        $contentElementRendererStrategy->render($block2)->willReturn('block2_content');
+
+        $this->render($collection, 1)->shouldReturn('block1_content');
+    }
+
+    public function it_supports_collections_with_blocks(
+        CollectionInterface $collection,
+        BlockInterface $block
+    ): void
+    {
+        $blocks = new ArrayCollection([$block]);
+        $collection->getBlocks()->willReturn($blocks);
+
+        $this->supports($collection)->shouldReturn(true);
+    }
+
+    public function it_does_not_support_empty_collections(
+        CollectionInterface $collection
+    ): void
+    {
+        $collection->getBlocks()->willReturn(new ArrayCollection());
+
+        $this->supports($collection)->shouldReturn(false);
+    }
+
+    public function it_throws_exception_when_blocks_are_null(
+        ContentElementRendererStrategyInterface $contentElementRendererStrategy,
+        CollectionInterface $collection
+    ): void
+    {
+        $collection->getBlocks()->willReturn(null);
+
+        $this->shouldThrow(\InvalidArgumentException::class)
+            ->during('render', [$collection]);
+    }
+}
diff --git a/spec/Renderer/Collection/CollectionMediaRendererSpec.php b/spec/Renderer/Collection/CollectionMediaRendererSpec.php
new file mode 100644
index 000000000..d04537038
--- /dev/null
+++ b/spec/Renderer/Collection/CollectionMediaRendererSpec.php
@@ -0,0 +1,78 @@
+<?php
+
+/*
+ * This file was created by developers working at BitBag
+ * Do you need more information about us and what we do? Visit our https://bitbag.io website!
+ * We are hiring developers from all over the world. Join us and start your new, exciting adventure and become part of us: https://bitbag.io/career
+*/
+
+declare(strict_types=1);
+
+namespace spec\BitBag\SyliusCmsPlugin\Renderer\Collection;
+
+use BitBag\SyliusCmsPlugin\Entity\CollectionInterface;
+use BitBag\SyliusCmsPlugin\Entity\MediaInterface;
+use BitBag\SyliusCmsPlugin\Renderer\Collection\CollectionMediaRenderer;
+use BitBag\SyliusCmsPlugin\Renderer\Collection\CollectionRendererInterface;
+use BitBag\SyliusCmsPlugin\Twig\Runtime\RenderMediaRuntimeInterface;
+use Doctrine\Common\Collections\ArrayCollection;
+use PhpSpec\ObjectBehavior;
+
+final class CollectionMediaRendererSpec extends ObjectBehavior
+{
+    function let(RenderMediaRuntimeInterface $renderMediaRuntime)
+    {
+        $this->beConstructedWith($renderMediaRuntime);
+    }
+
+    function it_is_initializable()
+    {
+        $this->shouldHaveType(CollectionMediaRenderer::class);
+        $this->shouldImplement(CollectionRendererInterface::class);
+    }
+
+    function it_renders_media_collection(RenderMediaRuntimeInterface $renderMediaRuntime, CollectionInterface $collection, MediaInterface $media1, MediaInterface $media2)
+    {
+        $media1->getId()->willReturn(1);
+        $media2->getId()->willReturn(2);
+
+        $media1->getCode()->willReturn('media_code_1');
+        $media2->getCode()->willReturn('media_code_2');
+
+        $collection->getMedia()->willReturn(new ArrayCollection([$media1->getWrappedObject(), $media2->getWrappedObject()]));
+
+        $renderMediaRuntime->renderMedia('media_code_1')->willReturn('media1');
+        $renderMediaRuntime->renderMedia('media_code_2')->willReturn('media2');
+
+        $this->render($collection)->shouldReturn('media1media2');
+    }
+
+    function it_renders_limited_number_of_media(RenderMediaRuntimeInterface $renderMediaRuntime, CollectionInterface $collection, MediaInterface $media1, MediaInterface $media2)
+    {
+        $media1->getId()->willReturn(1);
+        $media2->getId()->willReturn(2);
+
+        $media1->getCode()->willReturn('media_code_1');
+        $media2->getCode()->willReturn('media_code_2');
+
+        $collection->getMedia()->willReturn(new ArrayCollection([$media1->getWrappedObject(), $media2->getWrappedObject()]));
+
+        $renderMediaRuntime->renderMedia('media_code_1')->willReturn('media1');
+
+        $this->render($collection, 1)->shouldReturn('media1');
+    }
+
+    function it_supports_collections_with_media(CollectionInterface $collection, MediaInterface $media1)
+    {
+        $collection->getMedia()->willReturn(new ArrayCollection([$media1]));
+
+        $this->supports($collection)->shouldReturn(true);
+    }
+
+    function it_does_not_support_collections_without_media(CollectionInterface $collection)
+    {
+        $collection->getMedia()->willReturn(new ArrayCollection());
+
+        $this->supports($collection)->shouldReturn(false);
+    }
+}
diff --git a/spec/Renderer/Collection/CollectionPagesRendererSpec.php b/spec/Renderer/Collection/CollectionPagesRendererSpec.php
new file mode 100644
index 000000000..e5421f555
--- /dev/null
+++ b/spec/Renderer/Collection/CollectionPagesRendererSpec.php
@@ -0,0 +1,92 @@
+<?php
+
+/*
+ * This file was created by developers working at BitBag
+ * Do you need more information about us and what we do? Visit our https://bitbag.io website!
+ * We are hiring developers from all over the world. Join us and start your new, exciting adventure and become part of us: https://bitbag.io/career
+*/
+
+declare(strict_types=1);
+
+namespace spec\BitBag\SyliusCmsPlugin\Renderer\Collection;
+
+use BitBag\SyliusCmsPlugin\Entity\CollectionInterface;
+use BitBag\SyliusCmsPlugin\Entity\PageInterface;
+use BitBag\SyliusCmsPlugin\Renderer\Collection\CollectionPagesRenderer;
+use BitBag\SyliusCmsPlugin\Renderer\Collection\CollectionRendererInterface;
+use BitBag\SyliusCmsPlugin\Renderer\PageLinkRendererInterface;
+use Doctrine\Common\Collections\ArrayCollection;
+use PhpSpec\ObjectBehavior;
+
+final class CollectionPagesRendererSpec extends ObjectBehavior
+{
+    public function let(PageLinkRendererInterface $pageLinkRenderer): void
+    {
+        $this->beConstructedWith($pageLinkRenderer);
+    }
+
+    public function it_is_initializable(): void
+    {
+        $this->shouldHaveType(CollectionPagesRenderer::class);
+    }
+
+    public function it_implements_collection_renderer_interface(): void
+    {
+        $this->shouldImplement(CollectionRendererInterface::class);
+    }
+
+    public function it_renders_pages_from_collection(
+        PageLinkRendererInterface $pageLinkRenderer,
+        CollectionInterface $collection,
+        PageInterface $page1,
+        PageInterface $page2
+    ): void
+    {
+        $page1->getId()->willReturn(2);
+        $page2->getId()->willReturn(1);
+
+        $collection->getPages()->willReturn(new ArrayCollection([$page1->getWrappedObject(), $page2->getWrappedObject()]));
+
+        $pageLinkRenderer->render($page1)->willReturn('page1_content');
+        $pageLinkRenderer->render($page2)->willReturn('page2_content');
+
+        $this->render($collection)->shouldReturn('page1_contentpage2_content');
+    }
+
+    public function it_limits_number_of_rendered_pages(
+        PageLinkRendererInterface $pageLinkRenderer,
+        CollectionInterface $collection,
+        PageInterface $page1,
+        PageInterface $page2
+    ): void
+    {
+        $page1->getId()->willReturn(2);
+        $page2->getId()->willReturn(1);
+
+        $collection->getPages()->willReturn(new ArrayCollection([$page1->getWrappedObject(), $page2->getWrappedObject()]));
+
+        $pageLinkRenderer->render($page1)->willReturn('page1_content');
+        $pageLinkRenderer->render($page2)->willReturn('page2_content');
+
+        $this->render($collection, 1)->shouldReturn('page1_content');
+    }
+
+    public function it_supports_collections_with_pages(
+        CollectionInterface $collection,
+        PageInterface $page
+    ): void
+    {
+        $collection->getPages()->willReturn(new ArrayCollection([$page]));
+
+        $this->supports($collection)->shouldReturn(true);
+    }
+
+    public function it_does_not_support_empty_collections(
+        CollectionInterface $collection
+    ): void
+    {
+        $collection->getPages()->willReturn(new ArrayCollection());
+
+        $this->supports($collection)->shouldReturn(false);
+    }
+}
diff --git a/spec/Renderer/CollectionRendererStrategySpec.php b/spec/Renderer/CollectionRendererStrategySpec.php
new file mode 100644
index 000000000..67aebde51
--- /dev/null
+++ b/spec/Renderer/CollectionRendererStrategySpec.php
@@ -0,0 +1,73 @@
+<?php
+
+/*
+ * This file was created by developers working at BitBag
+ * Do you need more information about us and what we do? Visit our https://bitbag.io website!
+ * We are hiring developers from all over the world. Join us and start your new, exciting adventure and become part of us: https://bitbag.io/career
+*/
+
+declare(strict_types=1);
+
+namespace spec\BitBag\SyliusCmsPlugin\Renderer;
+
+use BitBag\SyliusCmsPlugin\Entity\CollectionInterface;
+use BitBag\SyliusCmsPlugin\Renderer\Collection\CollectionRendererInterface;
+use BitBag\SyliusCmsPlugin\Renderer\CollectionRendererStrategy;
+use BitBag\SyliusCmsPlugin\Renderer\CollectionRendererStrategyInterface;
+use PhpSpec\ObjectBehavior;
+
+final class CollectionRendererStrategySpec extends ObjectBehavior
+{
+    public function let(CollectionRendererInterface $renderer1, CollectionRendererInterface $renderer2): void
+    {
+        $this->beConstructedWith([$renderer1, $renderer2]);
+    }
+
+    public function it_is_initializable(): void
+    {
+        $this->shouldHaveType(CollectionRendererStrategy::class);
+    }
+
+    public function it_implements_collection_renderer_strategy_interface(): void
+    {
+        $this->shouldImplement(CollectionRendererStrategyInterface::class);
+    }
+
+    public function it_renders_collection_using_supported_renderer(
+        CollectionRendererInterface $renderer1,
+        CollectionRendererInterface $renderer2,
+        CollectionInterface $collection
+    ): void
+    {
+        $renderer1->supports($collection)->willReturn(false);
+        $renderer2->supports($collection)->willReturn(true);
+        $renderer2->render($collection, null)->willReturn('rendered content');
+
+        $this->render($collection)->shouldReturn('rendered content');
+    }
+
+    public function it_renders_collection_with_count_to_render(
+        CollectionRendererInterface $renderer1,
+        CollectionRendererInterface $renderer2,
+        CollectionInterface $collection
+    ): void
+    {
+        $renderer1->supports($collection)->willReturn(false);
+        $renderer2->supports($collection)->willReturn(true);
+        $renderer2->render($collection, 5)->willReturn('rendered content with count');
+
+        $this->render($collection, 5)->shouldReturn('rendered content with count');
+    }
+
+    public function it_returns_empty_string_when_no_renderer_supports_collection(
+        CollectionRendererInterface $renderer1,
+        CollectionRendererInterface $renderer2,
+        CollectionInterface $collection
+    ): void
+    {
+        $renderer1->supports($collection)->willReturn(false);
+        $renderer2->supports($collection)->willReturn(false);
+
+        $this->render($collection)->shouldReturn('');
+    }
+}
diff --git a/spec/Renderer/PageLinkRendererSpec.php b/spec/Renderer/PageLinkRendererSpec.php
new file mode 100644
index 000000000..45fb7b5f9
--- /dev/null
+++ b/spec/Renderer/PageLinkRendererSpec.php
@@ -0,0 +1,88 @@
+<?php
+
+/*
+ * This file was created by developers working at BitBag
+ * Do you need more information about us and what we do? Visit our https://bitbag.io website!
+ * We are hiring developers from all over the world. Join us and start your new, exciting adventure and become part of us: https://bitbag.io/career
+*/
+
+declare(strict_types=1);
+
+namespace spec\BitBag\SyliusCmsPlugin\Renderer;
+
+use BitBag\SyliusCmsPlugin\Entity\PageInterface;
+use BitBag\SyliusCmsPlugin\Renderer\PageLinkRenderer;
+use BitBag\SyliusCmsPlugin\Renderer\PageLinkRendererInterface;
+use PhpSpec\ObjectBehavior;
+use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
+use Twig\Environment;
+
+final class PageLinkRendererSpec extends ObjectBehavior
+{
+    public function let(
+        UrlGeneratorInterface $urlGenerator,
+        Environment $twig
+    ): void {
+        $this->beConstructedWith($urlGenerator, $twig);
+    }
+
+    public function it_is_initializable(): void
+    {
+        $this->shouldHaveType(PageLinkRenderer::class);
+    }
+
+    public function it_implements_page_link_renderer_interface(): void
+    {
+        $this->shouldImplement(PageLinkRendererInterface::class);
+    }
+
+    public function it_renders_page_link_with_default_template(
+        UrlGeneratorInterface $urlGenerator,
+        Environment $twig,
+        PageInterface $page
+    ): void {
+        $page->getSlug()->willReturn('page-slug');
+        $page->getName()->willReturn('Page Name');
+
+        $urlGenerator->generate(
+            'bitbag_sylius_cms_plugin_shop_page_show',
+            ['slug' => 'page-slug'],
+            UrlGeneratorInterface::ABSOLUTE_URL
+        )->willReturn('http://example.com/page-slug');
+
+        $twig->render(
+            '@BitBagSyliusCmsPlugin/Shop/Page/link.html.twig',
+            [
+                'link' => 'http://example.com/page-slug',
+                'name' => 'Page Name',
+            ]
+        )->willReturn('<a href="http://example.com/page-slug">Page Name</a>');
+
+        $this->render($page)->shouldReturn('<a href="http://example.com/page-slug">Page Name</a>');
+    }
+
+    public function it_renders_page_link_with_custom_template(
+        UrlGeneratorInterface $urlGenerator,
+        Environment $twig,
+        PageInterface $page
+    ): void {
+        $page->getSlug()->willReturn('page-slug');
+        $page->getName()->willReturn('Page Name');
+
+        $urlGenerator->generate(
+            'bitbag_sylius_cms_plugin_shop_page_show',
+            ['slug' => 'page-slug'],
+            UrlGeneratorInterface::ABSOLUTE_URL
+        )->willReturn('http://example.com/page-slug');
+
+        $twig->render(
+            'custom_template.html.twig',
+            [
+                'link' => 'http://example.com/page-slug',
+                'name' => 'Page Name',
+            ]
+        )->willReturn('<a href="http://example.com/page-slug">Page Name</a>');
+
+        $this->render($page, 'custom_template.html.twig')->shouldReturn('<a href="http://example.com/page-slug">Page Name</a>');
+    }
+}

From 637020ec7bb49e74bd6eb44aba87f5acf1b0b416 Mon Sep 17 00:00:00 2001
From: jkindly <kozupa.jakub@gmail.com>
Date: Fri, 19 Jul 2024 11:22:51 +0200
Subject: [PATCH 04/15] OP-440: Conditional block rendering

---
 src/Entity/Block.php                          | 13 +++++
 src/Entity/BlockInterface.php                 |  7 ++-
 src/Entity/BlockProductAwareInterface.php     | 20 ++++++++
 src/Entity/BlockTaxonAwareInterface.php       | 18 +++++++
 src/Entity/ProductsInTaxonsAwareInterface.php | 30 +++++++++++
 src/Entity/Trait/BlockProductAwareTrait.php   | 31 ++++++++++++
 src/Entity/Trait/BlockTaxonAwareTrait.php     | 21 ++++++++
 src/Entity/Trait/ProductsAwareTrait.php       |  2 +-
 .../Trait/ProductsInTaxonsAwareTrait.php      | 50 +++++++++++++++++++
 src/Entity/Trait/TaxonAwareTrait.php          | 12 ++---
 src/Form/Type/BlockType.php                   | 17 +++++++
 src/Resources/assets/admin/scss/_css.scss     |  6 +++
 src/Resources/config/doctrine/Block.orm.xml   | 33 ++++++++++++
 .../views/Block/Crud/_form.html.twig          |  6 +++
 src/Resources/views/Form/theme.html.twig      | 22 ++++++++
 src/Twig/Runtime/RenderBlockRuntime.php       | 30 +++++++----
 .../SyliusShopBundle/Product/show.html.twig   |  2 +-
 .../Taxon/Header/_content.html.twig           |  6 +++
 18 files changed, 307 insertions(+), 19 deletions(-)
 create mode 100644 src/Entity/BlockProductAwareInterface.php
 create mode 100644 src/Entity/BlockTaxonAwareInterface.php
 create mode 100755 src/Entity/ProductsInTaxonsAwareInterface.php
 create mode 100644 src/Entity/Trait/BlockProductAwareTrait.php
 create mode 100644 src/Entity/Trait/BlockTaxonAwareTrait.php
 create mode 100644 src/Entity/Trait/ProductsInTaxonsAwareTrait.php
 create mode 100644 tests/Application/templates/bundles/SyliusShopBundle/Taxon/Header/_content.html.twig

diff --git a/src/Entity/Block.php b/src/Entity/Block.php
index 4c8cb7341..3457c357c 100755
--- a/src/Entity/Block.php
+++ b/src/Entity/Block.php
@@ -10,10 +10,15 @@
 
 namespace BitBag\SyliusCmsPlugin\Entity;
 
+use BitBag\SyliusCmsPlugin\Entity\Trait\BlockProductAwareTrait;
+use BitBag\SyliusCmsPlugin\Entity\Trait\BlockTaxonAwareTrait;
 use BitBag\SyliusCmsPlugin\Entity\Trait\ChannelsAwareTrait;
 use BitBag\SyliusCmsPlugin\Entity\Trait\CollectibleTrait;
 use BitBag\SyliusCmsPlugin\Entity\Trait\ContentConfigurationAwareTrait;
 use BitBag\SyliusCmsPlugin\Entity\Trait\LocaleAwareTrait;
+use BitBag\SyliusCmsPlugin\Entity\Trait\ProductsAwareTrait;
+use BitBag\SyliusCmsPlugin\Entity\Trait\ProductsInTaxonsAwareTrait;
+use BitBag\SyliusCmsPlugin\Entity\Trait\TaxonAwareTrait;
 use Sylius\Component\Resource\Model\ToggleableTrait;
 
 class Block implements BlockInterface
@@ -23,6 +28,11 @@ class Block implements BlockInterface
     use ChannelsAwareTrait;
     use ContentConfigurationAwareTrait;
     use LocaleAwareTrait;
+    use ProductsAwareTrait;
+    use TaxonAwareTrait;
+    use ProductsInTaxonsAwareTrait;
+    use BlockTaxonAwareTrait;
+    use BlockProductAwareTrait;
 
     public function __construct()
     {
@@ -30,6 +40,9 @@ public function __construct()
         $this->initializeChannelsCollection();
         $this->initializeContentElementsCollection();
         $this->initializeLocalesCollection();
+        $this->initializeProductsCollection();
+        $this->initializeTaxonCollection();
+        $this->initializeProductsInTaxonsCollection();
     }
 
     protected ?int $id;
diff --git a/src/Entity/BlockInterface.php b/src/Entity/BlockInterface.php
index c60770207..e3c002ac0 100755
--- a/src/Entity/BlockInterface.php
+++ b/src/Entity/BlockInterface.php
@@ -20,7 +20,12 @@ interface BlockInterface extends
     CollectibleInterface,
     ChannelsAwareInterface,
     ContentConfigurationAwareInterface,
-    LocaleAwareInterface
+    LocaleAwareInterface,
+    ProductsAwareInterface,
+    TaxonAwareInterface,
+    ProductsInTaxonsAwareInterface,
+    BlockTaxonAwareInterface,
+    BlockProductAwareInterface
 {
     public function getCode(): ?string;
 
diff --git a/src/Entity/BlockProductAwareInterface.php b/src/Entity/BlockProductAwareInterface.php
new file mode 100644
index 000000000..fbbb94e9d
--- /dev/null
+++ b/src/Entity/BlockProductAwareInterface.php
@@ -0,0 +1,20 @@
+<?php
+
+/*
+ * This file was created by developers working at BitBag
+ * Do you need more information about us and what we do? Visit our https://bitbag.io website!
+ * We are hiring developers from all over the world. Join us and start your new, exciting adventure and become part of us: https://bitbag.io/career
+*/
+
+declare(strict_types=1);
+
+namespace BitBag\SyliusCmsPlugin\Entity;
+
+use Sylius\Component\Core\Model\ProductInterface;
+
+interface BlockProductAwareInterface
+{
+    public function canBeDisplayedForProduct(ProductInterface $product): bool;
+
+    public function canBeDisplayedForProductInTaxon(ProductInterface $product): bool;
+}
diff --git a/src/Entity/BlockTaxonAwareInterface.php b/src/Entity/BlockTaxonAwareInterface.php
new file mode 100644
index 000000000..1c4b8a57c
--- /dev/null
+++ b/src/Entity/BlockTaxonAwareInterface.php
@@ -0,0 +1,18 @@
+<?php
+
+/*
+ * This file was created by developers working at BitBag
+ * Do you need more information about us and what we do? Visit our https://bitbag.io website!
+ * We are hiring developers from all over the world. Join us and start your new, exciting adventure and become part of us: https://bitbag.io/career
+*/
+
+declare(strict_types=1);
+
+namespace BitBag\SyliusCmsPlugin\Entity;
+
+use Sylius\Component\Core\Model\TaxonInterface;
+
+interface BlockTaxonAwareInterface
+{
+    public function canBeDisplayedForTaxon(TaxonInterface $taxon): bool;
+}
diff --git a/src/Entity/ProductsInTaxonsAwareInterface.php b/src/Entity/ProductsInTaxonsAwareInterface.php
new file mode 100755
index 000000000..4a03c9939
--- /dev/null
+++ b/src/Entity/ProductsInTaxonsAwareInterface.php
@@ -0,0 +1,30 @@
+<?php
+
+/*
+ * This file was created by developers working at BitBag
+ * Do you need more information about us and what we do? Visit our https://bitbag.io website!
+ * We are hiring developers from all over the world. Join us and start your new, exciting adventure and become part of us: https://bitbag.io/career
+*/
+
+declare(strict_types=1);
+
+namespace BitBag\SyliusCmsPlugin\Entity;
+
+use Doctrine\Common\Collections\Collection;
+use Sylius\Component\Core\Model\TaxonInterface;
+
+interface ProductsInTaxonsAwareInterface
+{
+    public function initializeProductsInTaxonsCollection(): void;
+
+    /**
+     * @return Collection|TaxonInterface[]
+     */
+    public function getProductsInTaxons(): Collection;
+
+    public function hasProductsInTaxon(TaxonInterface $taxon): bool;
+
+    public function addProductsInTaxon(TaxonInterface $taxon): void;
+
+    public function removeProductsInTaxon(TaxonInterface $taxon): void;
+}
diff --git a/src/Entity/Trait/BlockProductAwareTrait.php b/src/Entity/Trait/BlockProductAwareTrait.php
new file mode 100644
index 000000000..27c13ab82
--- /dev/null
+++ b/src/Entity/Trait/BlockProductAwareTrait.php
@@ -0,0 +1,31 @@
+<?php
+
+/*
+ * This file was created by developers working at BitBag
+ * Do you need more information about us and what we do? Visit our https://bitbag.io website!
+ * We are hiring developers from all over the world. Join us and start your new, exciting adventure and become part of us: https://bitbag.io/career
+*/
+
+declare(strict_types=1);
+
+namespace BitBag\SyliusCmsPlugin\Entity\Trait;
+
+use Sylius\Component\Core\Model\ProductInterface;
+
+trait BlockProductAwareTrait
+{
+    public function canBeDisplayedForProduct(ProductInterface $product): bool
+    {
+        return $this->hasProduct($product);
+    }
+
+    public function canBeDisplayedForProductInTaxon(ProductInterface $product): bool
+    {
+        $taxon = $product->getMainTaxon();
+        if (null === $taxon) {
+            return false;
+        }
+
+        return $this->hasProductsInTaxon($taxon);
+    }
+}
diff --git a/src/Entity/Trait/BlockTaxonAwareTrait.php b/src/Entity/Trait/BlockTaxonAwareTrait.php
new file mode 100644
index 000000000..c84cd24cd
--- /dev/null
+++ b/src/Entity/Trait/BlockTaxonAwareTrait.php
@@ -0,0 +1,21 @@
+<?php
+
+/*
+ * This file was created by developers working at BitBag
+ * Do you need more information about us and what we do? Visit our https://bitbag.io website!
+ * We are hiring developers from all over the world. Join us and start your new, exciting adventure and become part of us: https://bitbag.io/career
+*/
+
+declare(strict_types=1);
+
+namespace BitBag\SyliusCmsPlugin\Entity\Trait;
+
+use Sylius\Component\Core\Model\TaxonInterface;
+
+trait BlockTaxonAwareTrait
+{
+    public function canBeDisplayedForTaxon(TaxonInterface $taxon): bool
+    {
+        return $this->hasTaxon($taxon);
+    }
+}
diff --git a/src/Entity/Trait/ProductsAwareTrait.php b/src/Entity/Trait/ProductsAwareTrait.php
index 16478bb2d..3710dea32 100755
--- a/src/Entity/Trait/ProductsAwareTrait.php
+++ b/src/Entity/Trait/ProductsAwareTrait.php
@@ -17,7 +17,7 @@
 trait ProductsAwareTrait
 {
     /** @var Collection|ProductInterface[] */
-    protected $products;
+    protected array|Collection $products;
 
     public function initializeProductsCollection(): void
     {
diff --git a/src/Entity/Trait/ProductsInTaxonsAwareTrait.php b/src/Entity/Trait/ProductsInTaxonsAwareTrait.php
new file mode 100644
index 000000000..6e331bdf8
--- /dev/null
+++ b/src/Entity/Trait/ProductsInTaxonsAwareTrait.php
@@ -0,0 +1,50 @@
+<?php
+
+/*
+ * This file was created by developers working at BitBag
+ * Do you need more information about us and what we do? Visit our https://bitbag.io website!
+ * We are hiring developers from all over the world. Join us and start your new, exciting adventure and become part of us: https://bitbag.io/career
+*/
+
+declare(strict_types=1);
+
+namespace BitBag\SyliusCmsPlugin\Entity\Trait;
+
+use Doctrine\Common\Collections\ArrayCollection;
+use Doctrine\Common\Collections\Collection;
+use Sylius\Component\Core\Model\TaxonInterface;
+
+trait ProductsInTaxonsAwareTrait
+{
+    /** @var Collection|TaxonInterface[] */
+    protected array|Collection $productsInTaxons;
+
+    public function initializeProductsInTaxonsCollection(): void
+    {
+        $this->productsInTaxons = new ArrayCollection();
+    }
+
+    public function getProductsInTaxons(): Collection
+    {
+        return $this->productsInTaxons;
+    }
+
+    public function hasProductsInTaxon(TaxonInterface $taxon): bool
+    {
+        return $this->productsInTaxons->contains($taxon);
+    }
+
+    public function addProductsInTaxon(TaxonInterface $taxon): void
+    {
+        if (false === $this->hasProductsInTaxon($taxon)) {
+            $this->productsInTaxons->add($taxon);
+        }
+    }
+
+    public function removeProductsInTaxon(TaxonInterface $taxon): void
+    {
+        if (true === $this->hasProductsInTaxon($taxon)) {
+            $this->productsInTaxons->removeElement($taxon);
+        }
+    }
+}
diff --git a/src/Entity/Trait/TaxonAwareTrait.php b/src/Entity/Trait/TaxonAwareTrait.php
index 1306731ed..8edc13cc7 100644
--- a/src/Entity/Trait/TaxonAwareTrait.php
+++ b/src/Entity/Trait/TaxonAwareTrait.php
@@ -17,34 +17,34 @@
 trait TaxonAwareTrait
 {
     /** @var Collection|TaxonInterface[] */
-    protected $taxonomies;
+    protected array|Collection $taxons;
 
     public function initializeTaxonCollection(): void
     {
-        $this->taxonomies = new ArrayCollection();
+        $this->taxons = new ArrayCollection();
     }
 
     public function getTaxons(): Collection
     {
-        return $this->taxonomies;
+        return $this->taxons;
     }
 
     public function hasTaxon(TaxonInterface $taxon): bool
     {
-        return $this->taxonomies->contains($taxon);
+        return $this->taxons->contains($taxon);
     }
 
     public function addTaxon(TaxonInterface $taxon): void
     {
         if (false === $this->hasTaxon($taxon)) {
-            $this->taxonomies->add($taxon);
+            $this->taxons->add($taxon);
         }
     }
 
     public function removeTaxon(TaxonInterface $taxon): void
     {
         if (true === $this->hasTaxon($taxon)) {
-            $this->taxonomies->removeElement($taxon);
+            $this->taxons->removeElement($taxon);
         }
     }
 }
diff --git a/src/Form/Type/BlockType.php b/src/Form/Type/BlockType.php
index c1425b237..73fb6c3de 100755
--- a/src/Form/Type/BlockType.php
+++ b/src/Form/Type/BlockType.php
@@ -12,7 +12,9 @@
 
 use BitBag\SyliusCmsPlugin\Entity\BlockInterface;
 use Sylius\Bundle\ChannelBundle\Form\Type\ChannelChoiceType;
+use Sylius\Bundle\ProductBundle\Form\Type\ProductAutocompleteChoiceType;
 use Sylius\Bundle\ResourceBundle\Form\Type\AbstractResourceType;
+use Sylius\Bundle\TaxonomyBundle\Form\Type\TaxonAutocompleteChoiceType;
 use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
 use Symfony\Component\Form\Extension\Core\Type\CollectionType;
 use Symfony\Component\Form\Extension\Core\Type\TextType;
@@ -55,6 +57,21 @@ public function buildForm(FormBuilderInterface $builder, array $options): void
                 'by_reference' => false,
                 'required' => false,
             ])
+            ->add('products', ProductAutocompleteChoiceType::class, [
+                'label' => 'bitbag_sylius_cms_plugin.ui.display_for_products.label',
+                'multiple' => true,
+                'help' => 'bitbag_sylius_cms_plugin.ui.display_for_products.help',
+            ])
+            ->add('productsInTaxons', TaxonAutocompleteChoiceType::class, [
+                'label' => 'bitbag_sylius_cms_plugin.ui.display_for_products_in_taxons.label',
+                'multiple' => true,
+                'help' => 'bitbag_sylius_cms_plugin.ui.display_for_products_in_taxons.help'
+            ])
+            ->add('taxons', TaxonAutocompleteChoiceType::class, [
+                'label' => 'bitbag_sylius_cms_plugin.ui.display_for_taxons.label',
+                'multiple' => true,
+                'help' => 'bitbag_sylius_cms_plugin.ui.display_for_taxons.help',
+            ])
         ;
     }
 
diff --git a/src/Resources/assets/admin/scss/_css.scss b/src/Resources/assets/admin/scss/_css.scss
index 872507af4..d3c63c491 100644
--- a/src/Resources/assets/admin/scss/_css.scss
+++ b/src/Resources/assets/admin/scss/_css.scss
@@ -115,3 +115,9 @@
 .cke_notifications_area {
   display: none;
 }
+
+.help-text {
+    font-size: 12px;
+    margin-top: -10px;
+    opacity: 0.5;
+}
diff --git a/src/Resources/config/doctrine/Block.orm.xml b/src/Resources/config/doctrine/Block.orm.xml
index 78b52ac7b..eaf0eb4cf 100644
--- a/src/Resources/config/doctrine/Block.orm.xml
+++ b/src/Resources/config/doctrine/Block.orm.xml
@@ -58,5 +58,38 @@
             </join-table>
         </many-to-many>
 
+        <many-to-many field="products" target-entity="Sylius\Component\Core\Model\ProductInterface">
+            <join-table name="bitbag_cms_block_products">
+                <join-columns>
+                    <join-column name="block_id" referenced-column-name="id" nullable="false" on-delete="CASCADE"/>
+                </join-columns>
+                <inverse-join-columns>
+                    <join-column name="product_id" referenced-column-name="id" nullable="false" on-delete="CASCADE"/>
+                </inverse-join-columns>
+            </join-table>
+        </many-to-many>
+
+        <many-to-many field="taxons" target-entity="Sylius\Component\Core\Model\TaxonInterface">
+            <join-table name="bitbag_cms_block_taxons">
+                <join-columns>
+                    <join-column name="block_id" referenced-column-name="id" nullable="false" on-delete="CASCADE"/>
+                </join-columns>
+                <inverse-join-columns>
+                    <join-column name="taxon_id" referenced-column-name="id" nullable="false" on-delete="CASCADE"/>
+                </inverse-join-columns>
+            </join-table>
+        </many-to-many>
+
+        <many-to-many field="productsInTaxons" target-entity="Sylius\Component\Core\Model\TaxonInterface">
+            <join-table name="bitbag_cms_block_products_in_taxons">
+                <join-columns>
+                    <join-column name="block_id" referenced-column-name="id" nullable="false" on-delete="CASCADE"/>
+                </join-columns>
+                <inverse-join-columns>
+                    <join-column name="taxon_id" referenced-column-name="id" nullable="false" on-delete="CASCADE"/>
+                </inverse-join-columns>
+            </join-table>
+        </many-to-many>
+
     </mapped-superclass>
 </doctrine-mapping>
diff --git a/src/Resources/views/Block/Crud/_form.html.twig b/src/Resources/views/Block/Crud/_form.html.twig
index d6d326322..d53d42d74 100755
--- a/src/Resources/views/Block/Crud/_form.html.twig
+++ b/src/Resources/views/Block/Crud/_form.html.twig
@@ -12,6 +12,12 @@
             {{ form_row(form.locales) }}
             {{ form_row(form.collections) }}
         </div>
+        <div class="ui segment">
+            <h4 class="ui dividing header">{{ 'bitbag_sylius_cms_plugin.ui.manage_block_display'|trans }}</h4>
+            {{ form_row(form.products) }}
+            {{ form_row(form.productsInTaxons) }}
+            {{ form_row(form.taxons) }}
+        </div>
     </div>
     <div class="column">
         <div class="ui segment">
diff --git a/src/Resources/views/Form/theme.html.twig b/src/Resources/views/Form/theme.html.twig
index 841343b8f..66b88405f 100755
--- a/src/Resources/views/Form/theme.html.twig
+++ b/src/Resources/views/Form/theme.html.twig
@@ -104,3 +104,25 @@
         </div>
     {% endapply %}
 {% endmacro %}
+
+{% block sylius_resource_autocomplete_choice_row %}
+    <div class="{% if required %}required {% endif %}{% if disabled %}disabled {% endif %}field{% if (not compound or force_error|default(false)) and not valid %} error{% endif %}">
+        {{- form_label(form) -}}
+        {{- form_help(form) -}}
+        <div
+                class="sylius-autocomplete ui fluid search selection dropdown {% if multiple %}multiple{% endif %}"
+                data-url="{{ remote_url }}"
+                data-choice-name="{{ choice_name }}"
+                data-choice-value="{{ choice_value }}"
+                data-criteria-type="{{ remote_criteria_type }}"
+                data-criteria-name="{{ remote_criteria_name }}"
+                data-load-edit-url="{{ load_edit_url }}"
+        >
+            {{- form_widget(form, {'attr': {'class' : 'autocomplete'}}) -}}
+            <i class="dropdown icon"></i>
+            <div class="default text">{% if placeholder is defined %} {{ placeholder|trans }} {% endif %}</div>
+            <div class="menu"></div>
+        </div>
+        {{- form_errors(form) -}}
+    </div>
+{% endblock %}
diff --git a/src/Twig/Runtime/RenderBlockRuntime.php b/src/Twig/Runtime/RenderBlockRuntime.php
index ddec96185..6b8798be2 100644
--- a/src/Twig/Runtime/RenderBlockRuntime.php
+++ b/src/Twig/Runtime/RenderBlockRuntime.php
@@ -12,6 +12,8 @@
 
 use BitBag\SyliusCmsPlugin\Renderer\ContentElementRendererStrategyInterface;
 use BitBag\SyliusCmsPlugin\Resolver\BlockResourceResolverInterface;
+use Sylius\Component\Core\Model\ProductInterface;
+use Sylius\Component\Core\Model\TaxonInterface;
 use Twig\Environment;
 
 final class RenderBlockRuntime implements RenderBlockRuntimeInterface
@@ -25,21 +27,29 @@ public function __construct(
     ) {
     }
 
-    public function renderBlock(string $code, ?string $template = null): string
+    public function renderBlock(string $code, ?string $template = null, ProductInterface|TaxonInterface $context = null): string
     {
         $block = $this->blockResourceResolver->findOrLog($code);
+        if (null === $block) {
+            return '';
+        }
 
-        if (null !== $block) {
-            $template = $template ?? self::DEFAULT_TEMPLATE;
+        if ($context instanceof TaxonInterface && false === $block->canBeDisplayedForTaxon($context)) {
+            return '';
+        }
 
-            return $this->templatingEngine->render(
-                $template,
-                [
-                    'content' => $this->contentElementRendererStrategy->render($block),
-                ],
-            );
+        if ($context instanceof ProductInterface &&
+            false === $block->canBeDisplayedForProduct($context) &&
+            false === $block->canBeDisplayedForProductInTaxon($context)
+        ) {
+            return '';
         }
 
-        return '';
+        return $this->templatingEngine->render(
+            $template ?? self::DEFAULT_TEMPLATE,
+            [
+                'content' => $this->contentElementRendererStrategy->render($block),
+            ],
+        );
     }
 }
diff --git a/tests/Application/templates/bundles/SyliusShopBundle/Product/show.html.twig b/tests/Application/templates/bundles/SyliusShopBundle/Product/show.html.twig
index eb749f4b3..37da1040b 100755
--- a/tests/Application/templates/bundles/SyliusShopBundle/Product/show.html.twig
+++ b/tests/Application/templates/bundles/SyliusShopBundle/Product/show.html.twig
@@ -52,7 +52,7 @@
 
     {{ render(path('bitbag_sylius_cms_plugin_shop_block_index_by_collection_code', {'collectionCode' : 'products', 'template' : '@BitBagSyliusCmsPlugin/Shop/Block/index.html.twig'})) }}
 
-    {{ bitbag_cms_render_block('lorem_ipsum') }}
+    {{ bitbag_cms_render_block('lorem_ipsum', null, product) }}
 
     {{ sonata_block_render_event('sylius.shop.product.show.before_tabs', {'product': product}) }}
 
diff --git a/tests/Application/templates/bundles/SyliusShopBundle/Taxon/Header/_content.html.twig b/tests/Application/templates/bundles/SyliusShopBundle/Taxon/Header/_content.html.twig
new file mode 100644
index 000000000..c55f25be1
--- /dev/null
+++ b/tests/Application/templates/bundles/SyliusShopBundle/Taxon/Header/_content.html.twig
@@ -0,0 +1,6 @@
+<h1 class="ui monster section dividing header">
+    {{ taxon.name }}
+    <div class="sub header">{{ taxon.description }}</div>
+</h1>
+
+{{ bitbag_cms_render_block('lorem_ipsum', null, taxon) }}

From 7a8ac18b940073105931bccc2b6d905103379cc0 Mon Sep 17 00:00:00 2001
From: jkindly <kozupa.jakub@gmail.com>
Date: Fri, 19 Jul 2024 12:34:57 +0200
Subject: [PATCH 05/15] OP-440: Translations

---
 src/Resources/translations/messages.cs.yml    | 10 ++++++++++
 src/Resources/translations/messages.cs_CZ.yml | 10 ++++++++++
 src/Resources/translations/messages.de.yml    | 10 ++++++++++
 src/Resources/translations/messages.en.yml    | 10 ++++++++++
 src/Resources/translations/messages.es.yml    | 10 ++++++++++
 src/Resources/translations/messages.fr.yml    | 10 ++++++++++
 src/Resources/translations/messages.hr.yml    | 10 ++++++++++
 src/Resources/translations/messages.lt.yml    | 10 ++++++++++
 src/Resources/translations/messages.nl.yml    | 10 ++++++++++
 src/Resources/translations/messages.pl.yml    | 10 ++++++++++
 src/Resources/translations/messages.ru.yml    | 10 ++++++++++
 src/Resources/translations/messages.sk.yml    | 10 ++++++++++
 src/Resources/translations/messages.uk.yml    | 10 ++++++++++
 13 files changed, 130 insertions(+)

diff --git a/src/Resources/translations/messages.cs.yml b/src/Resources/translations/messages.cs.yml
index 6ad54fb2f..1648a6c49 100755
--- a/src/Resources/translations/messages.cs.yml
+++ b/src/Resources/translations/messages.cs.yml
@@ -20,6 +20,16 @@ bitbag_sylius_cms_plugin:
                 products_grid_by_taxon: Produkty v mřížce podle taxonu
             heading_type: Typ nadpisu
         taxon: Taxon
+        display_for_products:
+            label: Zobrazit pro produkty
+            help: Vyberte produkty, ve kterých se bude tento blok zobrazovat
+        display_for_products_in_taxons:
+            label: Zobrazit pro produkty v taxonech
+            help: Tento blok bude zobrazen pro produkty ve vybraných taxonech. Pouze "Hlavní taxon" je brán v úvahu.
+        display_for_taxons:
+            label: Zobrazit pro taxony
+            help: Vyberte taxony, ve kterých se bude tento blok zobrazovat
+        manage_block_display: Správa zobrazení bloku
     cms:
         content_management: Obsahový management
         cms: Obsahový management
diff --git a/src/Resources/translations/messages.cs_CZ.yml b/src/Resources/translations/messages.cs_CZ.yml
index 6ad54fb2f..1648a6c49 100755
--- a/src/Resources/translations/messages.cs_CZ.yml
+++ b/src/Resources/translations/messages.cs_CZ.yml
@@ -20,6 +20,16 @@ bitbag_sylius_cms_plugin:
                 products_grid_by_taxon: Produkty v mřížce podle taxonu
             heading_type: Typ nadpisu
         taxon: Taxon
+        display_for_products:
+            label: Zobrazit pro produkty
+            help: Vyberte produkty, ve kterých se bude tento blok zobrazovat
+        display_for_products_in_taxons:
+            label: Zobrazit pro produkty v taxonech
+            help: Tento blok bude zobrazen pro produkty ve vybraných taxonech. Pouze "Hlavní taxon" je brán v úvahu.
+        display_for_taxons:
+            label: Zobrazit pro taxony
+            help: Vyberte taxony, ve kterých se bude tento blok zobrazovat
+        manage_block_display: Správa zobrazení bloku
     cms:
         content_management: Obsahový management
         cms: Obsahový management
diff --git a/src/Resources/translations/messages.de.yml b/src/Resources/translations/messages.de.yml
index 18cb3c28f..ff3180d51 100755
--- a/src/Resources/translations/messages.de.yml
+++ b/src/Resources/translations/messages.de.yml
@@ -70,3 +70,13 @@ bitbag_sylius_cms_plugin:
                 products_grid_by_taxon: Produkte im Raster nach Taxon
             heading_type: Überschrift-Typ
         taxon: Taxon
+        display_for_products:
+            label: Display für Produkte
+            help: Wählen Sie Produkte aus, in denen dieser Block angezeigt wird
+        display_for_products_in_taxons:
+            label: Display für Produkte in Taxons
+            help: Dieser Block wird für Produkte in ausgewählten Taxonen angezeigt. Nur „Haupttaxon“ ist belegt.
+        display_for_taxons:
+            label: Display für Taxons
+            help: Wählen Sie Taxonen aus, in denen dieser Block angezeigt wird
+        manage_block_display: Blockanzeige verwalten
diff --git a/src/Resources/translations/messages.en.yml b/src/Resources/translations/messages.en.yml
index d8daf5cfa..2a6b073a3 100755
--- a/src/Resources/translations/messages.en.yml
+++ b/src/Resources/translations/messages.en.yml
@@ -75,3 +75,13 @@ bitbag_sylius_cms_plugin:
             heading_type: Heading type
         taxon: Taxon
         seo: SEO
+        display_for_products:
+            label: Display for products
+            help: Select products in which this block will be displayed
+        display_for_products_in_taxons:
+            label: Display for products in taxons
+            help: This block will be displayed for products in selected taxons. Only "Main Taxon" is taken.
+        display_for_taxons:
+            label: Display for taxons
+            help: Select taxons in which this block will be displayed
+        manage_block_display: Manage block display
diff --git a/src/Resources/translations/messages.es.yml b/src/Resources/translations/messages.es.yml
index b93bfcac0..6de50ed77 100755
--- a/src/Resources/translations/messages.es.yml
+++ b/src/Resources/translations/messages.es.yml
@@ -49,3 +49,13 @@ bitbag_sylius_cms_plugin:
                 products_grid_by_taxon: Productos en cuadrícula por taxón
             heading_type: Tipo de encabezado
         taxon: Taxón
+        display_for_products:
+            label: Mostrar para productos
+            help: Seleccione productos en los que se mostrará este bloque
+        display_for_products_in_taxons:
+            label: Mostrar para productos en taxones
+            help: Este bloque se mostrará para productos en los taxones seleccionados. Solo se considera el "Taxón principal".
+        display_for_taxons:
+            label: Mostrar para taxones
+            help: Seleccione taxones en los que se mostrará este bloque
+        manage_block_display: Administrar visualización de bloques
diff --git a/src/Resources/translations/messages.fr.yml b/src/Resources/translations/messages.fr.yml
index 04ece97e9..1ec73eaa1 100755
--- a/src/Resources/translations/messages.fr.yml
+++ b/src/Resources/translations/messages.fr.yml
@@ -67,3 +67,13 @@ bitbag_sylius_cms_plugin:
                 products_grid_by_taxon: Produits en grille par taxon
             heading_type: Type d'en-tête
         taxon: Taxon
+        display_for_products:
+            label: Afficher pour les produits
+            help: Ce bloc sera affiché pour les produits sélectionnés
+        display_for_products_in_taxons:
+            label: Afficher pour les produits dans les taxons
+            help: Ce bloc sera affiché pour les produits des taxons sélectionnés. Seul le « taxon principal » est pris en compte.
+        display_for_taxons:
+            label: Afficher pour les taxons
+            help: Ce bloc sera affiché pour les taxons sélectionnés
+        manage_block_display: Gérer l'affichage du bloc
diff --git a/src/Resources/translations/messages.hr.yml b/src/Resources/translations/messages.hr.yml
index 9296a3222..6f7c423fd 100755
--- a/src/Resources/translations/messages.hr.yml
+++ b/src/Resources/translations/messages.hr.yml
@@ -49,3 +49,13 @@ bitbag_sylius_cms_plugin:
                 products_grid_by_taxon: Proizvodi u mreži po taksonu
             heading_type: Tip naslova
         taxon: Takson
+        display_for_products:
+            label: Prikaz za proizvode
+            help: Ovaj blok će biti prikazan za odabrane proizvode
+        display_for_products_in_taxons:
+            label: Prikaz za proizvode u taksonima
+            help: Ovaj će blok biti prikazan za proizvode u odabranim taksonima. Uzima se samo "Glavni takson".
+        display_for_taxons:
+            label: Prikaz za takson
+            help: Odaberite taksone u kojima će se ovaj blok prikazati
+        manage_block_display: Upravljanje prikazom bloka
diff --git a/src/Resources/translations/messages.lt.yml b/src/Resources/translations/messages.lt.yml
index 6799bf34f..6972d8d70 100644
--- a/src/Resources/translations/messages.lt.yml
+++ b/src/Resources/translations/messages.lt.yml
@@ -66,3 +66,13 @@ bitbag_sylius_cms_plugin:
                 products_grid_by_taxon: Produktų tinklelis pagal taksoną
             heading_type: Antraštės tipas
         taxon: Taksonas
+        display_for_products:
+            label: Rodyti produktams
+            help: Pasirinkite produktus, kuriuose šis blokas bus rodomas
+        display_for_products_in_taxons:
+            label: Rodyti produktams
+            help: Šis blokas bus rodomas produktams pasirinktuose taksonuose. Paimamas tik „Pagrindinis taksonas“.
+        display_for_taxons:
+            label: Rodyti taksonams
+            help: Pasirinkite taksonus, kuriuose šis blokas bus rodomas
+        manage_block_display: Bloko rodymo valdymas
diff --git a/src/Resources/translations/messages.nl.yml b/src/Resources/translations/messages.nl.yml
index 4b23f947a..15e996263 100755
--- a/src/Resources/translations/messages.nl.yml
+++ b/src/Resources/translations/messages.nl.yml
@@ -48,3 +48,13 @@ bitbag_sylius_cms_plugin:
                 products_grid_by_taxon: Producten grid per taxon
             heading_type: Kop type
         taxon: Taxon
+        display_for_products:
+            label: Display voor producten
+            help: Selecteer producten waarin dit blok wordt weergegeven
+        display_for_products_in_taxons:
+            label: Display voor producten in taxons
+            help: Dit blok wordt weergegeven voor producten in geselecteerde taxonen. Alleen "Hoofdtaxon" wordt gebruikt.
+        display_for_taxons:
+            label: Display voor taxons
+            help: Selecteer taxonen waarin dit blok wordt weergegeven
+        manage_block_display: Beheer blok weergave
diff --git a/src/Resources/translations/messages.pl.yml b/src/Resources/translations/messages.pl.yml
index 67a9d2ef3..641ad43cf 100755
--- a/src/Resources/translations/messages.pl.yml
+++ b/src/Resources/translations/messages.pl.yml
@@ -53,3 +53,13 @@ bitbag_sylius_cms_plugin:
                 products_grid_by_taxon: Siatka produktów według taksonomii
             heading_type: Typ nagłówka
         taxon: Taksonomia
+        display_for_products:
+            label: Wyświetlaj dla produktów
+            help: Wybierz produkty, dla których ten blok będzie wyświetlany
+        display_for_products_in_taxons:
+            label: Wyświetlaj dla produktów w taksonomiach
+            help: Ten blok będzie wyświetlany dla produktów w wybranych taksonomiach. Tylko "Główna taksonomia" jest brana pod uwagę.
+        display_for_taxons:
+            label: Wyświetlaj dla taksonomii
+            help: Wybież taksonomie, dla których ten blok będzie wyświetlany
+        manage_block_display: Zarządzaj wyświetlaniem bloku
diff --git a/src/Resources/translations/messages.ru.yml b/src/Resources/translations/messages.ru.yml
index 6de939b42..321edc71c 100755
--- a/src/Resources/translations/messages.ru.yml
+++ b/src/Resources/translations/messages.ru.yml
@@ -66,3 +66,13 @@ bitbag_sylius_cms_plugin:
                 products_grid_by_taxon: Продукты в сетке по таксону
             heading_type: Тип заголовка
         taxon: Таксон
+        display_for_products:
+            label: Отображать для товаров
+            help: Выберите товары, для которых этот блок будет отображаться
+        display_for_products_in_taxons:
+            label: Отображать для товаров в таксономиях
+            help: Этот блок будет отображаться для товаров в выбранных таксонах. Берется только «Основной таксон».
+        display_for_taxons:
+            label: Отображать для таксонов
+            help: Выберите таксоны, в которых будет отображаться этот блок
+        manage_block_display: Управление отображением блоков
diff --git a/src/Resources/translations/messages.sk.yml b/src/Resources/translations/messages.sk.yml
index 3e8fd8221..7091345c7 100644
--- a/src/Resources/translations/messages.sk.yml
+++ b/src/Resources/translations/messages.sk.yml
@@ -67,3 +67,13 @@ bitbag_sylius_cms_plugin:
                 products_grid_by_taxon: Produkty v mriežke podľa taxónu
             heading_type: Typ nadpisu
         taxon: Taxón
+        display_for_products:
+            label: Zobraziť pre produkty
+            help: Tento blok bude zobrazený pre vybrané produkty
+        display_for_products_in_taxons:
+            label: Zobraziť pre produkty v taxónoch
+            help: Tento blok sa zobrazí pri produktoch vo vybraných taxónoch. Zaberie sa iba „hlavný taxón“.
+        display_for_taxons:
+            label: Zobrazenie pre taxóny
+            help: Vyberte taxóny, v ktorých sa tento blok zobrazí
+        manage_block_display: Spravovať zobrazenie bloku
diff --git a/src/Resources/translations/messages.uk.yml b/src/Resources/translations/messages.uk.yml
index dba77b1ec..6786a053e 100755
--- a/src/Resources/translations/messages.uk.yml
+++ b/src/Resources/translations/messages.uk.yml
@@ -66,3 +66,13 @@ bitbag_sylius_cms_plugin:
                 products_grid_by_taxon: Продукти в сітці за таксоном
             heading_type: Тип заголовка
         taxon: Таксон
+        display_for_products:
+            label: Дисплей для продуктів
+            help: Цей блок буде відображатися для вибраних продуктів
+        display_for_products_in_taxons:
+            label: Дисплей для таксонів
+            help: Цей блок відображатиметься для продуктів у вибраних таксонах. Береться лише «Основний таксон».
+        display_for_taxons:
+            label: Дисплей для таксонів
+            help: Виберіть таксони, в яких буде відображатися цей блок
+        manage_block_display: Керування відображенням блоку

From 46838715175b211c378bfdadd5a8bdd07e0e8e6f Mon Sep 17 00:00:00 2001
From: jkindly <kozupa.jakub@gmail.com>
Date: Fri, 19 Jul 2024 12:35:03 +0200
Subject: [PATCH 06/15] OP-440: Migration

---
 src/Migrations/Version20240719070318.php | 47 ++++++++++++++++++++++++
 1 file changed, 47 insertions(+)
 create mode 100644 src/Migrations/Version20240719070318.php

diff --git a/src/Migrations/Version20240719070318.php b/src/Migrations/Version20240719070318.php
new file mode 100644
index 000000000..09bd3a4e2
--- /dev/null
+++ b/src/Migrations/Version20240719070318.php
@@ -0,0 +1,47 @@
+<?php
+
+declare(strict_types=1);
+
+namespace BitBag\SyliusCmsPlugin\Migrations;
+
+use Doctrine\DBAL\Schema\Schema;
+use Doctrine\Migrations\AbstractMigration;
+
+/**
+ * Auto-generated Migration: Please modify to your needs!
+ */
+final class Version20240719070318 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('CREATE TABLE bitbag_cms_block_products (block_id INT NOT NULL, product_id INT NOT NULL, INDEX IDX_C4B9089FE9ED820C (block_id), INDEX IDX_C4B9089F4584665A (product_id), PRIMARY KEY(block_id, product_id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
+        $this->addSql('CREATE TABLE bitbag_cms_block_taxons (block_id INT NOT NULL, taxon_id INT NOT NULL, INDEX IDX_E324C6CEE9ED820C (block_id), INDEX IDX_E324C6CEDE13F470 (taxon_id), PRIMARY KEY(block_id, taxon_id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
+        $this->addSql('CREATE TABLE bitbag_cms_block_products_in_taxons (block_id INT NOT NULL, taxon_id INT NOT NULL, INDEX IDX_DAA9DD18E9ED820C (block_id), INDEX IDX_DAA9DD18DE13F470 (taxon_id), PRIMARY KEY(block_id, taxon_id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
+        $this->addSql('ALTER TABLE bitbag_cms_block_products ADD CONSTRAINT FK_C4B9089FE9ED820C FOREIGN KEY (block_id) REFERENCES bitbag_cms_block (id) ON DELETE CASCADE');
+        $this->addSql('ALTER TABLE bitbag_cms_block_products ADD CONSTRAINT FK_C4B9089F4584665A FOREIGN KEY (product_id) REFERENCES sylius_product (id) ON DELETE CASCADE');
+        $this->addSql('ALTER TABLE bitbag_cms_block_taxons ADD CONSTRAINT FK_E324C6CEE9ED820C FOREIGN KEY (block_id) REFERENCES bitbag_cms_block (id) ON DELETE CASCADE');
+        $this->addSql('ALTER TABLE bitbag_cms_block_taxons ADD CONSTRAINT FK_E324C6CEDE13F470 FOREIGN KEY (taxon_id) REFERENCES sylius_taxon (id) ON DELETE CASCADE');
+        $this->addSql('ALTER TABLE bitbag_cms_block_products_in_taxons ADD CONSTRAINT FK_DAA9DD18E9ED820C FOREIGN KEY (block_id) REFERENCES bitbag_cms_block (id) ON DELETE CASCADE');
+        $this->addSql('ALTER TABLE bitbag_cms_block_products_in_taxons ADD CONSTRAINT FK_DAA9DD18DE13F470 FOREIGN KEY (taxon_id) REFERENCES sylius_taxon (id) ON DELETE CASCADE');
+    }
+
+    public function down(Schema $schema): void
+    {
+        // this down() migration is auto-generated, please modify it to your needs
+        $this->addSql('ALTER TABLE bitbag_cms_block_products DROP FOREIGN KEY FK_C4B9089FE9ED820C');
+        $this->addSql('ALTER TABLE bitbag_cms_block_products DROP FOREIGN KEY FK_C4B9089F4584665A');
+        $this->addSql('ALTER TABLE bitbag_cms_block_taxons DROP FOREIGN KEY FK_E324C6CEE9ED820C');
+        $this->addSql('ALTER TABLE bitbag_cms_block_taxons DROP FOREIGN KEY FK_E324C6CEDE13F470');
+        $this->addSql('ALTER TABLE bitbag_cms_block_products_in_taxons DROP FOREIGN KEY FK_DAA9DD18E9ED820C');
+        $this->addSql('ALTER TABLE bitbag_cms_block_products_in_taxons DROP FOREIGN KEY FK_DAA9DD18DE13F470');
+        $this->addSql('DROP TABLE bitbag_cms_block_products');
+        $this->addSql('DROP TABLE bitbag_cms_block_taxons');
+        $this->addSql('DROP TABLE bitbag_cms_block_products_in_taxons');
+    }
+}

From e9f9c62759bbaadca18d95cfd7d7bf62ca1cb019 Mon Sep 17 00:00:00 2001
From: jkindly <kozupa.jakub@gmail.com>
Date: Fri, 19 Jul 2024 12:47:43 +0200
Subject: [PATCH 07/15] OP-440: PHPStan & ECS fixes

---
 src/Entity/Trait/ProductsAwareTrait.php         | 2 +-
 src/Entity/Trait/ProductsInTaxonsAwareTrait.php | 2 +-
 src/Entity/Trait/TaxonAwareTrait.php            | 2 +-
 src/Form/Type/BlockType.php                     | 2 +-
 4 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/src/Entity/Trait/ProductsAwareTrait.php b/src/Entity/Trait/ProductsAwareTrait.php
index 3710dea32..57c35f557 100755
--- a/src/Entity/Trait/ProductsAwareTrait.php
+++ b/src/Entity/Trait/ProductsAwareTrait.php
@@ -17,7 +17,7 @@
 trait ProductsAwareTrait
 {
     /** @var Collection|ProductInterface[] */
-    protected array|Collection $products;
+    protected Collection $products;
 
     public function initializeProductsCollection(): void
     {
diff --git a/src/Entity/Trait/ProductsInTaxonsAwareTrait.php b/src/Entity/Trait/ProductsInTaxonsAwareTrait.php
index 6e331bdf8..f95dea5a7 100644
--- a/src/Entity/Trait/ProductsInTaxonsAwareTrait.php
+++ b/src/Entity/Trait/ProductsInTaxonsAwareTrait.php
@@ -17,7 +17,7 @@
 trait ProductsInTaxonsAwareTrait
 {
     /** @var Collection|TaxonInterface[] */
-    protected array|Collection $productsInTaxons;
+    protected Collection $productsInTaxons;
 
     public function initializeProductsInTaxonsCollection(): void
     {
diff --git a/src/Entity/Trait/TaxonAwareTrait.php b/src/Entity/Trait/TaxonAwareTrait.php
index 8edc13cc7..6679e4931 100644
--- a/src/Entity/Trait/TaxonAwareTrait.php
+++ b/src/Entity/Trait/TaxonAwareTrait.php
@@ -17,7 +17,7 @@
 trait TaxonAwareTrait
 {
     /** @var Collection|TaxonInterface[] */
-    protected array|Collection $taxons;
+    protected Collection $taxons;
 
     public function initializeTaxonCollection(): void
     {
diff --git a/src/Form/Type/BlockType.php b/src/Form/Type/BlockType.php
index 73fb6c3de..d2472dc34 100755
--- a/src/Form/Type/BlockType.php
+++ b/src/Form/Type/BlockType.php
@@ -65,7 +65,7 @@ public function buildForm(FormBuilderInterface $builder, array $options): void
             ->add('productsInTaxons', TaxonAutocompleteChoiceType::class, [
                 'label' => 'bitbag_sylius_cms_plugin.ui.display_for_products_in_taxons.label',
                 'multiple' => true,
-                'help' => 'bitbag_sylius_cms_plugin.ui.display_for_products_in_taxons.help'
+                'help' => 'bitbag_sylius_cms_plugin.ui.display_for_products_in_taxons.help',
             ])
             ->add('taxons', TaxonAutocompleteChoiceType::class, [
                 'label' => 'bitbag_sylius_cms_plugin.ui.display_for_taxons.label',

From 156989a56e9c35f4d4f560af2d04d06806f31cd6 Mon Sep 17 00:00:00 2001
From: jkindly <kozupa.jakub@gmail.com>
Date: Mon, 22 Jul 2024 08:37:41 +0200
Subject: [PATCH 08/15] OP-440: Spec

---
 spec/Twig/Runtime/RenderBlockRuntimeSpec.php  | 66 ++++++++++++++-----
 .../SyliusShopBundle/Product/show.html.twig   |  2 -
 2 files changed, 49 insertions(+), 19 deletions(-)

diff --git a/spec/Twig/Runtime/RenderBlockRuntimeSpec.php b/spec/Twig/Runtime/RenderBlockRuntimeSpec.php
index 85c9a3df1..72fab0841 100644
--- a/spec/Twig/Runtime/RenderBlockRuntimeSpec.php
+++ b/spec/Twig/Runtime/RenderBlockRuntimeSpec.php
@@ -18,6 +18,8 @@
 use BitBag\SyliusCmsPlugin\Twig\Runtime\RenderBlockRuntime;
 use BitBag\SyliusCmsPlugin\Twig\Runtime\RenderBlockRuntimeInterface;
 use PhpSpec\ObjectBehavior;
+use Sylius\Component\Core\Model\ProductInterface;
+use Sylius\Component\Core\Model\TaxonInterface;
 use Twig\Environment;
 
 final class RenderBlockRuntimeSpec extends ObjectBehavior
@@ -25,7 +27,7 @@ final class RenderBlockRuntimeSpec extends ObjectBehavior
     public function let(
         BlockResourceResolverInterface $blockResourceResolver,
         Environment $templatingEngine,
-        ContentElementRendererStrategyInterface $contentElementRendererStrategy,
+        ContentElementRendererStrategyInterface $contentElementRendererStrategy
     ): void {
         $this->beConstructedWith($blockResourceResolver, $templatingEngine, $contentElementRendererStrategy);
     }
@@ -37,38 +39,68 @@ public function it_is_initializable(): void
 
     public function it_implements_render_block_runtime_interface(): void
     {
-        $this->shouldHaveType(RenderBlockRuntimeInterface::class);
+        $this->shouldImplement(RenderBlockRuntimeInterface::class);
+    }
+
+    public function it_returns_empty_string_when_block_not_found(BlockResourceResolverInterface $blockResourceResolver): void
+    {
+        $blockResourceResolver->findOrLog('code')->willReturn(null);
+
+        $this->renderBlock('code')->shouldReturn('');
     }
 
-    public function it_renders_block(
+    public function it_returns_empty_string_when_block_not_displayable_for_taxon(
         BlockResourceResolverInterface $blockResourceResolver,
         BlockInterface $block,
+        TaxonInterface $taxon
+    ): void {
+        $blockResourceResolver->findOrLog('code')->willReturn($block);
+        $block->canBeDisplayedForTaxon($taxon)->willReturn(false);
+
+        $this->renderBlock('code', null, $taxon)->shouldReturn('');
+    }
+
+    public function it_returns_empty_string_when_block_not_displayable_for_product(
+        BlockResourceResolverInterface $blockResourceResolver,
+        BlockInterface $block,
+        ProductInterface $product
+    ): void {
+        $blockResourceResolver->findOrLog('code')->willReturn($block);
+        $block->canBeDisplayedForProduct($product)->willReturn(false);
+        $block->canBeDisplayedForProductInTaxon($product)->willReturn(false);
+
+        $this->renderBlock('code', null, $product)->shouldReturn('');
+    }
+
+    public function it_renders_block_with_default_template(
+        BlockResourceResolverInterface $blockResourceResolver,
         Environment $templatingEngine,
         ContentElementRendererStrategyInterface $contentElementRendererStrategy,
+        BlockInterface $block
     ): void {
-        $blockResourceResolver->findOrLog('bitbag')->willReturn($block);
+        $blockResourceResolver->findOrLog('code')->willReturn($block);
         $contentElementRendererStrategy->render($block)->willReturn('rendered content');
-        $templatingEngine->render(
-            '@BitBagSyliusCmsPlugin/Shop/Block/show.html.twig',
-            ['content' => 'rendered content'],
-        )->willReturn('<div>BitBag</div>');
 
-        $this->renderBlock('bitbag');
+        $templatingEngine->render('@BitBagSyliusCmsPlugin/Shop/Block/show.html.twig', [
+            'content' => 'rendered content',
+        ])->willReturn('rendered block');
+
+        $this->renderBlock('code')->shouldReturn('rendered block');
     }
 
-    public function it_renders_block_with_template(
+    public function it_renders_block_with_custom_template(
         BlockResourceResolverInterface $blockResourceResolver,
-        BlockInterface $block,
         Environment $templatingEngine,
         ContentElementRendererStrategyInterface $contentElementRendererStrategy,
+        BlockInterface $block
     ): void {
-        $blockResourceResolver->findOrLog('bitbag')->willReturn($block);
+        $blockResourceResolver->findOrLog('code')->willReturn($block);
         $contentElementRendererStrategy->render($block)->willReturn('rendered content');
-        $templatingEngine->render(
-            '@BitBagSyliusCmsPlugin/Shop/Block/otherTemplate.html.twig',
-            ['content' => 'rendered content'],
-        )->willReturn('<div>BitBag Other Template</div>');
 
-        $this->renderBlock('bitbag', '@BitBagSyliusCmsPlugin/Shop/Block/otherTemplate.html.twig');
+        $templatingEngine->render('custom_template.html.twig', [
+            'content' => 'rendered content',
+        ])->willReturn('rendered block');
+
+        $this->renderBlock('code', 'custom_template.html.twig')->shouldReturn('rendered block');
     }
 }
diff --git a/tests/Application/templates/bundles/SyliusShopBundle/Product/show.html.twig b/tests/Application/templates/bundles/SyliusShopBundle/Product/show.html.twig
index 37da1040b..8955ade69 100755
--- a/tests/Application/templates/bundles/SyliusShopBundle/Product/show.html.twig
+++ b/tests/Application/templates/bundles/SyliusShopBundle/Product/show.html.twig
@@ -50,8 +50,6 @@
         </div>
     </div>
 
-    {{ render(path('bitbag_sylius_cms_plugin_shop_block_index_by_collection_code', {'collectionCode' : 'products', 'template' : '@BitBagSyliusCmsPlugin/Shop/Block/index.html.twig'})) }}
-
     {{ bitbag_cms_render_block('lorem_ipsum', null, product) }}
 
     {{ sonata_block_render_event('sylius.shop.product.show.before_tabs', {'product': product}) }}

From f573e448b21275479da55645c4e02f11aa02ca53 Mon Sep 17 00:00:00 2001
From: jkindly <kozupa.jakub@gmail.com>
Date: Mon, 22 Jul 2024 08:40:33 +0200
Subject: [PATCH 09/15] OP-440: Migration comments

---
 src/Migrations/Version20240719070318.php | 13 +++++++------
 1 file changed, 7 insertions(+), 6 deletions(-)

diff --git a/src/Migrations/Version20240719070318.php b/src/Migrations/Version20240719070318.php
index 09bd3a4e2..dbbccf8df 100644
--- a/src/Migrations/Version20240719070318.php
+++ b/src/Migrations/Version20240719070318.php
@@ -1,5 +1,11 @@
 <?php
 
+/*
+ * This file was created by developers working at BitBag
+ * Do you need more information about us and what we do? Visit our https://bitbag.io website!
+ * We are hiring developers from all over the world. Join us and start your new, exciting adventure and become part of us: https://bitbag.io/career
+*/
+
 declare(strict_types=1);
 
 namespace BitBag\SyliusCmsPlugin\Migrations;
@@ -7,19 +13,15 @@
 use Doctrine\DBAL\Schema\Schema;
 use Doctrine\Migrations\AbstractMigration;
 
-/**
- * Auto-generated Migration: Please modify to your needs!
- */
 final class Version20240719070318 extends AbstractMigration
 {
     public function getDescription(): string
     {
-        return '';
+        return 'This migration adds products, taxons, products in taxons to the block entity.';
     }
 
     public function up(Schema $schema): void
     {
-        // this up() migration is auto-generated, please modify it to your needs
         $this->addSql('CREATE TABLE bitbag_cms_block_products (block_id INT NOT NULL, product_id INT NOT NULL, INDEX IDX_C4B9089FE9ED820C (block_id), INDEX IDX_C4B9089F4584665A (product_id), PRIMARY KEY(block_id, product_id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
         $this->addSql('CREATE TABLE bitbag_cms_block_taxons (block_id INT NOT NULL, taxon_id INT NOT NULL, INDEX IDX_E324C6CEE9ED820C (block_id), INDEX IDX_E324C6CEDE13F470 (taxon_id), PRIMARY KEY(block_id, taxon_id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
         $this->addSql('CREATE TABLE bitbag_cms_block_products_in_taxons (block_id INT NOT NULL, taxon_id INT NOT NULL, INDEX IDX_DAA9DD18E9ED820C (block_id), INDEX IDX_DAA9DD18DE13F470 (taxon_id), PRIMARY KEY(block_id, taxon_id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
@@ -33,7 +35,6 @@ public function up(Schema $schema): void
 
     public function down(Schema $schema): void
     {
-        // this down() migration is auto-generated, please modify it to your needs
         $this->addSql('ALTER TABLE bitbag_cms_block_products DROP FOREIGN KEY FK_C4B9089FE9ED820C');
         $this->addSql('ALTER TABLE bitbag_cms_block_products DROP FOREIGN KEY FK_C4B9089F4584665A');
         $this->addSql('ALTER TABLE bitbag_cms_block_taxons DROP FOREIGN KEY FK_E324C6CEE9ED820C');

From 8590a3aabe667af9b68e9bf75d62710d489eb2af Mon Sep 17 00:00:00 2001
From: jkindly <kozupa.jakub@gmail.com>
Date: Mon, 22 Jul 2024 09:01:50 +0200
Subject: [PATCH 10/15] OP-440: PHPUnit fix

---
 src/Resources/config/serialization/Block.xml      |  7 +++++--
 .../Api/BlockTest/test_it_get_block_by_id.json    |  5 ++++-
 .../Api/BlockTest/test_it_get_blocks.json         | 15 ++++++++++++---
 3 files changed, 21 insertions(+), 6 deletions(-)

diff --git a/src/Resources/config/serialization/Block.xml b/src/Resources/config/serialization/Block.xml
index 7549db77b..6bcab96ea 100644
--- a/src/Resources/config/serialization/Block.xml
+++ b/src/Resources/config/serialization/Block.xml
@@ -16,15 +16,18 @@
         <attribute name="collections">
             <group>shop:cms:read</group>
         </attribute>
-        <attribute name="products">
+        <attribute name="channels">
             <group>shop:cms:read</group>
         </attribute>
-        <attribute name="channels">
+        <attribute name="products">
             <group>shop:cms:read</group>
         </attribute>
         <attribute name="taxons">
             <group>shop:cms:read</group>
         </attribute>
+        <attribute name="productsInTaxons">
+            <group>shop:cms:read</group>
+        </attribute>
         <attribute name="contentElements">
             <group>shop:cms:read</group>
         </attribute>
diff --git a/tests/Functional/Responses/Expected/Api/BlockTest/test_it_get_block_by_id.json b/tests/Functional/Responses/Expected/Api/BlockTest/test_it_get_block_by_id.json
index 857e57f70..dbc9c1802 100644
--- a/tests/Functional/Responses/Expected/Api/BlockTest/test_it_get_block_by_id.json
+++ b/tests/Functional/Responses/Expected/Api/BlockTest/test_it_get_block_by_id.json
@@ -16,5 +16,8 @@
   "channels": [
     "/api/v2/shop/channels/code"
   ],
-  "contentElements": []
+  "contentElements": [],
+  "products": [],
+  "taxons": [],
+  "productsInTaxons": []
 }
diff --git a/tests/Functional/Responses/Expected/Api/BlockTest/test_it_get_blocks.json b/tests/Functional/Responses/Expected/Api/BlockTest/test_it_get_blocks.json
index 7a770ed6f..c163c76eb 100644
--- a/tests/Functional/Responses/Expected/Api/BlockTest/test_it_get_blocks.json
+++ b/tests/Functional/Responses/Expected/Api/BlockTest/test_it_get_blocks.json
@@ -20,7 +20,10 @@
       "channels": [
         "/api/v2/shop/channels/code"
       ],
-      "contentElements": []
+      "contentElements": [],
+      "products": [],
+      "taxons": [],
+      "productsInTaxons": []
     },
     {
       "@id": "/api/v2/shop/cms-plugin/blocks/@integer@",
@@ -39,7 +42,10 @@
       "channels": [
         "/api/v2/shop/channels/code"
       ],
-      "contentElements": []
+      "contentElements": [],
+      "products": [],
+      "taxons": [],
+      "productsInTaxons": []
     },
     {
       "@id": "/api/v2/shop/cms-plugin/blocks/@integer@",
@@ -58,7 +64,10 @@
       "channels": [
         "/api/v2/shop/channels/code"
       ],
-      "contentElements": []
+      "contentElements": [],
+      "products": [],
+      "taxons": [],
+      "productsInTaxons": []
     }
   ],
   "hydra:totalItems": 3

From cf8fb16b3ea834081b7b404efb8bd85ed57cc567 Mon Sep 17 00:00:00 2001
From: jkindly <kozupa.jakub@gmail.com>
Date: Tue, 23 Jul 2024 11:56:19 +0200
Subject: [PATCH 11/15] OP-441: Twig function to render Collection

---
 src/Form/Type/CollectionType.php              |  2 -
 .../Collection/CollectionBlocksRenderer.php   | 45 ++++++++++++++++++
 .../Collection/CollectionMediaRenderer.php    | 46 +++++++++++++++++++
 .../Collection/CollectionPagesRenderer.php    | 45 ++++++++++++++++++
 .../CollectionRendererInterface.php           | 20 ++++++++
 src/Renderer/CollectionRendererStrategy.php   | 35 ++++++++++++++
 .../CollectionRendererStrategyInterface.php   | 18 ++++++++
 src/Renderer/PageLinkRenderer.php             | 41 +++++++++++++++++
 src/Renderer/PageLinkRendererInterface.php    | 18 ++++++++
 src/Resolver/CollectionResourceResolver.php   | 40 ++++++++++++++++
 .../CollectionResourceResolverInterface.php   | 18 ++++++++
 .../js/bitbag/bitbag-content-configuration.js |  8 ++--
 src/Resources/config/services/renderer.xml    | 24 ++++++++++
 src/Resources/config/services/resolver.xml    |  5 ++
 src/Resources/config/services/twig.xml        | 12 +++++
 .../views/Shop/Collection/show.html.twig      |  3 ++
 .../views/Shop/Media/Show/file.html.twig      | 18 ++++----
 .../views/Shop/Media/Show/image.html.twig     | 14 +++---
 src/Resources/views/Shop/Page/link.html.twig  |  3 ++
 src/Sorter/SorterById.php                     | 27 +++++++++++
 .../Extension/RenderCollectionExtension.php   | 33 +++++++++++++
 src/Twig/Runtime/RenderCollectionRuntime.php  | 42 +++++++++++++++++
 .../RenderCollectionRuntimeInterface.php      | 16 +++++++
 .../SyliusShopBundle/Homepage/index.html.twig |  1 +
 24 files changed, 514 insertions(+), 20 deletions(-)
 create mode 100644 src/Renderer/Collection/CollectionBlocksRenderer.php
 create mode 100644 src/Renderer/Collection/CollectionMediaRenderer.php
 create mode 100644 src/Renderer/Collection/CollectionPagesRenderer.php
 create mode 100644 src/Renderer/Collection/CollectionRendererInterface.php
 create mode 100644 src/Renderer/CollectionRendererStrategy.php
 create mode 100644 src/Renderer/CollectionRendererStrategyInterface.php
 create mode 100644 src/Renderer/PageLinkRenderer.php
 create mode 100644 src/Renderer/PageLinkRendererInterface.php
 create mode 100755 src/Resolver/CollectionResourceResolver.php
 create mode 100755 src/Resolver/CollectionResourceResolverInterface.php
 create mode 100644 src/Resources/views/Shop/Collection/show.html.twig
 create mode 100644 src/Resources/views/Shop/Page/link.html.twig
 create mode 100644 src/Sorter/SorterById.php
 create mode 100644 src/Twig/Extension/RenderCollectionExtension.php
 create mode 100644 src/Twig/Runtime/RenderCollectionRuntime.php
 create mode 100644 src/Twig/Runtime/RenderCollectionRuntimeInterface.php

diff --git a/src/Form/Type/CollectionType.php b/src/Form/Type/CollectionType.php
index 73f802d9e..6478f89da 100755
--- a/src/Form/Type/CollectionType.php
+++ b/src/Form/Type/CollectionType.php
@@ -10,7 +10,6 @@
 
 namespace BitBag\SyliusCmsPlugin\Form\Type;
 
-use BitBag\SyliusCmsPlugin\Entity\MediaInterface;
 use Sylius\Bundle\ResourceBundle\Form\Type\AbstractResourceType;
 use Symfony\Component\Form\Event\PreSubmitEvent;
 use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
@@ -55,7 +54,6 @@ public function buildForm(FormBuilderInterface $builder, array $options): void
             ->add('media', MediaAutocompleteChoiceType::class, [
                 'label' => 'bitbag_sylius_cms_plugin.ui.media',
                 'multiple' => true,
-                'media_type' => MediaInterface::IMAGE_TYPE,
             ])
             ->addEventListener(FormEvents::PRE_SUBMIT, function (PreSubmitEvent $event): void {
                 $formData = $event->getData();
diff --git a/src/Renderer/Collection/CollectionBlocksRenderer.php b/src/Renderer/Collection/CollectionBlocksRenderer.php
new file mode 100644
index 000000000..584da2568
--- /dev/null
+++ b/src/Renderer/Collection/CollectionBlocksRenderer.php
@@ -0,0 +1,45 @@
+<?php
+
+/*
+ * This file was created by developers working at BitBag
+ * Do you need more information about us and what we do? Visit our https://bitbag.io website!
+ * We are hiring developers from all over the world. Join us and start your new, exciting adventure and become part of us: https://bitbag.io/career
+*/
+
+declare(strict_types=1);
+
+namespace BitBag\SyliusCmsPlugin\Renderer\Collection;
+
+use BitBag\SyliusCmsPlugin\Entity\BlockInterface;
+use BitBag\SyliusCmsPlugin\Entity\CollectionInterface;
+use BitBag\SyliusCmsPlugin\Renderer\ContentElementRendererStrategyInterface;
+use BitBag\SyliusCmsPlugin\Sorter\SorterById;
+use Webmozart\Assert\Assert;
+
+final class CollectionBlocksRenderer implements CollectionRendererInterface
+{
+    public function __construct(private ContentElementRendererStrategyInterface $contentElementRendererStrategy)
+    {
+    }
+
+    public function render(CollectionInterface $collection, ?int $countToRender = null): string
+    {
+        $content = '';
+        $iterator = 0;
+        Assert::notNull($collection->getBlocks());
+        /** @var BlockInterface $block */
+        foreach (SorterById::sort($collection->getBlocks()->toArray()) as $block) {
+            $content .= $this->contentElementRendererStrategy->render($block);
+            if (++$iterator === $countToRender) {
+                break;
+            }
+        }
+
+        return $content;
+    }
+
+    public function supports(CollectionInterface $collection): bool
+    {
+        return $collection->getBlocks()?->count() > 0;
+    }
+}
diff --git a/src/Renderer/Collection/CollectionMediaRenderer.php b/src/Renderer/Collection/CollectionMediaRenderer.php
new file mode 100644
index 000000000..b5c7ee8a3
--- /dev/null
+++ b/src/Renderer/Collection/CollectionMediaRenderer.php
@@ -0,0 +1,46 @@
+<?php
+
+/*
+ * This file was created by developers working at BitBag
+ * Do you need more information about us and what we do? Visit our https://bitbag.io website!
+ * We are hiring developers from all over the world. Join us and start your new, exciting adventure and become part of us: https://bitbag.io/career
+*/
+
+declare(strict_types=1);
+
+namespace BitBag\SyliusCmsPlugin\Renderer\Collection;
+
+use BitBag\SyliusCmsPlugin\Entity\CollectionInterface;
+use BitBag\SyliusCmsPlugin\Entity\MediaInterface;
+use BitBag\SyliusCmsPlugin\Sorter\SorterById;
+use BitBag\SyliusCmsPlugin\Twig\Runtime\RenderMediaRuntimeInterface;
+use Webmozart\Assert\Assert;
+
+final class CollectionMediaRenderer implements CollectionRendererInterface
+{
+    public function __construct(private RenderMediaRuntimeInterface $renderMediaRuntime)
+    {
+    }
+
+    public function render(CollectionInterface $collection, ?int $countToRender = null): string
+    {
+        $content = '';
+        $iterator = 0;
+        Assert::notNull($collection->getMedia());
+        /** @var MediaInterface $media */
+        foreach (SorterById::sort($collection->getMedia()->toArray()) as $media) {
+            Assert::notNull($media->getCode());
+            $content .= $this->renderMediaRuntime->renderMedia($media->getCode());
+            if (++$iterator === $countToRender) {
+                break;
+            }
+        }
+
+        return $content;
+    }
+
+    public function supports(CollectionInterface $collection): bool
+    {
+        return $collection->getMedia()?->count() > 0;
+    }
+}
diff --git a/src/Renderer/Collection/CollectionPagesRenderer.php b/src/Renderer/Collection/CollectionPagesRenderer.php
new file mode 100644
index 000000000..587434e17
--- /dev/null
+++ b/src/Renderer/Collection/CollectionPagesRenderer.php
@@ -0,0 +1,45 @@
+<?php
+
+/*
+ * This file was created by developers working at BitBag
+ * Do you need more information about us and what we do? Visit our https://bitbag.io website!
+ * We are hiring developers from all over the world. Join us and start your new, exciting adventure and become part of us: https://bitbag.io/career
+*/
+
+declare(strict_types=1);
+
+namespace BitBag\SyliusCmsPlugin\Renderer\Collection;
+
+use BitBag\SyliusCmsPlugin\Entity\CollectionInterface;
+use BitBag\SyliusCmsPlugin\Entity\PageInterface;
+use BitBag\SyliusCmsPlugin\Renderer\PageLinkRendererInterface;
+use BitBag\SyliusCmsPlugin\Sorter\SorterById;
+use Webmozart\Assert\Assert;
+
+final class CollectionPagesRenderer implements CollectionRendererInterface
+{
+    public function __construct(private PageLinkRendererInterface $pageLinkRenderer)
+    {
+    }
+
+    public function render(CollectionInterface $collection, ?int $countToRender = null): string
+    {
+        $content = '';
+        $iterator = 0;
+        Assert::notNull($collection->getPages());
+        /** @var PageInterface $page */
+        foreach (SorterById::sort($collection->getPages()->toArray(), 'desc') as $page) {
+            $content .= $this->pageLinkRenderer->render($page);
+            if (++$iterator === $countToRender) {
+                break;
+            }
+        }
+
+        return $content;
+    }
+
+    public function supports(CollectionInterface $collection): bool
+    {
+        return $collection->getPages()?->count() > 0;
+    }
+}
diff --git a/src/Renderer/Collection/CollectionRendererInterface.php b/src/Renderer/Collection/CollectionRendererInterface.php
new file mode 100644
index 000000000..4de98d3dc
--- /dev/null
+++ b/src/Renderer/Collection/CollectionRendererInterface.php
@@ -0,0 +1,20 @@
+<?php
+
+/*
+ * This file was created by developers working at BitBag
+ * Do you need more information about us and what we do? Visit our https://bitbag.io website!
+ * We are hiring developers from all over the world. Join us and start your new, exciting adventure and become part of us: https://bitbag.io/career
+*/
+
+declare(strict_types=1);
+
+namespace BitBag\SyliusCmsPlugin\Renderer\Collection;
+
+use BitBag\SyliusCmsPlugin\Entity\CollectionInterface;
+
+interface CollectionRendererInterface
+{
+    public function render(CollectionInterface $collection, ?int $countToRender = null): string;
+
+    public function supports(CollectionInterface $collection): bool;
+}
diff --git a/src/Renderer/CollectionRendererStrategy.php b/src/Renderer/CollectionRendererStrategy.php
new file mode 100644
index 000000000..5ec435386
--- /dev/null
+++ b/src/Renderer/CollectionRendererStrategy.php
@@ -0,0 +1,35 @@
+<?php
+
+/*
+ * This file was created by developers working at BitBag
+ * Do you need more information about us and what we do? Visit our https://bitbag.io website!
+ * We are hiring developers from all over the world. Join us and start your new, exciting adventure and become part of us: https://bitbag.io/career
+*/
+
+declare(strict_types=1);
+
+namespace BitBag\SyliusCmsPlugin\Renderer;
+
+use BitBag\SyliusCmsPlugin\Entity\CollectionInterface;
+use BitBag\SyliusCmsPlugin\Renderer\Collection\CollectionRendererInterface;
+
+final class CollectionRendererStrategy implements CollectionRendererStrategyInterface
+{
+    /**
+     * @param CollectionRendererInterface[] $renderers
+     */
+    public function __construct(private iterable $renderers)
+    {
+    }
+
+    public function render(CollectionInterface $collection, ?int $countToRender = null): string
+    {
+        foreach ($this->renderers as $renderer) {
+            if ($renderer->supports($collection)) {
+                return $renderer->render($collection, $countToRender);
+            }
+        }
+
+        return '';
+    }
+}
diff --git a/src/Renderer/CollectionRendererStrategyInterface.php b/src/Renderer/CollectionRendererStrategyInterface.php
new file mode 100644
index 000000000..d8c0c606f
--- /dev/null
+++ b/src/Renderer/CollectionRendererStrategyInterface.php
@@ -0,0 +1,18 @@
+<?php
+
+/*
+ * This file was created by developers working at BitBag
+ * Do you need more information about us and what we do? Visit our https://bitbag.io website!
+ * We are hiring developers from all over the world. Join us and start your new, exciting adventure and become part of us: https://bitbag.io/career
+*/
+
+declare(strict_types=1);
+
+namespace BitBag\SyliusCmsPlugin\Renderer;
+
+use BitBag\SyliusCmsPlugin\Entity\CollectionInterface;
+
+interface CollectionRendererStrategyInterface
+{
+    public function render(CollectionInterface $collection, ?int $countToRender = null): string;
+}
diff --git a/src/Renderer/PageLinkRenderer.php b/src/Renderer/PageLinkRenderer.php
new file mode 100644
index 000000000..c218462e6
--- /dev/null
+++ b/src/Renderer/PageLinkRenderer.php
@@ -0,0 +1,41 @@
+<?php
+
+/*
+ * This file was created by developers working at BitBag
+ * Do you need more information about us and what we do? Visit our https://bitbag.io website!
+ * We are hiring developers from all over the world. Join us and start your new, exciting adventure and become part of us: https://bitbag.io/career
+*/
+
+declare(strict_types=1);
+
+namespace BitBag\SyliusCmsPlugin\Renderer;
+
+use BitBag\SyliusCmsPlugin\Entity\PageInterface;
+use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
+use Twig\Environment;
+
+final class PageLinkRenderer implements PageLinkRendererInterface
+{
+    private const DEFAULT_TEMPLATE = '@BitBagSyliusCmsPlugin/Shop/Page/link.html.twig';
+
+    public function __construct(
+        private UrlGeneratorInterface $urlGenerator,
+        private Environment $twig,
+    ) {
+    }
+
+    public function render(PageInterface $page, ?string $template = null): string
+    {
+        return $this->twig->render(
+            $template ?? self::DEFAULT_TEMPLATE,
+            [
+                'link' => $this->urlGenerator->generate(
+                    'bitbag_sylius_cms_plugin_shop_page_show',
+                    ['slug' => $page->getSlug()],
+                    UrlGeneratorInterface::ABSOLUTE_URL,
+                ),
+                'name' => $page->getName(),
+            ],
+        );
+    }
+}
diff --git a/src/Renderer/PageLinkRendererInterface.php b/src/Renderer/PageLinkRendererInterface.php
new file mode 100644
index 000000000..a7a80001b
--- /dev/null
+++ b/src/Renderer/PageLinkRendererInterface.php
@@ -0,0 +1,18 @@
+<?php
+
+/*
+ * This file was created by developers working at BitBag
+ * Do you need more information about us and what we do? Visit our https://bitbag.io website!
+ * We are hiring developers from all over the world. Join us and start your new, exciting adventure and become part of us: https://bitbag.io/career
+*/
+
+declare(strict_types=1);
+
+namespace BitBag\SyliusCmsPlugin\Renderer;
+
+use BitBag\SyliusCmsPlugin\Entity\PageInterface;
+
+interface PageLinkRendererInterface
+{
+    public function render(PageInterface $page, ?string $template = null): string;
+}
diff --git a/src/Resolver/CollectionResourceResolver.php b/src/Resolver/CollectionResourceResolver.php
new file mode 100755
index 000000000..dd2a59b24
--- /dev/null
+++ b/src/Resolver/CollectionResourceResolver.php
@@ -0,0 +1,40 @@
+<?php
+
+/*
+ * This file was created by developers working at BitBag
+ * Do you need more information about us and what we do? Visit our https://bitbag.io website!
+ * We are hiring developers from all over the world. Join us and start your new, exciting adventure and become part of us: https://bitbag.io/career
+*/
+
+declare(strict_types=1);
+
+namespace BitBag\SyliusCmsPlugin\Resolver;
+
+use BitBag\SyliusCmsPlugin\Entity\CollectionInterface;
+use BitBag\SyliusCmsPlugin\Repository\CollectionRepositoryInterface;
+use Psr\Log\LoggerInterface;
+
+final class CollectionResourceResolver implements CollectionResourceResolverInterface
+{
+    public function __construct(
+        private CollectionRepositoryInterface $collectionRepository,
+        private LoggerInterface $logger,
+    ) {
+    }
+
+    public function findOrLog(string $code): ?CollectionInterface
+    {
+        $collection = $this->collectionRepository->findOneByCode($code);
+
+        if (false === $collection instanceof CollectionInterface) {
+            $this->logger->warning(sprintf(
+                'Collection with "%s" code was not found in the database.',
+                $code,
+            ));
+
+            return null;
+        }
+
+        return $collection;
+    }
+}
diff --git a/src/Resolver/CollectionResourceResolverInterface.php b/src/Resolver/CollectionResourceResolverInterface.php
new file mode 100755
index 000000000..4ea8827e0
--- /dev/null
+++ b/src/Resolver/CollectionResourceResolverInterface.php
@@ -0,0 +1,18 @@
+<?php
+
+/*
+ * This file was created by developers working at BitBag
+ * Do you need more information about us and what we do? Visit our https://bitbag.io website!
+ * We are hiring developers from all over the world. Join us and start your new, exciting adventure and become part of us: https://bitbag.io/career
+*/
+
+declare(strict_types=1);
+
+namespace BitBag\SyliusCmsPlugin\Resolver;
+
+use BitBag\SyliusCmsPlugin\Entity\CollectionInterface;
+
+interface CollectionResourceResolverInterface
+{
+    public function findOrLog(string $code): ?CollectionInterface;
+}
diff --git a/src/Resources/assets/admin/js/bitbag/bitbag-content-configuration.js b/src/Resources/assets/admin/js/bitbag/bitbag-content-configuration.js
index ee45066fa..b221f2ca4 100644
--- a/src/Resources/assets/admin/js/bitbag/bitbag-content-configuration.js
+++ b/src/Resources/assets/admin/js/bitbag/bitbag-content-configuration.js
@@ -5,6 +5,10 @@
 */
 
 $(document).ready(function() {
+    $('.bitbag-media-autocomplete, .sylius-autocomplete').each((index, element) => {
+        $(element).autoComplete();
+    });
+
     let pageElements = '#bitbag_sylius_cms_plugin_page_contentElements';
     let blockElements = '#bitbag_sylius_cms_plugin_block_contentElements';
 
@@ -69,10 +73,6 @@ $(document).ready(function() {
         }
     });
 
-    $('.bitbag-media-autocomplete, .sylius-autocomplete').each((index, element) => {
-        $(element).autoComplete();
-    });
-
     $(`${collectionHolder} [data-form-collection="item"]`).each((index, element) => {
         $(document).loadContentConfiguration(element);
     });
diff --git a/src/Resources/config/services/renderer.xml b/src/Resources/config/services/renderer.xml
index 2f2fba36b..a0943d659 100644
--- a/src/Resources/config/services/renderer.xml
+++ b/src/Resources/config/services/renderer.xml
@@ -64,5 +64,29 @@
             <argument type="service" id="sylius.repository.taxon" />
             <tag name="bitbag_sylius_cms_plugin.renderer.content_element" />
         </service>
+
+        <service id="bitbag_sylius_cms_plugin.collection_renderer_strategy" class="BitBag\SyliusCmsPlugin\Renderer\CollectionRendererStrategy">
+            <argument type="tagged_iterator" tag="bitbag_sylius_cms_plugin.renderer.collection" />
+        </service>
+
+        <service id="bitbag_sylius_cms_plugin.renderer.collection.blocks" class="BitBag\SyliusCmsPlugin\Renderer\Collection\CollectionBlocksRenderer">
+            <argument type="service" id="bitbag_sylius_cms_plugin.content_element_renderer_strategy" />
+            <tag name="bitbag_sylius_cms_plugin.renderer.collection" />
+        </service>
+
+        <service id="bitbag_sylius_cms_plugin.renderer.collection.media" class="BitBag\SyliusCmsPlugin\Renderer\Collection\CollectionMediaRenderer">
+            <argument type="service" id="bitbag_sylius_cms_plugin.twig.runtime.media" />
+            <tag name="bitbag_sylius_cms_plugin.renderer.collection" />
+        </service>
+
+        <service id="bitbag_sylius_cms_plugin.renderer.collection.pages" class="BitBag\SyliusCmsPlugin\Renderer\Collection\CollectionPagesRenderer">
+            <argument type="service" id="bitbag_sylius_cms_plugin.page_link_renderer" />
+            <tag name="bitbag_sylius_cms_plugin.renderer.collection" />
+        </service>
+
+        <service id="bitbag_sylius_cms_plugin.page_link_renderer" class="BitBag\SyliusCmsPlugin\Renderer\PageLinkRenderer">
+            <argument type="service" id="router.default" />
+            <argument type="service" id="twig" />
+        </service>
     </services>
 </container>
diff --git a/src/Resources/config/services/resolver.xml b/src/Resources/config/services/resolver.xml
index 2bfd80270..9d51b70b1 100644
--- a/src/Resources/config/services/resolver.xml
+++ b/src/Resources/config/services/resolver.xml
@@ -34,6 +34,11 @@
             <argument type="service" id="sylius.context.channel" />
         </service>
 
+        <service id="bitbag_sylius_cms_plugin.resolver.collection_resource" class="BitBag\SyliusCmsPlugin\Resolver\CollectionResourceResolver" public="true">
+            <argument type="service" id="bitbag_sylius_cms_plugin.repository.collection" />
+            <argument type="service" id="logger" />
+        </service>
+
         <service id="bitbag_sylius_cms_plugin.resolver.page_resource" class="BitBag\SyliusCmsPlugin\Resolver\PageResourceResolver" public="true">
             <argument type="service" id="bitbag_sylius_cms_plugin.repository.page" />
             <argument type="service" id="logger" />
diff --git a/src/Resources/config/services/twig.xml b/src/Resources/config/services/twig.xml
index 39d108966..d831b84ca 100644
--- a/src/Resources/config/services/twig.xml
+++ b/src/Resources/config/services/twig.xml
@@ -24,6 +24,18 @@
             <tag name="twig.runtime" />
         </service>
 
+        <service id="bitbag_sylius_cms_plugin.twig.extension.collection" class="BitBag\SyliusCmsPlugin\Twig\Extension\RenderCollectionExtension">
+            <argument type="service" id="bitbag_sylius_cms_plugin.twig.runtime.collection" />
+            <tag name="twig.extension" />
+        </service>
+
+        <service id="bitbag_sylius_cms_plugin.twig.runtime.collection" class="BitBag\SyliusCmsPlugin\Twig\Runtime\RenderCollectionRuntime">
+            <argument type="service" id="twig" />
+            <argument type="service" id="bitbag_sylius_cms_plugin.resolver.collection_resource" />
+            <argument type="service" id="bitbag_sylius_cms_plugin.collection_renderer_strategy" />
+            <tag name="twig.runtime" />
+        </service>
+
         <service id="bitbag_sylius_cms_plugin.twig.extension.media" class="BitBag\SyliusCmsPlugin\Twig\Extension\RenderMediaExtension">
             <argument type="service" id="bitbag_sylius_cms_plugin.twig.runtime.media" />
             <tag name="twig.extension" />
diff --git a/src/Resources/views/Shop/Collection/show.html.twig b/src/Resources/views/Shop/Collection/show.html.twig
new file mode 100644
index 000000000..c1e138d62
--- /dev/null
+++ b/src/Resources/views/Shop/Collection/show.html.twig
@@ -0,0 +1,3 @@
+<div class="bitbag-collection">
+    {{ content|raw }}
+</div>
diff --git a/src/Resources/views/Shop/Media/Show/file.html.twig b/src/Resources/views/Shop/Media/Show/file.html.twig
index 62a370885..2a03c5d81 100755
--- a/src/Resources/views/Shop/Media/Show/file.html.twig
+++ b/src/Resources/views/Shop/Media/Show/file.html.twig
@@ -1,10 +1,12 @@
-{% if null != media.name %}
-    <h2>{{ media.name|raw }}</h2>
-{% endif %}
+<div>
+    {% if null != media.name %}
+        <h2>{{ media.name|raw }}</h2>
+    {% endif %}
 
-<p>{{ bitbag_cms_render_content(media) }}</p>
+    <p>{{ bitbag_cms_render_content(media) }}</p>
 
-<a class="ui icon labeled primary button" href="{{ path('bitbag_sylius_cms_plugin_shop_media_download', {'code': media.code}) }}">
-    <i class="download icon"></i>
-    {{ 'bitbag_sylius_cms_plugin.ui.download'|trans }}
-</a>
+    <a class="ui icon labeled primary button" href="{{ path('bitbag_sylius_cms_plugin_shop_media_download', {'code': media.code}) }}">
+        <i class="download icon"></i>
+        {{ 'bitbag_sylius_cms_plugin.ui.download'|trans }}
+    </a>
+</div>
diff --git a/src/Resources/views/Shop/Media/Show/image.html.twig b/src/Resources/views/Shop/Media/Show/image.html.twig
index edf7565dc..0b13ea9f0 100755
--- a/src/Resources/views/Shop/Media/Show/image.html.twig
+++ b/src/Resources/views/Shop/Media/Show/image.html.twig
@@ -1,8 +1,10 @@
-<h2>{{ media.name|raw }}</h2>
+<div>
+    <h2>{{ media.name|raw }}</h2>
 
-<img class="ui fluid image" src="{{ media.path }}" alt="{{ media.alt }}"
-    {% if media.width is not null %} width="{{ media.width }}" {% endif %}
-    {% if media.height is not null %} height="{{ media.height }}" {% endif %}
-/>
+    <img class="ui fluid image" src="{{ media.path }}" alt="{{ media.alt }}"
+        {% if media.width is not null %} width="{{ media.width }}" {% endif %}
+        {% if media.height is not null %} height="{{ media.height }}" {% endif %}
+    />
 
-<p>{{ bitbag_cms_render_content(media) }}</p>
+    <p>{{ bitbag_cms_render_content(media) }}</p>
+</div>
diff --git a/src/Resources/views/Shop/Page/link.html.twig b/src/Resources/views/Shop/Page/link.html.twig
new file mode 100644
index 000000000..c55da6b38
--- /dev/null
+++ b/src/Resources/views/Shop/Page/link.html.twig
@@ -0,0 +1,3 @@
+<div>
+    <a href="{{ link }}">{{ name }}</a>
+</div>
diff --git a/src/Sorter/SorterById.php b/src/Sorter/SorterById.php
new file mode 100644
index 000000000..16757f288
--- /dev/null
+++ b/src/Sorter/SorterById.php
@@ -0,0 +1,27 @@
+<?php
+
+/*
+ * This file was created by developers working at BitBag
+ * Do you need more information about us and what we do? Visit our https://bitbag.io website!
+ * We are hiring developers from all over the world. Join us and start your new, exciting adventure and become part of us: https://bitbag.io/career
+*/
+
+declare(strict_types=1);
+
+namespace BitBag\SyliusCmsPlugin\Sorter;
+
+final class SorterById
+{
+    public static function sort(array $elements, string $direction = 'asc'): array
+    {
+        usort($elements, static function ($element1, $element2) use ($direction) {
+            if ($direction === 'asc') {
+                return $element1->getId() <=> $element2->getId();
+            }
+
+            return $element2->getId() <=> $element1->getId();
+        });
+
+        return $elements;
+    }
+}
diff --git a/src/Twig/Extension/RenderCollectionExtension.php b/src/Twig/Extension/RenderCollectionExtension.php
new file mode 100644
index 000000000..246be6897
--- /dev/null
+++ b/src/Twig/Extension/RenderCollectionExtension.php
@@ -0,0 +1,33 @@
+<?php
+
+/*
+ * This file was created by developers working at BitBag
+ * Do you need more information about us and what we do? Visit our https://bitbag.io website!
+ * We are hiring developers from all over the world. Join us and start your new, exciting adventure and become part of us: https://bitbag.io/career
+*/
+
+declare(strict_types=1);
+
+namespace BitBag\SyliusCmsPlugin\Twig\Extension;
+
+use BitBag\SyliusCmsPlugin\Twig\Runtime\RenderCollectionRuntimeInterface;
+use Twig\Extension\AbstractExtension;
+use Twig\TwigFunction;
+
+final class RenderCollectionExtension extends AbstractExtension
+{
+    public function __construct(private RenderCollectionRuntimeInterface $collectionRuntime)
+    {
+    }
+
+    public function getFunctions(): array
+    {
+        return [
+            new TwigFunction(
+                'bitbag_cms_render_collection',
+                [$this->collectionRuntime, 'renderCollection'],
+                ['is_safe' => ['html']],
+            ),
+        ];
+    }
+}
diff --git a/src/Twig/Runtime/RenderCollectionRuntime.php b/src/Twig/Runtime/RenderCollectionRuntime.php
new file mode 100644
index 000000000..45b9b5901
--- /dev/null
+++ b/src/Twig/Runtime/RenderCollectionRuntime.php
@@ -0,0 +1,42 @@
+<?php
+
+/*
+ * This file was created by developers working at BitBag
+ * Do you need more information about us and what we do? Visit our https://bitbag.io website!
+ * We are hiring developers from all over the world. Join us and start your new, exciting adventure and become part of us: https://bitbag.io/career
+*/
+
+declare(strict_types=1);
+
+namespace BitBag\SyliusCmsPlugin\Twig\Runtime;
+
+use BitBag\SyliusCmsPlugin\Renderer\CollectionRendererStrategyInterface;
+use BitBag\SyliusCmsPlugin\Resolver\CollectionResourceResolverInterface;
+use Twig\Environment;
+
+final class RenderCollectionRuntime implements RenderCollectionRuntimeInterface
+{
+    private const DEFAULT_TEMPLATE = '@BitBagSyliusCmsPlugin/Shop/Collection/show.html.twig';
+
+    public function __construct(
+        private Environment $twig,
+        private CollectionResourceResolverInterface $collectionResourceResolver,
+        private CollectionRendererStrategyInterface $collectionRenderer,
+    ) {
+    }
+
+    public function renderCollection(string $code, ?int $countToRender = null, ?string $template = null): string
+    {
+        $collection = $this->collectionResourceResolver->findOrLog($code);
+        if (null === $collection) {
+            return '';
+        }
+
+        return $this->twig->render(
+            $template ?? self::DEFAULT_TEMPLATE,
+            [
+                'content' => $this->collectionRenderer->render($collection, $countToRender),
+            ],
+        );
+    }
+}
diff --git a/src/Twig/Runtime/RenderCollectionRuntimeInterface.php b/src/Twig/Runtime/RenderCollectionRuntimeInterface.php
new file mode 100644
index 000000000..ac6f52a17
--- /dev/null
+++ b/src/Twig/Runtime/RenderCollectionRuntimeInterface.php
@@ -0,0 +1,16 @@
+<?php
+
+/*
+ * This file was created by developers working at BitBag
+ * Do you need more information about us and what we do? Visit our https://bitbag.io website!
+ * We are hiring developers from all over the world. Join us and start your new, exciting adventure and become part of us: https://bitbag.io/career
+*/
+
+declare(strict_types=1);
+
+namespace BitBag\SyliusCmsPlugin\Twig\Runtime;
+
+interface RenderCollectionRuntimeInterface
+{
+    public function renderCollection(string $code, ?int $countToRender = null, ?string $template = null): string;
+}
diff --git a/tests/Application/templates/bundles/SyliusShopBundle/Homepage/index.html.twig b/tests/Application/templates/bundles/SyliusShopBundle/Homepage/index.html.twig
index ab008b882..83f010ec7 100755
--- a/tests/Application/templates/bundles/SyliusShopBundle/Homepage/index.html.twig
+++ b/tests/Application/templates/bundles/SyliusShopBundle/Homepage/index.html.twig
@@ -1,6 +1,7 @@
 {% extends '@SyliusShop/layout.html.twig' %}
 
 {% block content %}
+    {{ bitbag_cms_render_collection('blog') }}
     <div class="top one">
         <div class="ui center aligned segment stackable grid cms-logo">
             <a href="https://github.com/BitBagCommerce/SyliusCmsPlugin">

From 9a239a327e5c8eaacf46d936aada61d67604a459 Mon Sep 17 00:00:00 2001
From: jkindly <kozupa.jakub@gmail.com>
Date: Tue, 23 Jul 2024 11:59:24 +0200
Subject: [PATCH 12/15] OP-441: ECS fixes

---
 src/Renderer/Collection/CollectionBlocksRenderer.php | 2 +-
 src/Renderer/Collection/CollectionMediaRenderer.php  | 2 +-
 src/Renderer/Collection/CollectionPagesRenderer.php  | 2 +-
 src/Sorter/SorterById.php                            | 2 +-
 4 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/src/Renderer/Collection/CollectionBlocksRenderer.php b/src/Renderer/Collection/CollectionBlocksRenderer.php
index 584da2568..250cf5709 100644
--- a/src/Renderer/Collection/CollectionBlocksRenderer.php
+++ b/src/Renderer/Collection/CollectionBlocksRenderer.php
@@ -40,6 +40,6 @@ public function render(CollectionInterface $collection, ?int $countToRender = nu
 
     public function supports(CollectionInterface $collection): bool
     {
-        return $collection->getBlocks()?->count() > 0;
+        return 0 < $collection->getBlocks()?->count();
     }
 }
diff --git a/src/Renderer/Collection/CollectionMediaRenderer.php b/src/Renderer/Collection/CollectionMediaRenderer.php
index b5c7ee8a3..ddb91be54 100644
--- a/src/Renderer/Collection/CollectionMediaRenderer.php
+++ b/src/Renderer/Collection/CollectionMediaRenderer.php
@@ -41,6 +41,6 @@ public function render(CollectionInterface $collection, ?int $countToRender = nu
 
     public function supports(CollectionInterface $collection): bool
     {
-        return $collection->getMedia()?->count() > 0;
+        return 0 < $collection->getMedia()?->count();
     }
 }
diff --git a/src/Renderer/Collection/CollectionPagesRenderer.php b/src/Renderer/Collection/CollectionPagesRenderer.php
index 587434e17..2b5578cf6 100644
--- a/src/Renderer/Collection/CollectionPagesRenderer.php
+++ b/src/Renderer/Collection/CollectionPagesRenderer.php
@@ -40,6 +40,6 @@ public function render(CollectionInterface $collection, ?int $countToRender = nu
 
     public function supports(CollectionInterface $collection): bool
     {
-        return $collection->getPages()?->count() > 0;
+        return 0 < $collection->getPages()?->count();
     }
 }
diff --git a/src/Sorter/SorterById.php b/src/Sorter/SorterById.php
index 16757f288..5ecdcd959 100644
--- a/src/Sorter/SorterById.php
+++ b/src/Sorter/SorterById.php
@@ -15,7 +15,7 @@ final class SorterById
     public static function sort(array $elements, string $direction = 'asc'): array
     {
         usort($elements, static function ($element1, $element2) use ($direction) {
-            if ($direction === 'asc') {
+            if ('asc' === $direction) {
                 return $element1->getId() <=> $element2->getId();
             }
 

From a2a21acece320f0afacb0af0af3c247017783748 Mon Sep 17 00:00:00 2001
From: jkindly <kozupa.jakub@gmail.com>
Date: Tue, 23 Jul 2024 14:12:36 +0200
Subject: [PATCH 13/15] OP-441: PHP Spec

---
 .../CollectionBlocksRendererSpec.php          | 100 ++++++++++++++++++
 .../CollectionMediaRendererSpec.php           |  78 ++++++++++++++
 .../CollectionPagesRendererSpec.php           |  92 ++++++++++++++++
 .../CollectionRendererStrategySpec.php        |  73 +++++++++++++
 spec/Renderer/PageLinkRendererSpec.php        |  88 +++++++++++++++
 5 files changed, 431 insertions(+)
 create mode 100644 spec/Renderer/Collection/CollectionBlocksRendererSpec.php
 create mode 100644 spec/Renderer/Collection/CollectionMediaRendererSpec.php
 create mode 100644 spec/Renderer/Collection/CollectionPagesRendererSpec.php
 create mode 100644 spec/Renderer/CollectionRendererStrategySpec.php
 create mode 100644 spec/Renderer/PageLinkRendererSpec.php

diff --git a/spec/Renderer/Collection/CollectionBlocksRendererSpec.php b/spec/Renderer/Collection/CollectionBlocksRendererSpec.php
new file mode 100644
index 000000000..d131e1cbf
--- /dev/null
+++ b/spec/Renderer/Collection/CollectionBlocksRendererSpec.php
@@ -0,0 +1,100 @@
+<?php
+
+/*
+ * This file was created by developers working at BitBag
+ * Do you need more information about us and what we do? Visit our https://bitbag.io website!
+ * We are hiring developers from all over the world. Join us and start your new, exciting adventure and become part of us: https://bitbag.io/career
+*/
+
+declare(strict_types=1);
+
+namespace spec\BitBag\SyliusCmsPlugin\Renderer\Collection;
+
+use BitBag\SyliusCmsPlugin\Entity\BlockInterface;
+use BitBag\SyliusCmsPlugin\Entity\CollectionInterface;
+use BitBag\SyliusCmsPlugin\Renderer\Collection\CollectionBlocksRenderer;
+use BitBag\SyliusCmsPlugin\Renderer\Collection\CollectionRendererInterface;
+use BitBag\SyliusCmsPlugin\Renderer\ContentElementRendererStrategyInterface;
+use Doctrine\Common\Collections\ArrayCollection;
+use PhpSpec\ObjectBehavior;
+
+final class CollectionBlocksRendererSpec extends ObjectBehavior
+{
+    public function let(ContentElementRendererStrategyInterface $contentElementRendererStrategy): void
+    {
+        $this->beConstructedWith($contentElementRendererStrategy);
+    }
+
+    public function it_is_initializable(): void
+    {
+        $this->shouldHaveType(CollectionBlocksRenderer::class);
+    }
+
+    public function it_implements_collection_renderer_interface(): void
+    {
+        $this->shouldImplement(CollectionRendererInterface::class);
+    }
+
+    public function it_renders_blocks_from_collection(
+        ContentElementRendererStrategyInterface $contentElementRendererStrategy,
+        CollectionInterface $collection,
+        BlockInterface $block1,
+        BlockInterface $block2
+    ): void
+    {
+        $blocks = new ArrayCollection([$block1->getWrappedObject(), $block2->getWrappedObject()]);
+        $collection->getBlocks()->willReturn($blocks);
+
+        $contentElementRendererStrategy->render($block1)->willReturn('block1_content');
+        $contentElementRendererStrategy->render($block2)->willReturn('block2_content');
+
+        $this->render($collection)->shouldReturn('block1_contentblock2_content');
+    }
+
+    public function it_limits_number_of_rendered_blocks(
+        ContentElementRendererStrategyInterface $contentElementRendererStrategy,
+        CollectionInterface $collection,
+        BlockInterface $block1,
+        BlockInterface $block2
+    ): void
+    {
+        $blocks = new ArrayCollection([$block1->getWrappedObject(), $block2->getWrappedObject()]);
+        $collection->getBlocks()->willReturn($blocks);
+
+        $contentElementRendererStrategy->render($block1)->willReturn('block1_content');
+        $contentElementRendererStrategy->render($block2)->willReturn('block2_content');
+
+        $this->render($collection, 1)->shouldReturn('block1_content');
+    }
+
+    public function it_supports_collections_with_blocks(
+        CollectionInterface $collection,
+        BlockInterface $block
+    ): void
+    {
+        $blocks = new ArrayCollection([$block]);
+        $collection->getBlocks()->willReturn($blocks);
+
+        $this->supports($collection)->shouldReturn(true);
+    }
+
+    public function it_does_not_support_empty_collections(
+        CollectionInterface $collection
+    ): void
+    {
+        $collection->getBlocks()->willReturn(new ArrayCollection());
+
+        $this->supports($collection)->shouldReturn(false);
+    }
+
+    public function it_throws_exception_when_blocks_are_null(
+        ContentElementRendererStrategyInterface $contentElementRendererStrategy,
+        CollectionInterface $collection
+    ): void
+    {
+        $collection->getBlocks()->willReturn(null);
+
+        $this->shouldThrow(\InvalidArgumentException::class)
+            ->during('render', [$collection]);
+    }
+}
diff --git a/spec/Renderer/Collection/CollectionMediaRendererSpec.php b/spec/Renderer/Collection/CollectionMediaRendererSpec.php
new file mode 100644
index 000000000..d04537038
--- /dev/null
+++ b/spec/Renderer/Collection/CollectionMediaRendererSpec.php
@@ -0,0 +1,78 @@
+<?php
+
+/*
+ * This file was created by developers working at BitBag
+ * Do you need more information about us and what we do? Visit our https://bitbag.io website!
+ * We are hiring developers from all over the world. Join us and start your new, exciting adventure and become part of us: https://bitbag.io/career
+*/
+
+declare(strict_types=1);
+
+namespace spec\BitBag\SyliusCmsPlugin\Renderer\Collection;
+
+use BitBag\SyliusCmsPlugin\Entity\CollectionInterface;
+use BitBag\SyliusCmsPlugin\Entity\MediaInterface;
+use BitBag\SyliusCmsPlugin\Renderer\Collection\CollectionMediaRenderer;
+use BitBag\SyliusCmsPlugin\Renderer\Collection\CollectionRendererInterface;
+use BitBag\SyliusCmsPlugin\Twig\Runtime\RenderMediaRuntimeInterface;
+use Doctrine\Common\Collections\ArrayCollection;
+use PhpSpec\ObjectBehavior;
+
+final class CollectionMediaRendererSpec extends ObjectBehavior
+{
+    function let(RenderMediaRuntimeInterface $renderMediaRuntime)
+    {
+        $this->beConstructedWith($renderMediaRuntime);
+    }
+
+    function it_is_initializable()
+    {
+        $this->shouldHaveType(CollectionMediaRenderer::class);
+        $this->shouldImplement(CollectionRendererInterface::class);
+    }
+
+    function it_renders_media_collection(RenderMediaRuntimeInterface $renderMediaRuntime, CollectionInterface $collection, MediaInterface $media1, MediaInterface $media2)
+    {
+        $media1->getId()->willReturn(1);
+        $media2->getId()->willReturn(2);
+
+        $media1->getCode()->willReturn('media_code_1');
+        $media2->getCode()->willReturn('media_code_2');
+
+        $collection->getMedia()->willReturn(new ArrayCollection([$media1->getWrappedObject(), $media2->getWrappedObject()]));
+
+        $renderMediaRuntime->renderMedia('media_code_1')->willReturn('media1');
+        $renderMediaRuntime->renderMedia('media_code_2')->willReturn('media2');
+
+        $this->render($collection)->shouldReturn('media1media2');
+    }
+
+    function it_renders_limited_number_of_media(RenderMediaRuntimeInterface $renderMediaRuntime, CollectionInterface $collection, MediaInterface $media1, MediaInterface $media2)
+    {
+        $media1->getId()->willReturn(1);
+        $media2->getId()->willReturn(2);
+
+        $media1->getCode()->willReturn('media_code_1');
+        $media2->getCode()->willReturn('media_code_2');
+
+        $collection->getMedia()->willReturn(new ArrayCollection([$media1->getWrappedObject(), $media2->getWrappedObject()]));
+
+        $renderMediaRuntime->renderMedia('media_code_1')->willReturn('media1');
+
+        $this->render($collection, 1)->shouldReturn('media1');
+    }
+
+    function it_supports_collections_with_media(CollectionInterface $collection, MediaInterface $media1)
+    {
+        $collection->getMedia()->willReturn(new ArrayCollection([$media1]));
+
+        $this->supports($collection)->shouldReturn(true);
+    }
+
+    function it_does_not_support_collections_without_media(CollectionInterface $collection)
+    {
+        $collection->getMedia()->willReturn(new ArrayCollection());
+
+        $this->supports($collection)->shouldReturn(false);
+    }
+}
diff --git a/spec/Renderer/Collection/CollectionPagesRendererSpec.php b/spec/Renderer/Collection/CollectionPagesRendererSpec.php
new file mode 100644
index 000000000..e5421f555
--- /dev/null
+++ b/spec/Renderer/Collection/CollectionPagesRendererSpec.php
@@ -0,0 +1,92 @@
+<?php
+
+/*
+ * This file was created by developers working at BitBag
+ * Do you need more information about us and what we do? Visit our https://bitbag.io website!
+ * We are hiring developers from all over the world. Join us and start your new, exciting adventure and become part of us: https://bitbag.io/career
+*/
+
+declare(strict_types=1);
+
+namespace spec\BitBag\SyliusCmsPlugin\Renderer\Collection;
+
+use BitBag\SyliusCmsPlugin\Entity\CollectionInterface;
+use BitBag\SyliusCmsPlugin\Entity\PageInterface;
+use BitBag\SyliusCmsPlugin\Renderer\Collection\CollectionPagesRenderer;
+use BitBag\SyliusCmsPlugin\Renderer\Collection\CollectionRendererInterface;
+use BitBag\SyliusCmsPlugin\Renderer\PageLinkRendererInterface;
+use Doctrine\Common\Collections\ArrayCollection;
+use PhpSpec\ObjectBehavior;
+
+final class CollectionPagesRendererSpec extends ObjectBehavior
+{
+    public function let(PageLinkRendererInterface $pageLinkRenderer): void
+    {
+        $this->beConstructedWith($pageLinkRenderer);
+    }
+
+    public function it_is_initializable(): void
+    {
+        $this->shouldHaveType(CollectionPagesRenderer::class);
+    }
+
+    public function it_implements_collection_renderer_interface(): void
+    {
+        $this->shouldImplement(CollectionRendererInterface::class);
+    }
+
+    public function it_renders_pages_from_collection(
+        PageLinkRendererInterface $pageLinkRenderer,
+        CollectionInterface $collection,
+        PageInterface $page1,
+        PageInterface $page2
+    ): void
+    {
+        $page1->getId()->willReturn(2);
+        $page2->getId()->willReturn(1);
+
+        $collection->getPages()->willReturn(new ArrayCollection([$page1->getWrappedObject(), $page2->getWrappedObject()]));
+
+        $pageLinkRenderer->render($page1)->willReturn('page1_content');
+        $pageLinkRenderer->render($page2)->willReturn('page2_content');
+
+        $this->render($collection)->shouldReturn('page1_contentpage2_content');
+    }
+
+    public function it_limits_number_of_rendered_pages(
+        PageLinkRendererInterface $pageLinkRenderer,
+        CollectionInterface $collection,
+        PageInterface $page1,
+        PageInterface $page2
+    ): void
+    {
+        $page1->getId()->willReturn(2);
+        $page2->getId()->willReturn(1);
+
+        $collection->getPages()->willReturn(new ArrayCollection([$page1->getWrappedObject(), $page2->getWrappedObject()]));
+
+        $pageLinkRenderer->render($page1)->willReturn('page1_content');
+        $pageLinkRenderer->render($page2)->willReturn('page2_content');
+
+        $this->render($collection, 1)->shouldReturn('page1_content');
+    }
+
+    public function it_supports_collections_with_pages(
+        CollectionInterface $collection,
+        PageInterface $page
+    ): void
+    {
+        $collection->getPages()->willReturn(new ArrayCollection([$page]));
+
+        $this->supports($collection)->shouldReturn(true);
+    }
+
+    public function it_does_not_support_empty_collections(
+        CollectionInterface $collection
+    ): void
+    {
+        $collection->getPages()->willReturn(new ArrayCollection());
+
+        $this->supports($collection)->shouldReturn(false);
+    }
+}
diff --git a/spec/Renderer/CollectionRendererStrategySpec.php b/spec/Renderer/CollectionRendererStrategySpec.php
new file mode 100644
index 000000000..67aebde51
--- /dev/null
+++ b/spec/Renderer/CollectionRendererStrategySpec.php
@@ -0,0 +1,73 @@
+<?php
+
+/*
+ * This file was created by developers working at BitBag
+ * Do you need more information about us and what we do? Visit our https://bitbag.io website!
+ * We are hiring developers from all over the world. Join us and start your new, exciting adventure and become part of us: https://bitbag.io/career
+*/
+
+declare(strict_types=1);
+
+namespace spec\BitBag\SyliusCmsPlugin\Renderer;
+
+use BitBag\SyliusCmsPlugin\Entity\CollectionInterface;
+use BitBag\SyliusCmsPlugin\Renderer\Collection\CollectionRendererInterface;
+use BitBag\SyliusCmsPlugin\Renderer\CollectionRendererStrategy;
+use BitBag\SyliusCmsPlugin\Renderer\CollectionRendererStrategyInterface;
+use PhpSpec\ObjectBehavior;
+
+final class CollectionRendererStrategySpec extends ObjectBehavior
+{
+    public function let(CollectionRendererInterface $renderer1, CollectionRendererInterface $renderer2): void
+    {
+        $this->beConstructedWith([$renderer1, $renderer2]);
+    }
+
+    public function it_is_initializable(): void
+    {
+        $this->shouldHaveType(CollectionRendererStrategy::class);
+    }
+
+    public function it_implements_collection_renderer_strategy_interface(): void
+    {
+        $this->shouldImplement(CollectionRendererStrategyInterface::class);
+    }
+
+    public function it_renders_collection_using_supported_renderer(
+        CollectionRendererInterface $renderer1,
+        CollectionRendererInterface $renderer2,
+        CollectionInterface $collection
+    ): void
+    {
+        $renderer1->supports($collection)->willReturn(false);
+        $renderer2->supports($collection)->willReturn(true);
+        $renderer2->render($collection, null)->willReturn('rendered content');
+
+        $this->render($collection)->shouldReturn('rendered content');
+    }
+
+    public function it_renders_collection_with_count_to_render(
+        CollectionRendererInterface $renderer1,
+        CollectionRendererInterface $renderer2,
+        CollectionInterface $collection
+    ): void
+    {
+        $renderer1->supports($collection)->willReturn(false);
+        $renderer2->supports($collection)->willReturn(true);
+        $renderer2->render($collection, 5)->willReturn('rendered content with count');
+
+        $this->render($collection, 5)->shouldReturn('rendered content with count');
+    }
+
+    public function it_returns_empty_string_when_no_renderer_supports_collection(
+        CollectionRendererInterface $renderer1,
+        CollectionRendererInterface $renderer2,
+        CollectionInterface $collection
+    ): void
+    {
+        $renderer1->supports($collection)->willReturn(false);
+        $renderer2->supports($collection)->willReturn(false);
+
+        $this->render($collection)->shouldReturn('');
+    }
+}
diff --git a/spec/Renderer/PageLinkRendererSpec.php b/spec/Renderer/PageLinkRendererSpec.php
new file mode 100644
index 000000000..45fb7b5f9
--- /dev/null
+++ b/spec/Renderer/PageLinkRendererSpec.php
@@ -0,0 +1,88 @@
+<?php
+
+/*
+ * This file was created by developers working at BitBag
+ * Do you need more information about us and what we do? Visit our https://bitbag.io website!
+ * We are hiring developers from all over the world. Join us and start your new, exciting adventure and become part of us: https://bitbag.io/career
+*/
+
+declare(strict_types=1);
+
+namespace spec\BitBag\SyliusCmsPlugin\Renderer;
+
+use BitBag\SyliusCmsPlugin\Entity\PageInterface;
+use BitBag\SyliusCmsPlugin\Renderer\PageLinkRenderer;
+use BitBag\SyliusCmsPlugin\Renderer\PageLinkRendererInterface;
+use PhpSpec\ObjectBehavior;
+use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
+use Twig\Environment;
+
+final class PageLinkRendererSpec extends ObjectBehavior
+{
+    public function let(
+        UrlGeneratorInterface $urlGenerator,
+        Environment $twig
+    ): void {
+        $this->beConstructedWith($urlGenerator, $twig);
+    }
+
+    public function it_is_initializable(): void
+    {
+        $this->shouldHaveType(PageLinkRenderer::class);
+    }
+
+    public function it_implements_page_link_renderer_interface(): void
+    {
+        $this->shouldImplement(PageLinkRendererInterface::class);
+    }
+
+    public function it_renders_page_link_with_default_template(
+        UrlGeneratorInterface $urlGenerator,
+        Environment $twig,
+        PageInterface $page
+    ): void {
+        $page->getSlug()->willReturn('page-slug');
+        $page->getName()->willReturn('Page Name');
+
+        $urlGenerator->generate(
+            'bitbag_sylius_cms_plugin_shop_page_show',
+            ['slug' => 'page-slug'],
+            UrlGeneratorInterface::ABSOLUTE_URL
+        )->willReturn('http://example.com/page-slug');
+
+        $twig->render(
+            '@BitBagSyliusCmsPlugin/Shop/Page/link.html.twig',
+            [
+                'link' => 'http://example.com/page-slug',
+                'name' => 'Page Name',
+            ]
+        )->willReturn('<a href="http://example.com/page-slug">Page Name</a>');
+
+        $this->render($page)->shouldReturn('<a href="http://example.com/page-slug">Page Name</a>');
+    }
+
+    public function it_renders_page_link_with_custom_template(
+        UrlGeneratorInterface $urlGenerator,
+        Environment $twig,
+        PageInterface $page
+    ): void {
+        $page->getSlug()->willReturn('page-slug');
+        $page->getName()->willReturn('Page Name');
+
+        $urlGenerator->generate(
+            'bitbag_sylius_cms_plugin_shop_page_show',
+            ['slug' => 'page-slug'],
+            UrlGeneratorInterface::ABSOLUTE_URL
+        )->willReturn('http://example.com/page-slug');
+
+        $twig->render(
+            'custom_template.html.twig',
+            [
+                'link' => 'http://example.com/page-slug',
+                'name' => 'Page Name',
+            ]
+        )->willReturn('<a href="http://example.com/page-slug">Page Name</a>');
+
+        $this->render($page, 'custom_template.html.twig')->shouldReturn('<a href="http://example.com/page-slug">Page Name</a>');
+    }
+}

From 20e54cf1f5d59176f1d6a4f6fa8d508ba1efc71a Mon Sep 17 00:00:00 2001
From: jkindly <kozupa.jakub@gmail.com>
Date: Wed, 24 Jul 2024 08:56:03 +0200
Subject: [PATCH 14/15] OP-441: Remove useless specs

---
 .../Extension/RenderBlockExtensionSpec.php    | 47 -------------------
 .../RenderContentElementsExtensionSpec.php    | 40 ----------------
 .../Extension/RenderContentExtensionSpec.php  | 40 ----------------
 .../Extension/RenderMediaExtensionSpec.php    | 47 -------------------
 .../Extension/RenderPageLinkExtensionSpec.php | 42 -----------------
 5 files changed, 216 deletions(-)
 delete mode 100755 spec/Twig/Extension/RenderBlockExtensionSpec.php
 delete mode 100644 spec/Twig/Extension/RenderContentElementsExtensionSpec.php
 delete mode 100644 spec/Twig/Extension/RenderContentExtensionSpec.php
 delete mode 100755 spec/Twig/Extension/RenderMediaExtensionSpec.php
 delete mode 100644 spec/Twig/Extension/RenderPageLinkExtensionSpec.php

diff --git a/spec/Twig/Extension/RenderBlockExtensionSpec.php b/spec/Twig/Extension/RenderBlockExtensionSpec.php
deleted file mode 100755
index a2d6c2985..000000000
--- a/spec/Twig/Extension/RenderBlockExtensionSpec.php
+++ /dev/null
@@ -1,47 +0,0 @@
-<?php
-
-/*
- * This file was created by developers working at BitBag
- * Do you need more information about us and what we do? Visit our https://bitbag.io website!
- * We are hiring developers from all over the world. Join us and start your new, exciting adventure and become part of us: https://bitbag.io/career
-*/
-
-declare(strict_types=1);
-
-namespace spec\BitBag\SyliusCmsPlugin\Twig\Extension;
-
-use BitBag\SyliusCmsPlugin\Twig\Extension\RenderBlockExtension;
-use BitBag\SyliusCmsPlugin\Twig\Runtime\RenderBlockRuntimeInterface;
-use PhpSpec\ObjectBehavior;
-use Twig\Extension\AbstractExtension;
-use Twig\TwigFunction;
-
-final class RenderBlockExtensionSpec extends ObjectBehavior
-{
-    public function let(
-        RenderBlockRuntimeInterface $blockRuntime
-    ): void {
-        $this->beConstructedWith($blockRuntime);
-    }
-
-    public function it_is_initializable(): void
-    {
-        $this->shouldHaveType(RenderBlockExtension::class);
-    }
-
-    public function it_extends_abstract_extension(): void
-    {
-        $this->shouldHaveType(AbstractExtension::class);
-    }
-
-    public function it_returns_functions(): void
-    {
-        $functions = $this->getFunctions();
-
-        $functions->shouldHaveCount(1);
-
-        foreach ($functions as $function) {
-            $function->shouldHaveType(TwigFunction::class);
-        }
-    }
-}
diff --git a/spec/Twig/Extension/RenderContentElementsExtensionSpec.php b/spec/Twig/Extension/RenderContentElementsExtensionSpec.php
deleted file mode 100644
index b7309efd0..000000000
--- a/spec/Twig/Extension/RenderContentElementsExtensionSpec.php
+++ /dev/null
@@ -1,40 +0,0 @@
-<?php
-
-/*
- * This file was created by developers working at BitBag
- * Do you need more information about us and what we do? Visit our https://bitbag.io website!
- * We are hiring developers from all over the world. Join us and start your new, exciting adventure and become part of us: https://bitbag.io/career
-*/
-
-declare(strict_types=1);
-
-namespace spec\BitBag\SyliusCmsPlugin\Twig\Extension;
-
-use BitBag\SyliusCmsPlugin\Twig\Extension\RenderContentElementsExtension;
-use PhpSpec\ObjectBehavior;
-use Twig\Extension\AbstractExtension;
-use Twig\TwigFunction;
-
-final class RenderContentElementsExtensionSpec extends ObjectBehavior
-{
-    public function it_is_initializable(): void
-    {
-        $this->shouldHaveType(RenderContentElementsExtension::class);
-    }
-
-    public function it_extends_abstract_extension(): void
-    {
-        $this->shouldHaveType(AbstractExtension::class);
-    }
-
-    public function it_returns_functions(): void
-    {
-        $functions = $this->getFunctions();
-
-        $functions->shouldHaveCount(1);
-
-        foreach ($functions as $function) {
-            $function->shouldHaveType(TwigFunction::class);
-        }
-    }
-}
diff --git a/spec/Twig/Extension/RenderContentExtensionSpec.php b/spec/Twig/Extension/RenderContentExtensionSpec.php
deleted file mode 100644
index 52e8c7f2c..000000000
--- a/spec/Twig/Extension/RenderContentExtensionSpec.php
+++ /dev/null
@@ -1,40 +0,0 @@
-<?php
-
-/*
- * This file was created by developers working at BitBag
- * Do you need more information about us and what we do? Visit our https://bitbag.io website!
- * We are hiring developers from all over the world. Join us and start your new, exciting adventure and become part of us: https://bitbag.io/career
-*/
-
-declare(strict_types=1);
-
-namespace spec\BitBag\SyliusCmsPlugin\Twig\Extension;
-
-use BitBag\SyliusCmsPlugin\Twig\Extension\RenderContentExtension;
-use PhpSpec\ObjectBehavior;
-use Twig\Extension\AbstractExtension;
-use Twig\TwigFunction;
-
-final class RenderContentExtensionSpec extends ObjectBehavior
-{
-    public function it_is_initializable(): void
-    {
-        $this->shouldHaveType(RenderContentExtension::class);
-    }
-
-    public function it_extends_abstract_extension(): void
-    {
-        $this->shouldHaveType(AbstractExtension::class);
-    }
-
-    public function it_returns_functions(): void
-    {
-        $functions = $this->getFunctions();
-
-        $functions->shouldHaveCount(1);
-
-        foreach ($functions as $function) {
-            $function->shouldHaveType(TwigFunction::class);
-        }
-    }
-}
diff --git a/spec/Twig/Extension/RenderMediaExtensionSpec.php b/spec/Twig/Extension/RenderMediaExtensionSpec.php
deleted file mode 100755
index 7fd5959cd..000000000
--- a/spec/Twig/Extension/RenderMediaExtensionSpec.php
+++ /dev/null
@@ -1,47 +0,0 @@
-<?php
-
-/*
- * This file was created by developers working at BitBag
- * Do you need more information about us and what we do? Visit our https://bitbag.io website!
- * We are hiring developers from all over the world. Join us and start your new, exciting adventure and become part of us: https://bitbag.io/career
-*/
-
-declare(strict_types=1);
-
-namespace spec\BitBag\SyliusCmsPlugin\Twig\Extension;
-
-use BitBag\SyliusCmsPlugin\Twig\Extension\RenderMediaExtension;
-use BitBag\SyliusCmsPlugin\Twig\Runtime\RenderMediaRuntimeInterface;
-use PhpSpec\ObjectBehavior;
-use Twig\Extension\AbstractExtension;
-use Twig\TwigFunction;
-
-final class RenderMediaExtensionSpec extends ObjectBehavior
-{
-    public function let(
-        RenderMediaRuntimeInterface $mediaRuntime
-    ) {
-        $this->beConstructedWith($mediaRuntime);
-    }
-
-    public function it_is_initializable(): void
-    {
-        $this->shouldHaveType(RenderMediaExtension::class);
-    }
-
-    public function it_extends_abstract_extension(): void
-    {
-        $this->shouldHaveType(AbstractExtension::class);
-    }
-
-    public function it_returns_functions(): void
-    {
-        $functions = $this->getFunctions();
-
-        $functions->shouldHaveCount(1);
-
-        foreach ($functions as $function) {
-            $function->shouldHaveType(TwigFunction::class);
-        }
-    }
-}
diff --git a/spec/Twig/Extension/RenderPageLinkExtensionSpec.php b/spec/Twig/Extension/RenderPageLinkExtensionSpec.php
deleted file mode 100644
index 3ef518c6a..000000000
--- a/spec/Twig/Extension/RenderPageLinkExtensionSpec.php
+++ /dev/null
@@ -1,42 +0,0 @@
-<?php
-
-/*
- * This file has been created by developers from BitBag.
- * Feel free to contact us once you face any issues or want to start
- * another great project.
- * You can find more information about us on https://bitbag.io and write us
- * an email on mikolaj.krol@bitbag.pl.
- */
-
-declare(strict_types=1);
-
-namespace spec\BitBag\SyliusCmsPlugin\Twig\Extension;
-
-use BitBag\SyliusCmsPlugin\Twig\Extension\RenderPageLinkExtension;
-use PhpSpec\ObjectBehavior;
-use Twig\Extension\AbstractExtension;
-use Twig\TwigFunction;
-
-final class RenderPageLinkExtensionSpec extends ObjectBehavior
-{
-    public function it_is_initializable(): void
-    {
-        $this->shouldHaveType(RenderPageLinkExtension::class);
-    }
-
-    public function it_extends_abstract_extension(): void
-    {
-        $this->shouldHaveType(AbstractExtension::class);
-    }
-
-    public function it_returns_functions(): void
-    {
-        $functions = $this->getFunctions();
-
-        $functions->shouldHaveCount(2);
-
-        foreach ($functions as $function) {
-            $function->shouldHaveType(TwigFunction::class);
-        }
-    }
-}

From 56ccb600a99f0bff22d7debd198505ccbe5d15b8 Mon Sep 17 00:00:00 2001
From: jkindly <kozupa.jakub@gmail.com>
Date: Wed, 24 Jul 2024 09:00:29 +0200
Subject: [PATCH 15/15] OP-441: Remove forgotten function

---
 .../templates/bundles/SyliusShopBundle/Homepage/index.html.twig  | 1 -
 1 file changed, 1 deletion(-)

diff --git a/tests/Application/templates/bundles/SyliusShopBundle/Homepage/index.html.twig b/tests/Application/templates/bundles/SyliusShopBundle/Homepage/index.html.twig
index 83f010ec7..ab008b882 100755
--- a/tests/Application/templates/bundles/SyliusShopBundle/Homepage/index.html.twig
+++ b/tests/Application/templates/bundles/SyliusShopBundle/Homepage/index.html.twig
@@ -1,7 +1,6 @@
 {% extends '@SyliusShop/layout.html.twig' %}
 
 {% block content %}
-    {{ bitbag_cms_render_collection('blog') }}
     <div class="top one">
         <div class="ui center aligned segment stackable grid cms-logo">
             <a href="https://github.com/BitBagCommerce/SyliusCmsPlugin">