pimcore/lib/Pimcore/Targeting/EventListener/TargetingListener.php line 141

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->isMasterRequest()) {
  92.             return;
  93.         }
  94.         $request $event->getRequest();
  95.         // only apply targeting for GET requests
  96.         // this may revised in later versions
  97.         if ('GET' !== $request->getMethod()) {
  98.             return;
  99.         }
  100.         if (!$this->matchesPimcoreContext($requestPimcoreContextResolver::CONTEXT_DEFAULT)) {
  101.             return;
  102.         }
  103.         if (!$this->requestHelper->isFrontendRequest($request) || $this->requestHelper->isFrontendRequestByAdmin($request)) {
  104.             return;
  105.         }
  106.         if (!$this->visitorInfoResolver->isTargetingConfigured()) {
  107.             return;
  108.         }
  109.         $this->startStopwatch('Targeting:resolveVisitorInfo''targeting');
  110.         $visitorInfo $this->visitorInfoResolver->resolve($request);
  111.         $this->stopStopwatch('Targeting:resolveVisitorInfo');
  112.         // propagate response (e.g. redirect) to request handling
  113.         if ($visitorInfo->hasResponse()) {
  114.             $event->setResponse($visitorInfo->getResponse());
  115.         }
  116.     }
  117.     public function onPreResolve(TargetingEvent $event)
  118.     {
  119.         $this->startStopwatch('Targeting:loadStoredAssignments''targeting');
  120.         /** @var AssignTargetGroup $assignTargetGroupHandler */
  121.         $assignTargetGroupHandler $this->actionHandler->getActionHandler('assign_target_group');
  122.         $assignTargetGroupHandler->loadStoredAssignments($event->getVisitorInfo()); // load previously assigned target groups
  123.         $this->stopStopwatch('Targeting:loadStoredAssignments');
  124.     }
  125.     public function onKernelResponse(FilterResponseEvent $event)
  126.     {
  127.         if (!$this->enabled) {
  128.             return;
  129.         }
  130.         if (!$this->visitorInfoStorage->hasVisitorInfo()) {
  131.             return;
  132.         }
  133.         $visitorInfo $this->visitorInfoStorage->getVisitorInfo();
  134.         $response    $event->getResponse();
  135.         if ($event->isMasterRequest()) {
  136.             $this->startStopwatch('Targeting:responseActions''targeting');
  137.             // handle recorded actions on response
  138.             $this->handleResponseActions($visitorInfo$response);
  139.             $this->stopStopwatch('Targeting:responseActions');
  140.             if ($this->visitorInfoResolver->isTargetingConfigured()) {
  141.                 $this->injectTargetingCode($response$visitorInfo);
  142.             }
  143.         }
  144.         // check if the visitor info influences the response
  145.         if ($this->appliesPersonalization($visitorInfo)) {
  146.             // set response to private as soon as we apply personalization
  147.             $response->setPrivate();
  148.         }
  149.     }
  150.     private function injectTargetingCode(Response $responseVisitorInfo $visitorInfo)
  151.     {
  152.         if (!$this->isHtmlResponse($response)) {
  153.             return;
  154.         }
  155.         $code $this->codeGenerator->generateCode($visitorInfo);
  156.         if (empty($code)) {
  157.             return;
  158.         }
  159.         $this->injectBeforeHeadEnd($response$code);
  160.     }
  161.     private function handleResponseActions(VisitorInfo $visitorInfoResponse $response)
  162.     {
  163.         $actions $this->getResponseActions($visitorInfo);
  164.         if (empty($actions)) {
  165.             return;
  166.         }
  167.         foreach ($actions as $type => $typeActions) {
  168.             $handler $this->actionHandler->getActionHandler($type);
  169.             if (!$handler instanceof ResponseTransformingActionHandlerInterface) {
  170.                 throw new \RuntimeException(sprintf(
  171.                     'The "%s" action handler does not implement ResponseTransformingActionHandlerInterface',
  172.                     $type
  173.                 ));
  174.             }
  175.             $handler->transformResponse($visitorInfo$response$typeActions);
  176.         }
  177.     }
  178.     private function getResponseActions(VisitorInfo $visitorInfo): array
  179.     {
  180.         $actions = [];
  181.         if (!$visitorInfo->hasActions()) {
  182.             return $actions;
  183.         }
  184.         foreach ($visitorInfo->getActions() as $action) {
  185.             $type  $action['type'] ?? null;
  186.             $scope $action['scope'] ?? null;
  187.             if (empty($type) || empty($scope) || $scope !== VisitorInfo::ACTION_SCOPE_RESPONSE) {
  188.                 continue;
  189.             }
  190.             if (!is_array($actions[$type])) {
  191.                 $actions[$type] = [$action];
  192.             } else {
  193.                 $actions[$type][] = $action;
  194.             }
  195.         }
  196.         return $actions;
  197.     }
  198.     private function appliesPersonalization(VisitorInfo $visitorInfo): bool
  199.     {
  200.         if (count($visitorInfo->getTargetGroupAssignments()) > 0) {
  201.             return true;
  202.         }
  203.         if ($visitorInfo->hasActions()) {
  204.             return true;
  205.         }
  206.         if ($visitorInfo->hasResponse()) {
  207.             return true;
  208.         }
  209.         return false;
  210.     }
  211. }