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

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