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

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