diff --git a/config/packages/sylius_twig_hooks.yaml b/config/packages/sylius_twig_hooks.yaml index 1a6c8fc9..5481c592 100644 --- a/config/packages/sylius_twig_hooks.yaml +++ b/config/packages/sylius_twig_hooks.yaml @@ -17,6 +17,12 @@ sylius_twig_hooks: description: template: 'book/index/content/header/description.html.twig' + 'sylius_admin.book.show.content.header.title_block': + title: + template: '@SyliusBootstrapAdminUi/shared/crud/common/content/header/title_block/title.html.twig' + configuration: + title: '@=_context.book.getTitle()' + 'sylius_admin.talk.create.content': form: component: 'App\Twig\Component\TalkFormComponent' diff --git a/src/TwigHooks/config/services.php b/src/TwigHooks/config/services.php index 41b53e71..33953965 100644 --- a/src/TwigHooks/config/services.php +++ b/src/TwigHooks/config/services.php @@ -22,6 +22,9 @@ use Sylius\TwigHooks\Provider\ComponentPropsProvider; use Sylius\TwigHooks\Provider\DefaultConfigurationProvider; use Sylius\TwigHooks\Provider\DefaultContextProvider; +use Sylius\TwigHooks\Provider\PropsProviderInterface; +use Sylius\TwigHooks\Provider\TemplateConfigurationProvider; +use Sylius\TwigHooks\Provider\TemplateConfigurationProviderInterface; use Sylius\TwigHooks\Registry\HookablesRegistry; use Sylius\TwigHooks\Twig\HooksExtension; use Sylius\TwigHooks\Twig\Runtime\HooksRuntime; @@ -39,9 +42,17 @@ inline_service(ExpressionLanguage::class), ]) ; + $services->alias(PropsProviderInterface::class, 'sylius_twig_hooks.provider.component_props'); $services->set('sylius_twig_hooks.provider.default_configuration', DefaultConfigurationProvider::class); + $services->set('sylius_twig_hooks.provider.template_configuration', TemplateConfigurationProvider::class) + ->args([ + inline_service(ExpressionLanguage::class), + ]) + ; + $services->alias(TemplateConfigurationProviderInterface::class, 'sylius_twig_hooks.provider.template_configuration'); + $services->set('sylius_twig_hooks.registry.hookables', HookablesRegistry::class) ->args([ tagged_iterator('sylius_twig_hooks.hookable'), diff --git a/src/TwigHooks/config/services/hookable_renderer.php b/src/TwigHooks/config/services/hookable_renderer.php index 3b61d147..28ba97a6 100644 --- a/src/TwigHooks/config/services/hookable_renderer.php +++ b/src/TwigHooks/config/services/hookable_renderer.php @@ -40,6 +40,7 @@ $services->set('sylius_twig_hooks.renderer.hookable.template', HookableTemplateRenderer::class) ->args([ service('twig'), + service('sylius_twig_hooks.provider.template_configuration'), ]) ->tag('sylius_twig_hooks.hookable_renderer') ; diff --git a/src/TwigHooks/src/Hookable/Metadata/HookableMetadata.php b/src/TwigHooks/src/Hookable/Metadata/HookableMetadata.php index 6a2ab5f7..8ab3d512 100644 --- a/src/TwigHooks/src/Hookable/Metadata/HookableMetadata.php +++ b/src/TwigHooks/src/Hookable/Metadata/HookableMetadata.php @@ -39,4 +39,9 @@ public function hasPrefixes(): bool { return count($this->prefixes) > 0; } + + public function withConfiguration(ScalarDataBagInterface $configuration): self + { + return new self($this->renderedBy, $this->context, $configuration, $this->prefixes); + } } diff --git a/src/TwigHooks/src/Hookable/Renderer/HookableTemplateRenderer.php b/src/TwigHooks/src/Hookable/Renderer/HookableTemplateRenderer.php index adf8f6ae..a55e141c 100644 --- a/src/TwigHooks/src/Hookable/Renderer/HookableTemplateRenderer.php +++ b/src/TwigHooks/src/Hookable/Renderer/HookableTemplateRenderer.php @@ -13,10 +13,12 @@ namespace Sylius\TwigHooks\Hookable\Renderer; +use Sylius\TwigHooks\Bag\ScalarDataBag; use Sylius\TwigHooks\Hookable\AbstractHookable; use Sylius\TwigHooks\Hookable\HookableTemplate; use Sylius\TwigHooks\Hookable\Metadata\HookableMetadata; use Sylius\TwigHooks\Hookable\Renderer\Exception\HookRenderException; +use Sylius\TwigHooks\Provider\TemplateConfigurationProviderInterface; use Sylius\TwigHooks\Twig\Runtime\HooksRuntime; use Twig\Environment as Twig; @@ -24,6 +26,7 @@ final class HookableTemplateRenderer implements SupportableHookableRendererInter { public function __construct( private readonly Twig $twig, + private readonly TemplateConfigurationProviderInterface $configurationProvider, ) { } @@ -39,6 +42,9 @@ public function render(AbstractHookable $hookable, HookableMetadata $metadata): } try { + $configuration = $this->configurationProvider->provide($hookable, $metadata); + $metadata = $metadata->withConfiguration(new ScalarDataBag($configuration)); + return $this->twig->render($hookable->template, [ HooksRuntime::HOOKABLE_METADATA => $metadata, ]); diff --git a/src/TwigHooks/src/Provider/TemplateConfigurationProvider.php b/src/TwigHooks/src/Provider/TemplateConfigurationProvider.php new file mode 100644 index 00000000..a1c0b469 --- /dev/null +++ b/src/TwigHooks/src/Provider/TemplateConfigurationProvider.php @@ -0,0 +1,72 @@ + $metadata->context, + ]; + + return $this->mapArrayRecursively(function (mixed $value) use ($values, $hookable): mixed { + if (is_string($value) && str_starts_with($value, '@=')) { + try { + return $this->expressionLanguage->evaluate(substr($value, 2), $values); + } catch (\Throwable $e) { + throw new InvalidExpressionException( + sprintf( + 'Failed to evaluate the "%s" expression while rendering the "%s" hookable in the "%s" hook. Error: %s".', + $value, + $hookable->name, + $hookable->hookName, + $e->getMessage(), + ), + previous: $e, + ); + } + } + + return $value; + }, $hookable->configuration); + } + + /** + * @param array $array + * + * @return array + */ + private function mapArrayRecursively(callable $callback, array $array): array + { + $result = []; + foreach ($array as $key => $value) { + $result[$key] = is_array($value) + ? $this->mapArrayRecursively($callback, $value) + : $callback($value); + } + + return $result; + } +} diff --git a/src/TwigHooks/src/Provider/TemplateConfigurationProviderInterface.php b/src/TwigHooks/src/Provider/TemplateConfigurationProviderInterface.php new file mode 100644 index 00000000..4432ab4a --- /dev/null +++ b/src/TwigHooks/src/Provider/TemplateConfigurationProviderInterface.php @@ -0,0 +1,28 @@ + + */ + public function provide(HookableTemplate $hookable, HookableMetadata $metadata): array; +} diff --git a/src/TwigHooks/tests/Unit/Hookable/Renderer/HookableTemplateRendererTest.php b/src/TwigHooks/tests/Unit/Hookable/Renderer/HookableTemplateRendererTest.php index a1f56d50..226d3fbf 100644 --- a/src/TwigHooks/tests/Unit/Hookable/Renderer/HookableTemplateRendererTest.php +++ b/src/TwigHooks/tests/Unit/Hookable/Renderer/HookableTemplateRendererTest.php @@ -18,7 +18,10 @@ use Sylius\TwigHooks\Hookable\Metadata\HookableMetadata; use Sylius\TwigHooks\Hookable\Renderer\Exception\HookRenderException; use Sylius\TwigHooks\Hookable\Renderer\HookableTemplateRenderer; +use Sylius\TwigHooks\Provider\TemplateConfigurationProvider; +use Symfony\Component\ExpressionLanguage\ExpressionLanguage; use Tests\Sylius\TwigHooks\Utils\MotherObject\HookableComponentMotherObject; +use Tests\Sylius\TwigHooks\Utils\MotherObject\HookableMetadataMotherObject; use Tests\Sylius\TwigHooks\Utils\MotherObject\HookableTemplateMotherObject; use Twig\Environment as Twig; use Twig\Error\Error; @@ -55,7 +58,7 @@ public function testItThrowsAnExceptionWhenTryingToRenderUnsupportedHookable(): public function testItRendersHookableTemplate(): void { - $metadata = $this->createMock(HookableMetadata::class); + $metadata = HookableMetadataMotherObject::some(); $this->twig->expects($this->once())->method('render')->with('some-template', [ 'hookable_metadata' => $metadata, @@ -69,7 +72,7 @@ public function testItRendersHookableTemplate(): void public function testItThrowsAnExceptionWhenTwigThrowsAnError(): void { - $metadata = $this->createMock(HookableMetadata::class); + $metadata = HookableMetadataMotherObject::some(); $this->twig->expects($this->once())->method('render')->with('some-template', [ 'hookable_metadata' => $metadata, @@ -78,13 +81,13 @@ public function testItThrowsAnExceptionWhenTwigThrowsAnError(): void $hookable = HookableTemplateMotherObject::withTarget('some-template'); $this->expectException(HookRenderException::class); - $this->expectExceptionMessage('An error occurred during rendering the "some_name" hook in the "some_hook" hookable. Unable to find the template at line 76.'); + $this->expectExceptionMessage('An error occurred during rendering the "some_name" hook in the "some_hook" hookable. Unable to find the template at line 79.'); $this->getTestSubject()->render($hookable, $metadata); } private function getTestSubject(): HookableTemplateRenderer { - return new HookableTemplateRenderer($this->twig); + return new HookableTemplateRenderer($this->twig, new TemplateConfigurationProvider(new ExpressionLanguage())); } } diff --git a/src/TwigHooks/tests/Unit/Provider/TemplatesConfigurationProviderTest.php b/src/TwigHooks/tests/Unit/Provider/TemplatesConfigurationProviderTest.php new file mode 100644 index 00000000..e87467dd --- /dev/null +++ b/src/TwigHooks/tests/Unit/Provider/TemplatesConfigurationProviderTest.php @@ -0,0 +1,63 @@ +createTestSubject(); + + $this->assertSame([], $templateConfigurationProvider->provide($hookable, $metadata)); + } + + public function testItReturnsConfiguration(): void + { + $hookable = HookableTemplateMotherObject::withConfiguration(['message' => 'Hello, World!']); + $metadata = HookableMetadataMotherObject::some(); + + $templateConfigurationProvider = $this->createTestSubject(); + + $this->assertSame(['message' => 'Hello, World!'], $templateConfigurationProvider->provide($hookable, $metadata)); + } + + public function testItEvaluatesExpressions(): void + { + $hookable = HookableTemplateMotherObject::withConfiguration([ + 'username' => '@=_context.username', + ]); + $metadata = HookableMetadataMotherObject::withContext( + ['username' => 'Jacob'], + ); + + $templateConfigurationProvider = $this->createTestSubject(); + + $this->assertSame(['username' => 'Jacob'], $templateConfigurationProvider->provide($hookable, $metadata)); + } + + private function createTestSubject(): TemplateConfigurationProviderInterface + { + return new TemplateConfigurationProvider(new ExpressionLanguage()); + } +} diff --git a/templates/book/show/content/page_body.html.twig b/templates/book/show/content/page_body.html.twig index 3995d2ab..ce6eb7c4 100644 --- a/templates/book/show/content/page_body.html.twig +++ b/templates/book/show/content/page_body.html.twig @@ -3,11 +3,6 @@
-
-
- {{ book.title }} -
-
{{ 'app.ui.author'|trans }}: {{ book.authorName }} @@ -15,5 +10,3 @@
- - diff --git a/tests/Functional/BookTest.php b/tests/Functional/BookTest.php index e76e60da..1e3a990f 100644 --- a/tests/Functional/BookTest.php +++ b/tests/Functional/BookTest.php @@ -46,10 +46,9 @@ public function testShowingBook(): void self::assertResponseIsSuccessful(); // Validate Header - self::assertSelectorTextContains('h1.page-title', 'Show Book'); + self::assertSelectorTextContains('h1.page-title', 'Shinning'); // Validate page body - self::assertSelectorTextContains('[data-test-title]', 'Shinning'); self::assertSelectorTextContains('[data-test-author-name]', 'Stephen King'); } diff --git a/tests/Translations/FrenchTranslatedUiTest.php b/tests/Translations/FrenchTranslatedUiTest.php index 585622ad..e7c52842 100644 --- a/tests/Translations/FrenchTranslatedUiTest.php +++ b/tests/Translations/FrenchTranslatedUiTest.php @@ -48,8 +48,8 @@ public function testShowItem(): void self::assertResponseIsSuccessful(); - // Validate Header - self::assertSelectorTextContains('h1.page-title', 'Afficher Livre'); + // Validate Body + self::assertSelectorTextContains('[data-test-author-name] strong', 'Auteur'); } public function testBrowsingItems(): void