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 @@ +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 @@ +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 @@ +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 @@ +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 @@ +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('Page Name'); + + $this->render($page)->shouldReturn('Page Name'); + } + + 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('Page Name'); + + $this->render($page, 'custom_template.html.twig')->shouldReturn('Page Name'); + } +} 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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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); - } - } -} 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..250cf5709 --- /dev/null +++ b/src/Renderer/Collection/CollectionBlocksRenderer.php @@ -0,0 +1,45 @@ +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 0 < $collection->getBlocks()?->count(); + } +} diff --git a/src/Renderer/Collection/CollectionMediaRenderer.php b/src/Renderer/Collection/CollectionMediaRenderer.php new file mode 100644 index 000000000..ddb91be54 --- /dev/null +++ b/src/Renderer/Collection/CollectionMediaRenderer.php @@ -0,0 +1,46 @@ +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 0 < $collection->getMedia()?->count(); + } +} diff --git a/src/Renderer/Collection/CollectionPagesRenderer.php b/src/Renderer/Collection/CollectionPagesRenderer.php new file mode 100644 index 000000000..2b5578cf6 --- /dev/null +++ b/src/Renderer/Collection/CollectionPagesRenderer.php @@ -0,0 +1,45 @@ +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 0 < $collection->getPages()?->count(); + } +} 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 @@ +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 @@ +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 @@ +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 @@ + { + $(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 @@ + + + + + + + + + + + + + + + + + + + + + + + + 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 @@ + + + + + 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 @@ + + + + + + + + + + + + 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 @@ +
+ {{ content|raw }} +
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 %} -

{{ media.name|raw }}

-{% endif %} +
+ {% if null != media.name %} +

{{ media.name|raw }}

+ {% endif %} -

{{ bitbag_cms_render_content(media) }}

+

{{ bitbag_cms_render_content(media) }}

- - - {{ 'bitbag_sylius_cms_plugin.ui.download'|trans }} - + + + {{ 'bitbag_sylius_cms_plugin.ui.download'|trans }} + +
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 @@ -

{{ media.name|raw }}

+
+

{{ media.name|raw }}

-{{ media.alt }} + {{ media.alt }} -

{{ bitbag_cms_render_content(media) }}

+

{{ bitbag_cms_render_content(media) }}

+
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 @@ + diff --git a/src/Sorter/SorterById.php b/src/Sorter/SorterById.php new file mode 100644 index 000000000..5ecdcd959 --- /dev/null +++ b/src/Sorter/SorterById.php @@ -0,0 +1,27 @@ +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 @@ +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 @@ +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 @@ +