pimcore/lib/Pimcore/Bundle/CoreBundle/EventListener/Frontend/GoogleTagManagerListener.php line 87

Open in your IDE?
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4.  * Pimcore
  5.  *
  6.  * This source file is available under two different licenses:
  7.  * - GNU General Public License version 3 (GPLv3)
  8.  * - Pimcore Enterprise License (PEL)
  9.  * Full copyright and license information is available in
  10.  * LICENSE.md which is distributed with this source code.
  11.  *
  12.  * @copyright  Copyright (c) Pimcore GmbH (http://www.pimcore.org)
  13.  * @license    http://www.pimcore.org/license     GPLv3 and PEL
  14.  */
  15. namespace Pimcore\Bundle\CoreBundle\EventListener\Frontend;
  16. use Pimcore\Analytics\Code\CodeBlock;
  17. use Pimcore\Analytics\SiteId\SiteIdProvider;
  18. use Pimcore\Bundle\CoreBundle\EventListener\Traits\EnabledTrait;
  19. use Pimcore\Bundle\CoreBundle\EventListener\Traits\PimcoreContextAwareTrait;
  20. use Pimcore\Bundle\CoreBundle\EventListener\Traits\ResponseInjectionTrait;
  21. use Pimcore\Config;
  22. use Pimcore\Event\Analytics\Google\TagManager\CodeEvent;
  23. use Pimcore\Event\Analytics\GoogleTagManagerEvents;
  24. use Pimcore\Http\Request\Resolver\PimcoreContextResolver;
  25. use Pimcore\Tool;
  26. use Symfony\Component\EventDispatcher\EventDispatcherInterface;
  27. use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
  28. use Symfony\Component\Templating\EngineInterface;
  29. class GoogleTagManagerListener
  30. {
  31.     const BLOCK_HEAD_BEFORE_SCRIPT_TAG 'beforeScriptTag';
  32.     const BLOCK_HEAD_AFTER_SCRIPT_TAG 'afterScriptTag';
  33.     const BLOCK_BODY_BEFORE_NOSCRIPT_TAG 'beforeNoscriptTag';
  34.     const BLOCK_BODY_AFTER_NOSCRIPT_TAG 'afterNoscriptTag';
  35.     use EnabledTrait;
  36.     use ResponseInjectionTrait;
  37.     use PimcoreContextAwareTrait;
  38.     /**
  39.      * @var SiteIdProvider
  40.      */
  41.     private $siteIdProvider;
  42.     /**
  43.      * @var EventDispatcherInterface
  44.      */
  45.     private $eventDispatcher;
  46.     /**
  47.      * @var EngineInterface
  48.      */
  49.     private $templatingEngine;
  50.     /**
  51.      * @var array
  52.      */
  53.     private $headBlocks = [
  54.         self::BLOCK_HEAD_BEFORE_SCRIPT_TAG,
  55.         self::BLOCK_HEAD_AFTER_SCRIPT_TAG
  56.     ];
  57.     /**
  58.      * @var array
  59.      */
  60.     private $bodyBlocks = [
  61.         self::BLOCK_BODY_BEFORE_NOSCRIPT_TAG,
  62.         self::BLOCK_BODY_AFTER_NOSCRIPT_TAG
  63.     ];
  64.     public function __construct(
  65.         SiteIdProvider $siteIdProvider,
  66.         EventDispatcherInterface $eventDispatcher,
  67.         EngineInterface $templatingEngine
  68.     ) {
  69.         $this->siteIdProvider   $siteIdProvider;
  70.         $this->eventDispatcher  $eventDispatcher;
  71.         $this->templatingEngine $templatingEngine;
  72.     }
  73.     public function onKernelResponse(FilterResponseEvent $event)
  74.     {
  75.         if (!$this->isEnabled()) {
  76.             return;
  77.         }
  78.         $request $event->getRequest();
  79.         if (!$event->isMasterRequest()) {
  80.             return;
  81.         }
  82.         // only inject tag manager code on non-admin requests
  83.         if (!$this->matchesPimcoreContext($requestPimcoreContextResolver::CONTEXT_DEFAULT)) {
  84.             return;
  85.         }
  86.         if (!Tool::useFrontendOutputFilters()) {
  87.             return;
  88.         }
  89.         // It's standard industry practice to exclude tracking if the request includes the header 'X-Purpose:preview'
  90.         $serverVars $event->getRequest()->server;
  91.         if ($serverVars->get('HTTP_X_PURPOSE') === 'preview') {
  92.             return;
  93.         }
  94.         $siteId  $this->siteIdProvider->getForRequest($event->getRequest());
  95.         $siteKey $siteId->getConfigKey();
  96.         $reportConfig Config::getReportConfig();
  97.         if (!isset($reportConfig->tagmanager->sites->$siteKey->containerId)) {
  98.             return;
  99.         }
  100.         $containerId $reportConfig->tagmanager->sites->$siteKey->containerId;
  101.         if (!$containerId) {
  102.             return;
  103.         }
  104.         $response $event->getResponse();
  105.         if (!$this->isHtmlResponse($response)) {
  106.             return;
  107.         }
  108.         $codeHead $this->generateCode(
  109.             GoogleTagManagerEvents::CODE_HEAD,
  110.             '@PimcoreCore/Google/TagManager/codeHead.html.twig',
  111.             $this->headBlocks,
  112.             [
  113.                 'containerId' => $containerId
  114.             ]
  115.         );
  116.         $codeBody $this->generateCode(
  117.             GoogleTagManagerEvents::CODE_BODY,
  118.             '@PimcoreCore/Google/TagManager/codeBody.html.twig',
  119.             $this->bodyBlocks,
  120.             [
  121.                 'containerId' => $containerId
  122.             ]
  123.         );
  124.         $content $response->getContent();
  125.         if (!empty($codeHead)) {
  126.             // search for the end <head> tag, and insert the google tag manager code before
  127.             // this method is much faster than using simple_html_dom and uses less memory
  128.             $headEndPosition stripos($content'</head>');
  129.             if ($headEndPosition !== false) {
  130.                 $content substr_replace($content$codeHead '</head>'$headEndPosition7);
  131.             }
  132.         }
  133.         if (!empty($codeBody)) {
  134.             // insert code after the opening <body> tag
  135.             $content preg_replace('@<body(>|.*?[^?]>)@'"<body$1\n\n" $codeBody$content);
  136.         }
  137.         $response->setContent($content);
  138.     }
  139.     private function generateCode(string $eventNamestring $template, array $blockNames, array $data): string
  140.     {
  141.         $blocks = [];
  142.         foreach ($blockNames as $blockName) {
  143.             $blocks[$blockName] = new CodeBlock();
  144.         }
  145.         $event = new CodeEvent($data$blocks$template);
  146.         $this->eventDispatcher->dispatch($eventName$event);
  147.         return $this->renderTemplate($event);
  148.     }
  149.     private function renderTemplate(CodeEvent $event): string
  150.     {
  151.         $data           $event->getData();
  152.         $data['blocks'] = $event->getBlocks();
  153.         $code $this->templatingEngine->render(
  154.             $event->getTemplate(),
  155.             $data
  156.         );
  157.         $code trim($code);
  158.         if (!empty($code)) {
  159.             $code "\n" $code "\n";
  160.         }
  161.         return $code;
  162.     }
  163. }