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
  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
  170.             if (null === $icon) {
  171.                 $bundle null;
  172.                 try {
  173.                     $bundle $this->bundleLocator->getBundle($brick);
  174.                     // check if file exists
  175.                     $iconPath sprintf('%s/Resources/public/areas/%s/icon.png'$bundle->getPath(), $brick->getId());
  176.                     if (file_exists($iconPath)) {
  177.                         // build URL to icon
  178.                         $icon $this->webPathResolver->getPath($bundle'areas/' $brick->getId(), 'icon.png');
  179.                     }
  180.                 } catch (\Exception $e) {
  181.                     $icon '';
  182.                 }
  183.             }
  184.             $previewHtml $brick instanceof PreviewAwareInterface
  185.                 $brick->getPreviewHtml()
  186.                 : null;
  187.             if ($this->editmodeResolver->isEditmode()) {
  188.                 $name $this->translator->trans($name);
  189.                 $desc $this->translator->trans($desc);
  190.             }
  191.             $areas[$brick->getId()] = [
  192.                 'name' => $name,
  193.                 'description' => $desc,
  194.                 'type' => $brick->getId(),
  195.                 'icon' => $icon,
  196.                 'previewHtml' => $previewHtml,
  197.                 'limit' => $limit,
  198.                 'needsReload' => $brick->needsReload(),
  199.                 'hasDialogBoxConfiguration' => $hasDialogBoxConfiguration,
  200.             ];
  201.         }
  202.         return $areas;
  203.     }
  204.     /**
  205.      * @param Info $info
  206.      * @param array $templateParams
  207.      *
  208.      * @return string
  209.      */
  210.     public function renderAreaFrontend(Info $info$templateParams = []): string
  211.     {
  212.         $brick $this->brickManager->getBrick($info->getId());
  213.         $request $this->requestHelper->getCurrentRequest();
  214.         $brickInfoRestoreValue $request->attributes->get(self::ATTRIBUTE_AREABRICK_INFO);
  215.         $request->attributes->set(self::ATTRIBUTE_AREABRICK_INFO$info);
  216.         $info->setRequest($request);
  217.         // call action
  218.         $this->handleBrickActionResult($brick->action($info));
  219.         $params $info->getParams();
  220.         $params['brick'] = $info;
  221.         $params['info'] = $info;
  222.         $params['instance'] = $brick;
  223.         // check if view template exists and throw error before open tag is rendered
  224.         $viewTemplate $this->resolveBrickTemplate($brick'view');
  225.         if (!$this->templating->exists($viewTemplate)) {
  226.             $e = new ConfigurationException(sprintf(
  227.                 'The view template "%s" for areabrick %s does not exist',
  228.                 $viewTemplate,
  229.                 $brick->getId()
  230.             ));
  231.             $this->logger->error($e->getMessage());
  232.             throw $e;
  233.         }
  234.         // general parameters
  235.         $editmode $this->editmodeResolver->isEditmode();
  236.         if (!isset($templateParams['isAreaBlock'])) {
  237.             $templateParams['isAreaBlock'] = false;
  238.         }
  239.         // render complete areabrick
  240.         // passing the engine interface is necessary otherwise rendering a
  241.         // php template inside the twig template returns the content of the php file
  242.         // instead of actually parsing the php template
  243.         $html $this->templating->render('@PimcoreCore/Areabrick/wrapper.html.twig'array_merge([
  244.             'brick' => $brick,
  245.             'info' => $info,
  246.             'templating' => $this->templating,
  247.             'editmode' => $editmode,
  248.             'viewTemplate' => $viewTemplate,
  249.             'viewParameters' => $params,
  250.         ], $templateParams));
  251.         if ($brickInfoRestoreValue === null) {
  252.             $request->attributes->remove(self::ATTRIBUTE_AREABRICK_INFO);
  253.         } else {
  254.             $request->attributes->set(self::ATTRIBUTE_AREABRICK_INFO$brickInfoRestoreValue);
  255.         }
  256.         // call post render
  257.         $this->handleBrickActionResult($brick->postRenderAction($info));
  258.         return $html;
  259.     }
  260.     /**
  261.      * @param Response|null $result
  262.      */
  263.     protected function handleBrickActionResult($result)
  264.     {
  265.         // if the action result is a response object, push it onto the
  266.         // response stack. this response will be used by the ResponseStackListener
  267.         // and sent back to the client
  268.         if ($result instanceof Response) {
  269.             $this->responseStack->push($result);
  270.         }
  271.     }
  272.     /**
  273.      * Try to get the brick template from get*Template method. If method returns null and brick implements
  274.      * TemplateAreabrickInterface fall back to auto-resolving the template reference. See interface for examples.
  275.      *
  276.      * @param AreabrickInterface $brick
  277.      * @param string $type
  278.      *
  279.      * @return mixed|null|string
  280.      */
  281.     protected function resolveBrickTemplate(AreabrickInterface $brick$type)
  282.     {
  283.         $cacheKey sprintf('%s.%s'$brick->getId(), $type);
  284.         if (isset($this->brickTemplateCache[$cacheKey])) {
  285.             return $this->brickTemplateCache[$cacheKey];
  286.         }
  287.         $template null;
  288.         if ($type === 'view') {
  289.             $template $brick->getTemplate();
  290.         }
  291.         if (null === $template) {
  292.             if ($brick instanceof TemplateAreabrickInterface) {
  293.                 $template $this->buildBrickTemplateReference($brick$type);
  294.             } else {
  295.                 $e = new ConfigurationException(sprintf(
  296.                     'Brick %s is configured to have a %s template but does not return a template path and does not implement %s',
  297.                     $brick->getId(),
  298.                     $type,
  299.                     TemplateAreabrickInterface::class
  300.                 ));
  301.                 $this->logger->error($e->getMessage());
  302.                 throw $e;
  303.             }
  304.         }
  305.         $this->brickTemplateCache[$cacheKey] = $template;
  306.         return $template;
  307.     }
  308.     /**
  309.      * Return either bundle or global (= app/Resources) template reference
  310.      *
  311.      * @param TemplateAreabrickInterface $brick
  312.      * @param string $type
  313.      *
  314.      * @return string
  315.      */
  316.     protected function buildBrickTemplateReference(TemplateAreabrickInterface $brick$type)
  317.     {
  318.         if ($brick->getTemplateLocation() === TemplateAreabrickInterface::TEMPLATE_LOCATION_BUNDLE) {
  319.             $bundle $this->bundleLocator->getBundle($brick);
  320.             $bundleName $bundle->getName();
  321.             if (str_ends_with($bundleName'Bundle')) {
  322.                 $bundleName substr($bundleName0, -6);
  323.             }
  324.             foreach (['areas''Areas'] as $folderName) {
  325.                 $templateReference sprintf(
  326.                     '@%s/%s/%s/%s.%s',
  327.                     $bundleName,
  328.                     $folderName,
  329.                     $brick->getId(),
  330.                     $type,
  331.                     $brick->getTemplateSuffix()
  332.                 );
  333.                 if ($this->templating->exists($templateReference)) {
  334.                     return $templateReference;
  335.                 }
  336.             }
  337.             // return the last reference, even we know that it doesn't exist -> let care the templating engine
  338.             return $templateReference;
  339.         } else {
  340.             return sprintf(
  341.                 'areas/%s/%s.%s',
  342.                 $brick->getId(),
  343.                 $type,
  344.                 $brick->getTemplateSuffix()
  345.             );
  346.         }
  347.     }
  348.     /**
  349.      * @param string $controller
  350.      * @param array $attributes
  351.      * @param array $query
  352.      *
  353.      * @return string|Response
  354.      */
  355.     public function renderAction($controller, array $attributes = [], array $query = [])
  356.     {
  357.         $document $attributes['document'] ?? null;
  358.         if ($document && $document instanceof PageSnippet) {
  359.             unset($attributes['document']);
  360.             $attributes $this->addDocumentAttributes($document$attributes);
  361.         }
  362.         $uri = new ControllerReference($controller$attributes$query);
  363.         if ($this->requestHelper->hasCurrentRequest()) {
  364.             return $this->httpKernelRuntime->renderFragment($uri$attributes);
  365.         } else {
  366.             // this case could happen when rendering on CLI, e.g. search-reindex ...
  367.             $request $this->requestHelper->createRequestWithContext();
  368.             $this->requestStack->push($request);
  369.             $response $this->fragmentRenderer->render($uri$request$attributes);
  370.             $this->requestStack->pop();
  371.             return $response;
  372.         }
  373.     }
  374.     /**
  375.      * @param PageSnippet $document
  376.      * @param array $attributes
  377.      *
  378.      * @return array
  379.      */
  380.     public function addDocumentAttributes(PageSnippet $document, array $attributes = [])
  381.     {
  382.         // The CMF dynamic router sets the 2 attributes contentDocument and contentTemplate to set
  383.         // a route's document and template. Those attributes are later used by controller listeners to
  384.         // determine what to render. By injecting those attributes into the sub-request we can rely on
  385.         // the same rendering logic as in the routed request.
  386.         $attributes[DynamicRouter::CONTENT_KEY] = $document;
  387.         if ($document->getTemplate()) {
  388.             $attributes[DynamicRouter::CONTENT_TEMPLATE] = $document->getTemplate();
  389.         }
  390.         if ($language $document->getProperty('language')) {
  391.             $attributes['_locale'] = $language;
  392.         }
  393.         return $attributes;
  394.     }
  395. }