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

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