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

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