vendor/pimcore/pimcore/lib/Targeting/EventListener/TargetingListener.php line 167

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 Commercial License (PCL)
  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 PCL
  14.  */
  15. namespace Pimcore\Targeting\EventListener;
  16. use Pimcore\Bundle\CoreBundle\EventListener\Traits\EnabledTrait;
  17. use Pimcore\Bundle\CoreBundle\EventListener\Traits\PimcoreContextAwareTrait;
  18. use Pimcore\Bundle\CoreBundle\EventListener\Traits\ResponseInjectionTrait;
  19. use Pimcore\Bundle\CoreBundle\EventListener\Traits\StaticPageContextAwareTrait;
  20. use Pimcore\Debug\Traits\StopwatchTrait;
  21. use Pimcore\Event\Targeting\TargetingEvent;
  22. use Pimcore\Event\TargetingEvents;
  23. use Pimcore\Http\Request\Resolver\PimcoreContextResolver;
  24. use Pimcore\Http\RequestHelper;
  25. use Pimcore\Targeting\ActionHandler\ActionHandlerInterface;
  26. use Pimcore\Targeting\ActionHandler\AssignTargetGroup;
  27. use Pimcore\Targeting\ActionHandler\DelegatingActionHandler;
  28. use Pimcore\Targeting\ActionHandler\ResponseTransformingActionHandlerInterface;
  29. use Pimcore\Targeting\Code\TargetingCodeGenerator;
  30. use Pimcore\Targeting\Model\VisitorInfo;
  31. use Pimcore\Targeting\VisitorInfoResolver;
  32. use Pimcore\Targeting\VisitorInfoStorageInterface;
  33. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  34. use Symfony\Component\HttpFoundation\Response;
  35. use Symfony\Component\HttpKernel\Event\RequestEvent;
  36. use Symfony\Component\HttpKernel\Event\ResponseEvent;
  37. use Symfony\Component\HttpKernel\KernelEvents;
  38. class TargetingListener implements EventSubscriberInterface
  39. {
  40.     use StopwatchTrait;
  41.     use PimcoreContextAwareTrait;
  42.     use EnabledTrait;
  43.     use ResponseInjectionTrait;
  44.     use StaticPageContextAwareTrait;
  45.     /**
  46.      * @var VisitorInfoResolver
  47.      */
  48.     private $visitorInfoResolver;
  49.     /**
  50.      * @var DelegatingActionHandler|ActionHandlerInterface
  51.      */
  52.     private $actionHandler;
  53.     /**
  54.      * @var VisitorInfoStorageInterface
  55.      */
  56.     private $visitorInfoStorage;
  57.     /**
  58.      * @var RequestHelper
  59.      */
  60.     private $requestHelper;
  61.     /**
  62.      * @var TargetingCodeGenerator
  63.      */
  64.     private $codeGenerator;
  65.     public function __construct(
  66.         VisitorInfoResolver $visitorInfoResolver,
  67.         ActionHandlerInterface $actionHandler,
  68.         VisitorInfoStorageInterface $visitorInfoStorage,
  69.         RequestHelper $requestHelper,
  70.         TargetingCodeGenerator $codeGenerator
  71.     ) {
  72.         $this->visitorInfoResolver $visitorInfoResolver;
  73.         $this->actionHandler $actionHandler;
  74.         $this->visitorInfoStorage $visitorInfoStorage;
  75.         $this->requestHelper $requestHelper;
  76.         $this->codeGenerator $codeGenerator;
  77.     }
  78.     /**
  79.      * @return array
  80.      */
  81.     public static function getSubscribedEvents()//: array
  82.     {
  83.         return [
  84.             // needs to run before ElementListener to make sure there's a
  85.             // resolved VisitorInfo when the document is loaded
  86.             KernelEvents::REQUEST => ['onKernelRequest'7],
  87.             KernelEvents::RESPONSE => ['onKernelResponse', -115],
  88.             TargetingEvents::PRE_RESOLVE => 'onPreResolve',
  89.         ];
  90.     }
  91.     public function onKernelRequest(RequestEvent $event)
  92.     {
  93.         if (!$this->enabled) {
  94.             return;
  95.         }
  96.         if ($event->getRequest()->cookies->getBoolean('pimcore_targeting_disabled')) {
  97.             $this->disable();
  98.             return;
  99.         }
  100.         $request $event->getRequest();
  101.         if (!$event->isMainRequest() && !$this->matchesStaticPageContext($request)) {
  102.             return;
  103.         }
  104.         // only apply targeting for GET requests
  105.         // this may revised in later versions
  106.         if ('GET' !== $request->getMethod()) {
  107.             return;
  108.         }
  109.         if (!$this->matchesPimcoreContext($requestPimcoreContextResolver::CONTEXT_DEFAULT)
  110.             && !$this->matchesStaticPageContext($request)) {
  111.             return;
  112.         }
  113.         if ((!$this->requestHelper->isFrontendRequest($request) && !$this->matchesStaticPageContext($request)) || $this->requestHelper->isFrontendRequestByAdmin($request)) {
  114.             return;
  115.         }
  116.         if (!$this->visitorInfoResolver->isTargetingConfigured()) {
  117.             return;
  118.         }
  119.         $this->startStopwatch('Targeting:resolveVisitorInfo''targeting');
  120.         $visitorInfo $this->visitorInfoResolver->resolve($request);
  121.         $this->stopStopwatch('Targeting:resolveVisitorInfo');
  122.         // propagate response (e.g. redirect) to request handling
  123.         if ($visitorInfo->hasResponse()) {
  124.             $event->setResponse($visitorInfo->getResponse());
  125.         }
  126.     }
  127.     public function onPreResolve(TargetingEvent $event)
  128.     {
  129.         $this->startStopwatch('Targeting:loadStoredAssignments''targeting');
  130.         if (method_exists($this->actionHandler'getActionHandler')) {
  131.             /** @var AssignTargetGroup $assignTargetGroupHandler */
  132.             $assignTargetGroupHandler $this->actionHandler->getActionHandler('assign_target_group');
  133.             $assignTargetGroupHandler->loadStoredAssignments($event->getVisitorInfo()); // load previously assigned target groups
  134.         }
  135.         $this->stopStopwatch('Targeting:loadStoredAssignments');
  136.     }
  137.     public function onKernelResponse(ResponseEvent $event)
  138.     {
  139.         if (!$this->enabled) {
  140.             return;
  141.         }
  142.         if (!$this->visitorInfoStorage->hasVisitorInfo()) {
  143.             return;
  144.         }
  145.         $visitorInfo $this->visitorInfoStorage->getVisitorInfo();
  146.         $response $event->getResponse();
  147.         if ($event->isMainRequest() || $this->matchesStaticPageContext($event->getRequest())) {
  148.             $this->startStopwatch('Targeting:responseActions''targeting');
  149.             // handle recorded actions on response
  150.             $this->handleResponseActions($visitorInfo$response);
  151.             $this->stopStopwatch('Targeting:responseActions');
  152.             if ($this->visitorInfoResolver->isTargetingConfigured()) {
  153.                 $this->injectTargetingCode($response$visitorInfo);
  154.             }
  155.         }
  156.         // check if the visitor info influences the response
  157.         if ($this->appliesPersonalization($visitorInfo)) {
  158.             // set response to private as soon as we apply personalization
  159.             $response->setPrivate();
  160.         }
  161.     }
  162.     private function injectTargetingCode(Response $responseVisitorInfo $visitorInfo)
  163.     {
  164.         if (!$this->isHtmlResponse($response)) {
  165.             return;
  166.         }
  167.         $code $this->codeGenerator->generateCode($visitorInfo);
  168.         if (empty($code)) {
  169.             return;
  170.         }
  171.         $this->injectBeforeHeadEnd($response$code);
  172.     }
  173.     private function handleResponseActions(VisitorInfo $visitorInfoResponse $response)
  174.     {
  175.         $actions $this->getResponseActions($visitorInfo);
  176.         if (empty($actions)) {
  177.             return;
  178.         }
  179.         foreach ($actions as $type => $typeActions) {
  180.             $handler null;
  181.             if (method_exists($this->actionHandler'getActionHandler')) {
  182.                 $handler $this->actionHandler->getActionHandler($type);
  183.             }
  184.             if (!$handler instanceof ResponseTransformingActionHandlerInterface) {
  185.                 throw new \RuntimeException(sprintf(
  186.                     'The "%s" action handler does not implement ResponseTransformingActionHandlerInterface',
  187.                     $type
  188.                 ));
  189.             }
  190.             $handler->transformResponse($visitorInfo$response$typeActions);
  191.         }
  192.     }
  193.     private function getResponseActions(VisitorInfo $visitorInfo): array
  194.     {
  195.         $actions = [];
  196.         if (!$visitorInfo->hasActions()) {
  197.             return $actions;
  198.         }
  199.         foreach ($visitorInfo->getActions() as $action) {
  200.             $type $action['type'] ?? null;
  201.             $scope $action['scope'] ?? null;
  202.             if (empty($type) || empty($scope) || $scope !== VisitorInfo::ACTION_SCOPE_RESPONSE) {
  203.                 continue;
  204.             }
  205.             if (!isset($actions[$type])) {
  206.                 $actions[$type] = [$action];
  207.             } else {
  208.                 $actions[$type][] = $action;
  209.             }
  210.         }
  211.         return $actions;
  212.     }
  213.     private function appliesPersonalization(VisitorInfo $visitorInfo): bool
  214.     {
  215.         if (count($visitorInfo->getTargetGroupAssignments()) > 0) {
  216.             return true;
  217.         }
  218.         if ($visitorInfo->hasActions()) {
  219.             return true;
  220.         }
  221.         if ($visitorInfo->hasResponse()) {
  222.             return true;
  223.         }
  224.         return false;
  225.     }
  226. }