vendor/netgen/layouts-core/bundles/LayoutsBundle/Templating/Twig/Runtime/RenderingRuntime.php line 198

  1. <?php
  2. declare(strict_types=1);
  3. namespace Netgen\Bundle\LayoutsBundle\Templating\Twig\Runtime;
  4. use Generator;
  5. use Netgen\Layouts\API\Service\BlockService;
  6. use Netgen\Layouts\API\Values\Block\Block;
  7. use Netgen\Layouts\API\Values\Collection\Slot;
  8. use Netgen\Layouts\API\Values\Layout\Layout;
  9. use Netgen\Layouts\API\Values\Layout\Zone;
  10. use Netgen\Layouts\Collection\Result\ManualItem;
  11. use Netgen\Layouts\Collection\Result\Result;
  12. use Netgen\Layouts\Error\ErrorHandlerInterface;
  13. use Netgen\Layouts\Exception\InvalidArgumentException;
  14. use Netgen\Layouts\Item\CmsItemInterface;
  15. use Netgen\Layouts\Locale\LocaleProviderInterface;
  16. use Netgen\Layouts\View\RendererInterface;
  17. use Netgen\Layouts\View\Twig\ContextualizedTwigTemplate;
  18. use Netgen\Layouts\View\View\ZoneView\ZoneReference;
  19. use Netgen\Layouts\View\ViewInterface;
  20. use Symfony\Component\HttpFoundation\Request;
  21. use Symfony\Component\HttpFoundation\RequestStack;
  22. use Throwable;
  23. use Twig\Environment;
  24. use Twig\Template;
  25. use Twig\TemplateWrapper;
  26. use function get_debug_type;
  27. use function is_object;
  28. use function is_scalar;
  29. use function iterator_to_array;
  30. use function method_exists;
  31. use function sprintf;
  32. final class RenderingRuntime
  33. {
  34.     private BlockService $blockService;
  35.     private RendererInterface $renderer;
  36.     private LocaleProviderInterface $localeProvider;
  37.     private RequestStack $requestStack;
  38.     private ErrorHandlerInterface $errorHandler;
  39.     private Environment $simpleTwig;
  40.     public function __construct(
  41.         BlockService $blockService,
  42.         RendererInterface $renderer,
  43.         LocaleProviderInterface $localeProvider,
  44.         RequestStack $requestStack,
  45.         ErrorHandlerInterface $errorHandler,
  46.         Environment $simpleTwig
  47.     ) {
  48.         $this->blockService $blockService;
  49.         $this->renderer $renderer;
  50.         $this->localeProvider $localeProvider;
  51.         $this->requestStack $requestStack;
  52.         $this->errorHandler $errorHandler;
  53.         $this->simpleTwig $simpleTwig;
  54.     }
  55.     /**
  56.      * Renders the provided item.
  57.      *
  58.      * @param array<string, mixed> $context
  59.      * @param array<string, mixed> $parameters
  60.      */
  61.     public function renderItem(array $contextCmsItemInterface $itemstring $viewType, array $parameters = [], ?string $viewContext null): string
  62.     {
  63.         try {
  64.             return $this->renderer->renderValue(
  65.                 $item,
  66.                 $this->getViewContext($context$viewContext),
  67.                 ['view_type' => $viewType] + $parameters,
  68.             );
  69.         } catch (Throwable $t) {
  70.             $message sprintf(
  71.                 'Error rendering an item with value "%s" and value type "%s"',
  72.                 $item->getValue(),
  73.                 $item->getValueType(),
  74.             );
  75.             $this->errorHandler->handleError($t$message);
  76.         }
  77.         return '';
  78.     }
  79.     /**
  80.      * Renders the provided result.
  81.      *
  82.      * @param array<string, mixed> $context
  83.      * @param array<string, mixed> $parameters
  84.      */
  85.     public function renderResult(array $contextResult $result, ?string $overrideViewType null, ?string $fallbackViewType null, array $parameters = [], ?string $viewContext null): string
  86.     {
  87.         $item $result->getItem();
  88.         $slot $result->getSlot();
  89.         try {
  90.             $viewType $fallbackViewType;
  91.             if ($overrideViewType !== null) {
  92.                 $viewType $overrideViewType;
  93.             } elseif ($item instanceof ManualItem && $item->getCollectionItem()->getViewType() !== null) {
  94.                 $viewType $item->getCollectionItem()->getViewType();
  95.             } elseif ($slot instanceof Slot && $slot->getViewType() !== null) {
  96.                 $viewType $slot->getViewType();
  97.             }
  98.             if ($viewType === null) {
  99.                 throw new InvalidArgumentException(
  100.                     'fallbackViewType',
  101.                     'To render a result item, view type needs to be set through slot, or provided via "overrideViewType" or "fallbackViewType" parameters.',
  102.                 );
  103.             }
  104.             return $this->renderItem($context$item$viewType$parameters$viewContext);
  105.         } catch (Throwable $t) {
  106.             $message sprintf(
  107.                 'Error rendering an item with value "%s" and value type "%s"',
  108.                 $item->getValue(),
  109.                 $item->getValueType(),
  110.             );
  111.             $this->errorHandler->handleError($t$message);
  112.         }
  113.         return '';
  114.     }
  115.     /**
  116.      * Renders the provided value.
  117.      *
  118.      * @param array<string, mixed> $context
  119.      * @param mixed $value
  120.      * @param array<string, mixed> $parameters
  121.      */
  122.     public function renderValue(array $context$value, array $parameters = [], ?string $viewContext null): string
  123.     {
  124.         try {
  125.             return $this->renderer->renderValue(
  126.                 $value,
  127.                 $this->getViewContext($context$viewContext),
  128.                 $parameters,
  129.             );
  130.         } catch (Throwable $t) {
  131.             $message sprintf('Error rendering a value of type "%s"'get_debug_type($value));
  132.             $this->errorHandler->handleError($t$message, ['object' => $value]);
  133.         }
  134.         return '';
  135.     }
  136.     /**
  137.      * Displays the provided zone.
  138.      */
  139.     public function displayZone(Layout $layoutstring $zoneIdentifierstring $viewContextContextualizedTwigTemplate $twigTemplate): void
  140.     {
  141.         $locales null;
  142.         $request $this->requestStack->getCurrentRequest();
  143.         if ($request instanceof Request) {
  144.             $locales $this->localeProvider->getRequestLocales($request);
  145.         }
  146.         $zone $layout->getZone($zoneIdentifier);
  147.         $linkedZone $zone->getLinkedZone();
  148.         $blocks $this->blockService->loadZoneBlocks(
  149.             $linkedZone instanceof Zone $linkedZone $zone,
  150.             $locales,
  151.         );
  152.         echo $this->renderValue(
  153.             [],
  154.             new ZoneReference($layout$zoneIdentifier),
  155.             [
  156.                 'blocks' => $blocks,
  157.                 'twig_template' => $twigTemplate,
  158.             ],
  159.             $viewContext,
  160.         );
  161.     }
  162.     /**
  163.      * Renders the provided block.
  164.      *
  165.      * @param array<string, mixed> $context
  166.      * @param array<string, mixed> $parameters
  167.      */
  168.     public function renderBlock(array $contextBlock $block, array $parameters = [], ?string $viewContext null): string
  169.     {
  170.         try {
  171.             return $this->renderer->renderValue(
  172.                 $block,
  173.                 $this->getViewContext($context$viewContext),
  174.                 [
  175.                     'twig_template' => $context['twig_template'] ?? null,
  176.                 ] + $parameters,
  177.             );
  178.         } catch (Throwable $t) {
  179.             $message sprintf('Error rendering a block with UUID "%s"'$block->getId()->toString());
  180.             $this->errorHandler->handleError($t$message);
  181.         }
  182.         return '';
  183.     }
  184.     /**
  185.      * Renders the provided placeholder.
  186.      *
  187.      * @param array<string, mixed> $context
  188.      * @param array<string, mixed> $parameters
  189.      */
  190.     public function renderPlaceholder(array $contextBlock $blockstring $placeholder, array $parameters = [], ?string $viewContext null): string
  191.     {
  192.         try {
  193.             return $this->renderer->renderValue(
  194.                 $block->getPlaceholder($placeholder),
  195.                 $this->getViewContext($context$viewContext),
  196.                 [
  197.                     'block' => $block,
  198.                     'twig_template' => $context['twig_template'] ?? null,
  199.                 ] + $parameters,
  200.             );
  201.         } catch (Throwable $t) {
  202.             $message sprintf(
  203.                 'Error rendering a placeholder "%s" in block with UUID "%s"',
  204.                 $placeholder,
  205.                 $block->getId()->toString(),
  206.             );
  207.             $this->errorHandler->handleError($t$message);
  208.         }
  209.         return '';
  210.     }
  211.     /**
  212.      * Renders the provided template, with a reduced set of variables from the provided
  213.      * parameters list. Variables included are only those which can be safely printed.
  214.      *
  215.      * @param array<string, mixed> $parameters
  216.      */
  217.     public function renderStringTemplate(string $string, array $parameters = []): string
  218.     {
  219.         try {
  220.             $parameters iterator_to_array($this->getTemplateVariables($parameters));
  221.             $template $this->simpleTwig->createTemplate($string);
  222.             return $this->simpleTwig->resolveTemplate($template)->render($parameters);
  223.         } catch (Throwable $t) {
  224.             return '';
  225.         }
  226.     }
  227.     /**
  228.      * Returns the correct view context based on provided Twig context and view context
  229.      * provided through function call.
  230.      *
  231.      * @param array<string, mixed> $context
  232.      */
  233.     private function getViewContext(array $context, ?string $viewContext null): string
  234.     {
  235.         return $viewContext ?? $context['view_context'] ?? ViewInterface::CONTEXT_DEFAULT;
  236.     }
  237.     /**
  238.      * Returns all safely printable variables: scalars and objects with __toString method.
  239.      *
  240.      * If the context has an instance of ContextualizedTwigTemplate, its context is also
  241.      * included in the output. Any variables from the main context will override variables
  242.      * from ContextualizedTwigTemplate objects.
  243.      *
  244.      * @param array<string, mixed> $parameters
  245.      *
  246.      * @return \Generator<string, mixed>
  247.      */
  248.     private function getTemplateVariables(array $parameters): Generator
  249.     {
  250.         foreach ($parameters as $name => $value) {
  251.             if ($value instanceof ContextualizedTwigTemplate) {
  252.                 yield from $this->getTemplateVariables($value->getContext());
  253.             }
  254.         }
  255.         foreach ($parameters as $name => $value) {
  256.             if ($value instanceof Template || $value instanceof TemplateWrapper) {
  257.                 continue;
  258.             }
  259.             if (is_scalar($value) || (is_object($value) && method_exists($value'__toString'))) {
  260.                 yield $name => $value;
  261.             }
  262.         }
  263.     }
  264. }