vendor/pimcore/pimcore/lib/Document/Editable/EditableHandler.php line 425

Open in your IDE?
  1. <?php
  2. /**
  3.  * Pimcore
  4.  *
  5.  * This source file is available under two different licenses:
  6.  * - GNU General Public License version 3 (GPLv3)
  7.  * - Pimcore Commercial License (PCL)
  8.  * Full copyright and license information is available in
  9.  * LICENSE.md which is distributed with this source code.
  10.  *
  11.  *  @copyright  Copyright (c) Pimcore GmbH (http://www.pimcore.org)
  12.  *  @license    http://www.pimcore.org/license     GPLv3 and PCL
  13.  */
  14. namespace Pimcore\Document\Editable;
  15. use Pimcore\Extension\Document\Areabrick\AreabrickInterface;
  16. use Pimcore\Extension\Document\Areabrick\AreabrickManagerInterface;
  17. use Pimcore\Extension\Document\Areabrick\EditableDialogBoxInterface;
  18. use Pimcore\Extension\Document\Areabrick\Exception\ConfigurationException;
  19. use Pimcore\Extension\Document\Areabrick\PreviewAwareInterface;
  20. use Pimcore\Extension\Document\Areabrick\TemplateAreabrickInterface;
  21. use Pimcore\Http\Request\Resolver\EditmodeResolver;
  22. use Pimcore\Http\RequestHelper;
  23. use Pimcore\Http\ResponseStack;
  24. use Pimcore\HttpKernel\BundleLocator\BundleLocatorInterface;
  25. use Pimcore\HttpKernel\WebPathResolver;
  26. use Pimcore\Model\Document\Editable;
  27. use Pimcore\Model\Document\Editable\Area\Info;
  28. use Pimcore\Model\Document\PageSnippet;
  29. use Psr\Log\LoggerAwareInterface;
  30. use Psr\Log\LoggerAwareTrait;
  31. use Symfony\Bridge\Twig\Extension\HttpKernelRuntime;
  32. use Symfony\Cmf\Bundle\RoutingBundle\Routing\DynamicRouter;
  33. use Symfony\Component\HttpFoundation\RequestStack;
  34. use Symfony\Component\HttpFoundation\Response;
  35. use Symfony\Component\HttpKernel\Controller\ControllerReference;
  36. use Symfony\Component\HttpKernel\Fragment\FragmentRendererInterface;
  37. use Symfony\Component\Templating\EngineInterface;
  38. use Symfony\Contracts\Translation\TranslatorInterface;
  39. /**
  40.  * @internal
  41.  */
  42. class EditableHandler implements LoggerAwareInterface
  43. {
  44.     use LoggerAwareTrait;
  45.     /**
  46.      * @var AreabrickManagerInterface
  47.      */
  48.     protected $brickManager;
  49.     /**
  50.      * @var EngineInterface
  51.      */
  52.     protected $templating;
  53.     /**
  54.      * @var BundleLocatorInterface
  55.      */
  56.     protected $bundleLocator;
  57.     /**
  58.      * @var WebPathResolver
  59.      */
  60.     protected $webPathResolver;
  61.     /**
  62.      * @var RequestHelper
  63.      */
  64.     protected $requestHelper;
  65.     /**
  66.      * @var TranslatorInterface
  67.      */
  68.     protected $translator;
  69.     /**
  70.      * @var ResponseStack
  71.      */
  72.     protected $responseStack;
  73.     /**
  74.      * @var array<string, string>
  75.      */
  76.     protected $brickTemplateCache = [];
  77.     /**
  78.      * @var EditmodeResolver
  79.      */
  80.     protected $editmodeResolver;
  81.     /**
  82.      * @var HttpKernelRuntime
  83.      */
  84.     protected $httpKernelRuntime;
  85.     /**
  86.      * @var FragmentRendererInterface
  87.      */
  88.     protected $fragmentRenderer;
  89.     /**
  90.      * @var RequestStack
  91.      */
  92.     protected $requestStack;
  93.     public const ATTRIBUTE_AREABRICK_INFO '_pimcore_areabrick_info';
  94.     /**
  95.      * @param AreabrickManagerInterface $brickManager
  96.      * @param EngineInterface $templating
  97.      * @param BundleLocatorInterface $bundleLocator
  98.      * @param WebPathResolver $webPathResolver
  99.      * @param RequestHelper $requestHelper
  100.      * @param TranslatorInterface $translator
  101.      * @param ResponseStack $responseStack
  102.      * @param EditmodeResolver $editmodeResolver
  103.      * @param HttpKernelRuntime $httpKernelRuntime
  104.      * @param FragmentRendererInterface $fragmentRenderer
  105.      * @param RequestStack $requestStack
  106.      */
  107.     public function __construct(
  108.         AreabrickManagerInterface $brickManager,
  109.         EngineInterface $templating,
  110.         BundleLocatorInterface $bundleLocator,
  111.         WebPathResolver $webPathResolver,
  112.         RequestHelper $requestHelper,
  113.         TranslatorInterface $translator,
  114.         ResponseStack $responseStack,
  115.         EditmodeResolver $editmodeResolver,
  116.         HttpKernelRuntime $httpKernelRuntime,
  117.         FragmentRendererInterface $fragmentRenderer,
  118.         RequestStack $requestStack
  119.     ) {
  120.         $this->brickManager $brickManager;
  121.         $this->templating $templating;
  122.         $this->bundleLocator $bundleLocator;
  123.         $this->webPathResolver $webPathResolver;
  124.         $this->requestHelper $requestHelper;
  125.         $this->translator $translator;
  126.         $this->responseStack $responseStack;
  127.         $this->editmodeResolver $editmodeResolver;
  128.         $this->httpKernelRuntime $httpKernelRuntime;
  129.         $this->fragmentRenderer $fragmentRenderer;
  130.         $this->requestStack $requestStack;
  131.     }
  132.     /**
  133.      * @param Editable $editable
  134.      * @param AreabrickInterface|string|bool $brick
  135.      *
  136.      * @return bool
  137.      */
  138.     public function isBrickEnabled(Editable $editable$brick)
  139.     {
  140.         if ($brick instanceof AreabrickInterface) {
  141.             $brick $brick->getId();
  142.         }
  143.         return $this->brickManager->isEnabled($brick);
  144.     }
  145.     /**
  146.      * @param Editable\Areablock $editable
  147.      * @param array $options
  148.      *
  149.      * @return array
  150.      */
  151.     public function getAvailableAreablockAreas(Editable\Areablock $editable, array $options)
  152.     {
  153.         $areas = [];
  154.         foreach ($this->brickManager->getBricks() as $brick) {
  155.             // don't show disabled bricks
  156.             if (!isset($options['dontCheckEnabled']) || !$options['dontCheckEnabled']) {
  157.                 if (!$this->isBrickEnabled($editable$brick)) {
  158.                     continue;
  159.                 }
  160.             }
  161.             if (!(empty($options['allowed']) || in_array($brick->getId(), $options['allowed']))) {
  162.                 continue;
  163.             }
  164.             $name $brick->getName();
  165.             $desc $brick->getDescription();
  166.             $icon $brick->getIcon();
  167.             $limit $options['limits'][$brick->getId()] ?? null;
  168.             $hasDialogBoxConfiguration $brick instanceof EditableDialogBoxInterface;
  169.             // autoresolve icon as <bundleName>/Resources/public/areas/<id>/icon.png or <bundleName>/public/areas/<id>/icon.png
  170.             if (null === $icon) {
  171.                 $bundle null;
  172.                 try {
  173.                     $bundle $this->bundleLocator->getBundle($brick);
  174.                     // check if file exists
  175.                     $publicDir is_dir($bundle->getPath().'/Resources/public') ? $bundle->getPath().'/Resources/public' $bundle->getPath().'/public';
  176.                     $iconPath sprintf('%s/areas/%s/icon.png'$publicDir$brick->getId());
  177.                     if (file_exists($iconPath)) {
  178.                         // build URL to icon
  179.                         $icon $this->webPathResolver->getPath($bundle'areas/' $brick->getId(), 'icon.png');
  180.                     }
  181.                 } catch (\Exception $e) {
  182.                     $icon '';
  183.                 }
  184.             }
  185.             $previewHtml $brick instanceof PreviewAwareInterface
  186.                 $brick->getPreviewHtml()
  187.                 : null;
  188.             if ($this->editmodeResolver->isEditmode()) {
  189.                 $name $this->translator->trans($name);
  190.                 $desc $this->translator->trans($desc);
  191.             }
  192.             $areas[$brick->getId()] = [
  193.                 'name' => $name,
  194.                 'description' => $desc,
  195.                 'type' => $brick->getId(),
  196.                 'icon' => $icon,
  197.                 'previewHtml' => $previewHtml,
  198.                 'limit' => $limit,
  199.                 'needsReload' => $brick->needsReload(),
  200.                 'hasDialogBoxConfiguration' => $hasDialogBoxConfiguration,
  201.             ];
  202.         }
  203.         return $areas;
  204.     }
  205.     /**
  206.      * @param Info $info
  207.      * @param array $templateParams
  208.      *
  209.      * @return string
  210.      */
  211.     public function renderAreaFrontend(Info $info$templateParams = []): string
  212.     {
  213.         $brick $this->brickManager->getBrick($info->getId());
  214.         $request $this->requestHelper->getCurrentRequest();
  215.         $brickInfoRestoreValue $request->attributes->get(self::ATTRIBUTE_AREABRICK_INFO);
  216.         $request->attributes->set(self::ATTRIBUTE_AREABRICK_INFO$info);
  217.         $info->setRequest($request);
  218.         // call action
  219.         $this->handleBrickActionResult($brick->action($info));
  220.         $params $info->getParams();
  221.         $params['brick'] = $info;
  222.         $params['info'] = $info;
  223.         $params['instance'] = $brick;
  224.         // check if view template exists and throw error before open tag is rendered
  225.         $viewTemplate $this->resolveBrickTemplate($brick'view');
  226.         if (!$this->templating->exists($viewTemplate)) {
  227.             $e = new ConfigurationException(sprintf(
  228.                 'The view template "%s" for areabrick %s does not exist',
  229.                 $viewTemplate,
  230.                 $brick->getId()
  231.             ));
  232.             $this->logger->error($e->getMessage());
  233.             throw $e;
  234.         }
  235.         // general parameters
  236.         $editmode $this->editmodeResolver->isEditmode();
  237.         if (!isset($templateParams['isAreaBlock'])) {
  238.             $templateParams['isAreaBlock'] = false;
  239.         }
  240.         // render complete areabrick
  241.         // passing the engine interface is necessary otherwise rendering a
  242.         // php template inside the twig template returns the content of the php file
  243.         // instead of actually parsing the php template
  244.         $html $this->templating->render('@PimcoreCore/Areabrick/wrapper.html.twig'array_merge([
  245.             'brick' => $brick,
  246.             'info' => $info,
  247.             'templating' => $this->templating,
  248.             'editmode' => $editmode,
  249.             'viewTemplate' => $viewTemplate,
  250.             'viewParameters' => $params,
  251.         ], $templateParams));
  252.         if ($brickInfoRestoreValue === null) {
  253.             $request->attributes->remove(self::ATTRIBUTE_AREABRICK_INFO);
  254.         } else {
  255.             $request->attributes->set(self::ATTRIBUTE_AREABRICK_INFO$brickInfoRestoreValue);
  256.         }
  257.         // call post render
  258.         $this->handleBrickActionResult($brick->postRenderAction($info));
  259.         return $html;
  260.     }
  261.     /**
  262.      * @param Response|null $result
  263.      */
  264.     protected function handleBrickActionResult($result)
  265.     {
  266.         // if the action result is a response object, push it onto the
  267.         // response stack. this response will be used by the ResponseStackListener
  268.         // and sent back to the client
  269.         if ($result instanceof Response) {
  270.             $this->responseStack->push($result);
  271.         }
  272.     }
  273.     /**
  274.      * Try to get the brick template from get*Template method. If method returns null and brick implements
  275.      * TemplateAreabrickInterface fall back to auto-resolving the template reference. See interface for examples.
  276.      *
  277.      * @param AreabrickInterface $brick
  278.      * @param string $type
  279.      *
  280.      * @return null|string
  281.      */
  282.     protected function resolveBrickTemplate(AreabrickInterface $brick$type)
  283.     {
  284.         $cacheKey sprintf('%s.%s'$brick->getId(), $type);
  285.         if (isset($this->brickTemplateCache[$cacheKey])) {
  286.             return $this->brickTemplateCache[$cacheKey];
  287.         }
  288.         $template null;
  289.         if ($type === 'view') {
  290.             $template $brick->getTemplate();
  291.         }
  292.         if (null === $template) {
  293.             if ($brick instanceof TemplateAreabrickInterface) {
  294.                 $template $this->buildBrickTemplateReference($brick$type);
  295.             } else {
  296.                 $e = new ConfigurationException(sprintf(
  297.                     'Brick %s is configured to have a %s template but does not return a template path and does not implement %s',
  298.                     $brick->getId(),
  299.                     $type,
  300.                     TemplateAreabrickInterface::class
  301.                 ));
  302.                 $this->logger->error($e->getMessage());
  303.                 throw $e;
  304.             }
  305.         }
  306.         $this->brickTemplateCache[$cacheKey] = $template;
  307.         return $template;
  308.     }
  309.     /**
  310.      * Return either bundle or global (= app/Resources) template reference
  311.      *
  312.      * @param TemplateAreabrickInterface $brick
  313.      * @param string $type
  314.      *
  315.      * @return string
  316.      */
  317.     protected function buildBrickTemplateReference(TemplateAreabrickInterface $brick$type)
  318.     {
  319.         if ($brick->getTemplateLocation() === TemplateAreabrickInterface::TEMPLATE_LOCATION_BUNDLE) {
  320.             $bundle $this->bundleLocator->getBundle($brick);
  321.             $bundleName $bundle->getName();
  322.             if (str_ends_with($bundleName'Bundle')) {
  323.                 $bundleName substr($bundleName0, -6);
  324.             }
  325.             foreach (['areas''Areas'] as $folderName) {
  326.                 $templateReference sprintf(
  327.                     '@%s/%s/%s/%s.%s',
  328.                     $bundleName,
  329.                     $folderName,
  330.                     $brick->getId(),
  331.                     $type,
  332.                     $brick->getTemplateSuffix()
  333.                 );
  334.                 if ($this->templating->exists($templateReference)) {
  335.                     return $templateReference;
  336.                 }
  337.             }
  338.             // return the last reference, even we know that it doesn't exist -> let care the templating engine
  339.             return $templateReference;
  340.         } else {
  341.             return sprintf(
  342.                 'areas/%s/%s.%s',
  343.                 $brick->getId(),
  344.                 $type,
  345.                 $brick->getTemplateSuffix()
  346.             );
  347.         }
  348.     }
  349.     /**
  350.      * @param string $controller
  351.      * @param array $attributes
  352.      * @param array $query
  353.      *
  354.      * @return string|Response
  355.      */
  356.     public function renderAction($controller, array $attributes = [], array $query = [])
  357.     {
  358.         $document $attributes['document'] ?? null;
  359.         if ($document && $document instanceof PageSnippet) {
  360.             unset($attributes['document']);
  361.             $attributes $this->addDocumentAttributes($document$attributes);
  362.         }
  363.         $uri = new ControllerReference($controller$attributes$query);
  364.         if ($this->requestHelper->hasCurrentRequest()) {
  365.             return $this->httpKernelRuntime->renderFragment($uri$attributes);
  366.         } else {
  367.             // this case could happen when rendering on CLI, e.g. search-reindex ...
  368.             $request $this->requestHelper->createRequestWithContext();
  369.             $this->requestStack->push($request);
  370.             $response $this->fragmentRenderer->render($uri$request$attributes);
  371.             $this->requestStack->pop();
  372.             return $response;
  373.         }
  374.     }
  375.     /**
  376.      * @param PageSnippet $document
  377.      * @param array $attributes
  378.      *
  379.      * @return array
  380.      */
  381.     public function addDocumentAttributes(PageSnippet $document, array $attributes = [])
  382.     {
  383.         // The CMF dynamic router sets the 2 attributes contentDocument and contentTemplate to set
  384.         // a route's document and template. Those attributes are later used by controller listeners to
  385.         // determine what to render. By injecting those attributes into the sub-request we can rely on
  386.         // the same rendering logic as in the routed request.
  387.         $attributes[DynamicRouter::CONTENT_KEY] = $document;
  388.         if ($document->getTemplate()) {
  389.             $attributes[DynamicRouter::CONTENT_TEMPLATE] = $document->getTemplate();
  390.         }
  391.         if ($language $document->getProperty('language')) {
  392.             $attributes['_locale'] = $language;
  393.         }
  394.         return $attributes;
  395.     }
  396. }