diff --git a/src/Element/ComponentElementAlter.php b/src/Element/ComponentElementAlter.php index f4e944e77e7b94a4c5de7e61863c4e3c0dc98752..7edf611c5107a7845ead0f6bab59ccfd5cdadee7 100644 --- a/src/Element/ComponentElementAlter.php +++ b/src/Element/ComponentElementAlter.php @@ -60,6 +60,7 @@ class ComponentElementAlter implements TrustedCallbackInterface { continue; } $element["#slots"][$slot_id] = SlotPropType::normalize($slot); + $element["#slots"][$slot_id] = "Yes, render eleemnt is executed"; } return $element; } diff --git a/src/Template/ComponentNodeVisitor.php b/src/Template/ComponentNodeVisitor.php index 6da6ff0e1fb1b73bf360a953b7f265ecd8be4432..f4170edd830b4403e87be9375137f7e2b3360ae6 100755 --- a/src/Template/ComponentNodeVisitor.php +++ b/src/Template/ComponentNodeVisitor.php @@ -22,11 +22,6 @@ use Twig\TwigFunction; */ class ComponentNodeVisitor implements NodeVisitorInterface { - /** - * Node name: expr. - */ - const NODE_NAME_EXPR = 'expr'; - /** * The component plugin manager. */ diff --git a/src/Template/IncludeNodeVisitor.php b/src/Template/IncludeNodeVisitor.php new file mode 100755 index 0000000000000000000000000000000000000000..a003297fab182ff4bdd81513b6018094983136b5 --- /dev/null +++ b/src/Template/IncludeNodeVisitor.php @@ -0,0 +1,90 @@ +<?php + +namespace Drupal\ui_patterns\Template; + +use Drupal\Core\Template\ComponentNodeVisitor as CoreComponentNodeVisitor; +use Drupal\Core\Theme\ComponentPluginManager; +use Twig\Environment; +use Twig\Node\EmbedNode; +use Twig\Node\Expression\FunctionExpression; +use Twig\Node\IncludeNode; +use Twig\Node\Node; +use Twig\NodeVisitor\NodeVisitorInterface; +use Twig\TwigFunction; + +/** + * Provides a IncludeNodeVisitor to change the generated parse-tree. + * + * @internal + */ +class IncludeNodeVisitor implements NodeVisitorInterface { + + /** + * The component plugin manager. + */ + protected ComponentPluginManager $componentManager; + + /** + * Constructs a new ComponentNodeVisitor object. + * + * @param \Drupal\Core\Theme\ComponentPluginManager $component_plugin_manager + * The component plugin manager. + */ + public function __construct(ComponentPluginManager $component_plugin_manager) { + $this->componentManager = $component_plugin_manager; + } + + /** + * {@inheritdoc} + */ + public function enterNode(Node $node, Environment $env): Node { + return $node; + } + + /** + * {@inheritdoc} + */ + public function leaveNode(Node $node, Environment $env): ?Node { + if (!$node instanceof IncludeNode) { + return $node; + } + if ($node instanceof EmbedNode) { + // @todo Embed is a different beast. + return $node; + } + $template = $node->getNode("expr")->getAttribute("value"); + if (!preg_match('/^[a-z]([a-zA-Z0-9_-]*[a-zA-Z0-9])*:[a-z]([a-zA-Z0-9_-]*[a-zA-Z0-9])*$/', $template)) { + return $node; + } + $line = $node->getTemplateLine(); + $arguments = new Node([ + $node->getNode("expr"), + $node->getNode('variables'), + ]); + $function = new FunctionExpression( + new TwigFunction( + "include", + ["Twig\Extension\CoreExtension", "include"], + ), + $arguments, + $line, + ); + return $function; + } + + /** + * {@inheritdoc} + */ + public function getPriority(): int { + $priority = &drupal_static(__METHOD__); + if (!isset($priority)) { + $original_node_visitor = new CoreComponentNodeVisitor($this->componentManager); + // Ensure that this component node visitor's priority is higher than + // core's node visitor class for components, because this class has to run + // core's class. + $priority = $original_node_visitor->getPriority() + 1; + } + return is_numeric($priority) ? (int) $priority : 0; + } + +} diff --git a/src/Template/TwigExtension.php b/src/Template/TwigExtension.php index d193e8ab09c636b7e2664a4e18a4764849023ffd..dc45f8e2499a90754b17865ffa80e70a523b1da1 100644 --- a/src/Template/TwigExtension.php +++ b/src/Template/TwigExtension.php @@ -5,7 +5,9 @@ declare(strict_types=1); namespace Drupal\ui_patterns\Template; use Drupal\ui_patterns\ComponentPluginManager; +use Twig\Environment; use Twig\Extension\AbstractExtension; +use Twig\Extension\CoreExtension; use Twig\TwigFilter; use Twig\TwigFunction; @@ -41,6 +43,7 @@ class TwigExtension extends AbstractExtension { public function getNodeVisitors(): array { return [ new ComponentNodeVisitor($this->componentManager), + new IncludeNodeVisitor($this->componentManager), ]; } @@ -49,6 +52,11 @@ class TwigExtension extends AbstractExtension { */ public function getFunctions() { return [ + new TwigFunction('include', [$this, 'include'], [ + 'needs_environment' => TRUE, + 'needs_context' => TRUE, + 'is_safe' => ['all'], + ]), new TwigFunction('component', [$this, 'renderComponent']), new TwigFunction('_ui_patterns_preprocess_props', [$this, 'preprocessProps'], ['needs_context' => TRUE]), ]; @@ -64,6 +72,40 @@ class TwigExtension extends AbstractExtension { ]; } + /** + * Renders a template. + * + * A hack of Twig\Extension\CoreExtension::include(). + * If the template filepath is a component ID, call the renderer service + * instead of the normal include function. + */ + public function include(Environment $env, $context, $template, $variables = [], $withContext = TRUE, $ignoreMissing = FALSE, $sandboxed = FALSE): array|string { + if (!preg_match('/^[a-z]([a-zA-Z0-9_-]*[a-zA-Z0-9])*:[a-z]([a-zA-Z0-9_-]*[a-zA-Z0-9])*$/', $template)) { + return CoreExtension::include($env, $context, $template, $variables, $withContext, $ignoreMissing, $sandboxed); + } + if ($withContext) { + $variables = array_merge($context, $variables); + } + $component = $this->componentManager->find($template); + $definition = $component->getPluginDefinition(); + $slots = []; + $props = []; + foreach ($variables as $variable => $value) { + if (isset($definition["slots"][$variable])) { + $slots[$variable] = $value; + } + elseif (isset($definition["props"]["properties"][$variable])) { + $props[$variable] = $value; + } + } + return [ + "#type" => "component", + "#component" => $template, + "#slots" => $slots, + "#props" => $props, + ]; + } + /** * Render given component. *