vendor/pimcore/pimcore/bundles/CoreBundle/EventListener/Frontend/EditmodeListener.php line 98

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\Bundle\CoreBundle\EventListener\Frontend;
  15. use Pimcore\Bundle\AdminBundle\Security\User\UserLoader;
  16. use Pimcore\Bundle\CoreBundle\EventListener\Traits\PimcoreContextAwareTrait;
  17. use Pimcore\Document\Editable\EditmodeEditableDefinitionCollector;
  18. use Pimcore\Extension\Bundle\PimcoreBundleManager;
  19. use Pimcore\Http\Request\Resolver\DocumentResolver;
  20. use Pimcore\Http\Request\Resolver\EditmodeResolver;
  21. use Pimcore\Http\Request\Resolver\PimcoreContextResolver;
  22. use Pimcore\Model\Document;
  23. use Pimcore\Version;
  24. use Psr\Log\LoggerAwareTrait;
  25. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  26. use Symfony\Component\HttpFoundation\Response;
  27. use Symfony\Component\HttpKernel\Event\RequestEvent;
  28. use Symfony\Component\HttpKernel\Event\ResponseEvent;
  29. use Symfony\Component\HttpKernel\KernelEvents;
  30. use Symfony\Component\Routing\RouterInterface;
  31. /**
  32.  * Modifies responses for editmode
  33.  *
  34.  * @internal
  35.  */
  36. class EditmodeListener implements EventSubscriberInterface
  37. {
  38.     use LoggerAwareTrait;
  39.     use PimcoreContextAwareTrait;
  40.     /**
  41.      * @var array
  42.      */
  43.     protected $contentTypes = [
  44.         'text/html',
  45.     ];
  46.     /**
  47.      * @param EditmodeResolver $editmodeResolver
  48.      * @param DocumentResolver $documentResolver
  49.      * @param UserLoader $userLoader
  50.      * @param PimcoreBundleManager $bundleManager
  51.      * @param RouterInterface $router
  52.      * @param EditmodeEditableDefinitionCollector $editableConfigCollector
  53.      */
  54.     public function __construct(
  55.         protected EditmodeResolver $editmodeResolver,
  56.         protected DocumentResolver $documentResolver,
  57.         protected UserLoader $userLoader,
  58.         protected PimcoreBundleManager $bundleManager,
  59.         protected RouterInterface $router,
  60.         private EditmodeEditableDefinitionCollector $editableConfigCollector
  61.     ) {
  62.     }
  63.     /**
  64.      * {@inheritdoc}
  65.      */
  66.     public static function getSubscribedEvents()
  67.     {
  68.         return [
  69.             KernelEvents::REQUEST => 'onKernelRequest',
  70.             KernelEvents::RESPONSE => 'onKernelResponse',
  71.         ];
  72.     }
  73.     public function onKernelRequest(RequestEvent $event)
  74.     {
  75.         $request $event->getRequest();
  76.         if (!$event->isMainRequest()) {
  77.             return; // only resolve editmode in frontend
  78.         }
  79.         if (!$this->matchesPimcoreContext($requestPimcoreContextResolver::CONTEXT_DEFAULT)) {
  80.             return;
  81.         }
  82.         // trigger this once to make sure it is resolved properly
  83.         // TODO is this needed?
  84.         $this->editmodeResolver->isEditmode($request);
  85.     }
  86.     public function onKernelResponse(ResponseEvent $event)
  87.     {
  88.         $request $event->getRequest();
  89.         $response $event->getResponse();
  90.         if (!$event->isMainRequest()) {
  91.             return; // only master requests inject editmode assets
  92.         }
  93.         if (!$this->matchesPimcoreContext($requestPimcoreContextResolver::CONTEXT_DEFAULT)) {
  94.             return;
  95.         }
  96.         if (!$this->editmodeResolver->isEditmode($request)) {
  97.             return;
  98.         }
  99.         if (!$this->contentTypeMatches($response)) {
  100.             return;
  101.         }
  102.         $document $this->documentResolver->getDocument($request);
  103.         if (!$document) {
  104.             return;
  105.         }
  106.         $this->logger->info('Injecting editmode assets into request {request}', [
  107.             'request' => $request->getPathInfo(),
  108.         ]);
  109.         $this->addEditmodeAssets($document$response);
  110.         // set sameorigin header for editmode responses
  111.         $response->headers->set('X-Frame-Options''SAMEORIGIN'true);
  112.     }
  113.     /**
  114.      * @param Response $response
  115.      *
  116.      * @return bool
  117.      */
  118.     protected function contentTypeMatches(Response $response)
  119.     {
  120.         $contentType $response->headers->get('Content-Type');
  121.         if (!$contentType) {
  122.             return true;
  123.         }
  124.         // check for substring as the content type could define attributes (e.g. charset)
  125.         foreach ($this->contentTypes as $ct) {
  126.             if (false !== strpos($contentType$ct)) {
  127.                 return true;
  128.             }
  129.         }
  130.         return false;
  131.     }
  132.     /**
  133.      * Inject editmode assets into response HTML
  134.      *
  135.      * @param Document $document
  136.      * @param Response $response
  137.      */
  138.     protected function addEditmodeAssets(Document $documentResponse $response)
  139.     {
  140.         if (Document\Service::isValidType($document->getType())) {
  141.             $html $response->getContent();
  142.             if (!$html) {
  143.                 return;
  144.             }
  145.             $user $this->userLoader->getUser();
  146.             $htmlElement preg_match('/<html[^a-zA-Z]?( [^>]+)?>/'$html);
  147.             $headElement preg_match('/<head[^a-zA-Z]?( [^>]+)?>/'$html);
  148.             $bodyElement preg_match('/<body[^a-zA-Z]?( [^>]+)?>/'$html);
  149.             $skipCheck false;
  150.             // if there's no head and no body, create a wrapper including these elements
  151.             // add html headers for snippets in editmode, so there is no problem with javascript
  152.             if (!$headElement && !$bodyElement && !$htmlElement) {
  153.                 $html "<!DOCTYPE html>\n<html>\n<head></head><body>" $html '</body></html>';
  154.                 $skipCheck true;
  155.             }
  156.             if ($skipCheck || ($headElement && $bodyElement && $htmlElement)) {
  157.                 $startupJavascript '/bundles/pimcoreadmin/js/pimcore/document/edit/startup.js';
  158.                 $headHtml $this->buildHeadHtml($document$user->getLanguage());
  159.                 $bodyHtml "\n\n" $this->editableConfigCollector->getHtml() . "\n\n";
  160.                 $bodyHtml .= "\n\n" '<script src="' $startupJavascript '?_dc=' Version::getRevision() . '"></script>' "\n\n";
  161.                 $html $this->insertBefore('</head>'$html$headHtml);
  162.                 $html $this->insertBefore('</body>'$html$bodyHtml);
  163.                 $response->setContent($html);
  164.             } else {
  165.                 $response->setContent('<div style="font-size:30px; font-family: Arial; font-weight:bold; color:red; text-align: center; margin: 40px 0">You have to define a &lt;html&gt;, &lt;head&gt;, &lt;body&gt;<br />HTML-tag in your view/layout markup!</div>');
  166.             }
  167.         }
  168.     }
  169.     private function insertBefore(string $searchstring $codestring $insert): string
  170.     {
  171.         $endPosition strripos($code$search);
  172.         if (false !== $endPosition) {
  173.             $code substr_replace($code$insert "\n\n" $search$endPosition7);
  174.         }
  175.         return $code;
  176.     }
  177.     /**
  178.      * @param Document $document
  179.      * @param string $language
  180.      *
  181.      * @return string
  182.      */
  183.     protected function buildHeadHtml(Document $document$language)
  184.     {
  185.         $libraries $this->getEditmodeLibraries();
  186.         $scripts $this->getEditmodeScripts();
  187.         $stylesheets $this->getEditmodeStylesheets();
  188.         $headHtml "\n\n\n<!-- pimcore editmode -->\n";
  189.         $headHtml .= '<meta name="google" value="notranslate">';
  190.         $headHtml .= "\n\n";
  191.         // include stylesheets
  192.         foreach ($stylesheets as $stylesheet) {
  193.             $headHtml .= '<link rel="stylesheet" type="text/css" href="' $stylesheet '?_dc=' Version::getRevision() . '" />';
  194.             $headHtml .= "\n";
  195.         }
  196.         $headHtml .= "\n\n";
  197.         // include script libraries
  198.         foreach ($libraries as $script) {
  199.             $headHtml .= '<script src="' $script '?_dc=' Version::getRevision() . '"></script>';
  200.             $headHtml .= "\n";
  201.         }
  202.         // combine the pimcore scripts in non-devmode
  203.         if (\Pimcore::disableMinifyJs()) {
  204.             foreach ($scripts as $script) {
  205.                 $headHtml .= '<script src="' $script '?_dc=' Version::getRevision() . '"></script>';
  206.                 $headHtml .= "\n";
  207.             }
  208.         } else {
  209.             $scriptContents '';
  210.             foreach ($scripts as $scriptUrl) {
  211.                 $scriptContents .= file_get_contents(PIMCORE_WEB_ROOT $scriptUrl) . "\n\n\n";
  212.             }
  213.             $headHtml .= '<script src="' $this->router->generate('pimcore_admin_misc_scriptproxy'\Pimcore\Tool\Admin::getMinimizedScriptPath($scriptContents)) . '"></script>' "\n";
  214.         }
  215.         $path $this->router->generate('pimcore_admin_misc_jsontranslationssystem', [
  216.             'language' => $language,
  217.             '_dc' => Version::getRevision(),
  218.         ]);
  219.         $headHtml .= '<script src="'.$path.'"></script>' "\n";
  220.         $headHtml .= '<script src="' $this->router->generate('fos_js_routing_js', ['callback' => 'fos.Router.setData']) . '"></script>' "\n";
  221.         $headHtml .= "\n\n";
  222.         // set var for editable configurations which is filled by Document\Tag::admin()
  223.         $headHtml .= '<script>
  224.             var editableDefinitions = [];
  225.             var pimcore_document_id = ' $document->getId() . ';
  226.         </script>';
  227.         $headHtml .= "\n\n<!-- /pimcore editmode -->\n\n\n";
  228.         return $headHtml;
  229.     }
  230.     /**
  231.      * @return array
  232.      */
  233.     protected function getEditmodeLibraries()
  234.     {
  235.         $disableMinifyJs \Pimcore::disableMinifyJs();
  236.         return [
  237.             '/bundles/pimcoreadmin/js/pimcore/common.js',
  238.             '/bundles/pimcoreadmin/js/lib/class.js',
  239.             '/bundles/pimcoreadmin/extjs/js/ext-all' . ($disableMinifyJs '-debug' '') . '.js',
  240.             '/bundles/pimcoreadmin/js/lib/ckeditor/ckeditor.js',
  241.         ];
  242.     }
  243.     /**
  244.      * @return array
  245.      */
  246.     protected function getEditmodeScripts()
  247.     {
  248.         return array_merge(
  249.             [
  250.                 '/bundles/fosjsrouting/js/router.js',
  251.                 '/bundles/pimcoreadmin/js/pimcore/functions.js',
  252.                 '/bundles/pimcoreadmin/js/pimcore/overrides.js',
  253.                 '/bundles/pimcoreadmin/js/pimcore/tool/milestoneslider.js',
  254.                 '/bundles/pimcoreadmin/js/pimcore/element/tag/imagehotspotmarkereditor.js',
  255.                 '/bundles/pimcoreadmin/js/pimcore/element/tag/imagecropper.js',
  256.                 '/bundles/pimcoreadmin/js/pimcore/document/edit/helper.js',
  257.                 '/bundles/pimcoreadmin/js/pimcore/elementservice.js',
  258.                 '/bundles/pimcoreadmin/js/pimcore/document/edit/dnd.js',
  259.                 '/bundles/pimcoreadmin/js/pimcore/document/editable.js',
  260.                 '/bundles/pimcoreadmin/js/pimcore/document/editables/block.js',
  261.                 '/bundles/pimcoreadmin/js/pimcore/document/editables/scheduledblock.js',
  262.                 '/bundles/pimcoreadmin/js/pimcore/document/editables/date.js',
  263.                 '/bundles/pimcoreadmin/js/pimcore/document/editables/relation.js',
  264.                 '/bundles/pimcoreadmin/js/pimcore/document/editables/relations.js',
  265.                 '/bundles/pimcoreadmin/js/pimcore/document/editables/checkbox.js',
  266.                 '/bundles/pimcoreadmin/js/pimcore/document/editables/image.js',
  267.                 '/bundles/pimcoreadmin/js/pimcore/document/editables/input.js',
  268.                 '/bundles/pimcoreadmin/js/pimcore/document/editables/link.js',
  269.                 '/bundles/pimcoreadmin/js/pimcore/document/editables/select.js',
  270.                 '/bundles/pimcoreadmin/js/pimcore/document/editables/snippet.js',
  271.                 '/bundles/pimcoreadmin/js/pimcore/document/editables/textarea.js',
  272.                 '/bundles/pimcoreadmin/js/pimcore/document/editables/numeric.js',
  273.                 '/bundles/pimcoreadmin/js/pimcore/document/editables/wysiwyg.js',
  274.                 '/bundles/pimcoreadmin/js/pimcore/document/editables/renderlet.js',
  275.                 '/bundles/pimcoreadmin/js/pimcore/document/editables/table.js',
  276.                 '/bundles/pimcoreadmin/js/pimcore/document/editables/video.js',
  277.                 '/bundles/pimcoreadmin/js/pimcore/document/editables/multiselect.js',
  278.                 '/bundles/pimcoreadmin/js/pimcore/document/editables/area_abstract.js',
  279.                 '/bundles/pimcoreadmin/js/pimcore/document/editables/areablock.js',
  280.                 '/bundles/pimcoreadmin/js/pimcore/document/editables/area.js',
  281.                 '/bundles/pimcoreadmin/js/pimcore/document/editables/pdf.js',
  282.                 '/bundles/pimcoreadmin/js/pimcore/document/editables/embed.js',
  283.                 '/bundles/pimcoreadmin/js/pimcore/document/editables/manager.js',
  284.                 '/bundles/pimcoreadmin/js/pimcore/document/edit/helper.js',
  285.             ],
  286.             $this->bundleManager->getEditmodeJsPaths()
  287.         );
  288.     }
  289.     /**
  290.      * @return array
  291.      */
  292.     protected function getEditmodeStylesheets()
  293.     {
  294.         return array_merge(
  295.             [
  296.                 '/bundles/pimcoreadmin/css/icons.css',
  297.                 '/bundles/pimcoreadmin/extjs/css/PimcoreApp-all_1.css',
  298.                 '/bundles/pimcoreadmin/extjs/css/PimcoreApp-all_2.css',
  299.                 '/bundles/pimcoreadmin/css/editmode.css?_dc=' time(),
  300.             ],
  301.             $this->bundleManager->getEditmodeCssPaths()
  302.         );
  303.     }
  304. }