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

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