vendor/pimcore/pimcore/lib/Targeting/VisitorInfoResolver.php line 116

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;
  16. use Doctrine\DBAL\Connection;
  17. use Pimcore\Debug\Traits\StopwatchTrait;
  18. use Pimcore\Event\Targeting\TargetingEvent;
  19. use Pimcore\Event\Targeting\TargetingResolveVisitorInfoEvent;
  20. use Pimcore\Event\Targeting\TargetingRuleEvent;
  21. use Pimcore\Event\TargetingEvents;
  22. use Pimcore\Model\Tool\Targeting\Rule;
  23. use Pimcore\Targeting\ActionHandler\ActionHandlerInterface;
  24. use Pimcore\Targeting\ActionHandler\DelegatingActionHandler;
  25. use Pimcore\Targeting\Model\VisitorInfo;
  26. use Pimcore\Targeting\Storage\TargetingStorageInterface;
  27. use Symfony\Component\HttpFoundation\Request;
  28. use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
  29. class VisitorInfoResolver
  30. {
  31.     use StopwatchTrait;
  32.     const ATTRIBUTE_VISITOR_INFO '_visitor_info';
  33.     const STORAGE_KEY_RULE_CONDITION_VARIABLES 'vi:var';
  34.     const STORAGE_KEY_MATCHED_SESSION_RULES 'vi:sru'// visitorInfo:sessionRules
  35.     const STORAGE_KEY_MATCHED_VISITOR_RULES 'vi:vru'// visitorInfo:visitorRules
  36.     /**
  37.      * @var TargetingStorageInterface
  38.      */
  39.     private $targetingStorage;
  40.     /**
  41.      * @var VisitorInfoStorageInterface
  42.      */
  43.     private $visitorInfoStorage;
  44.     /**
  45.      * @var ConditionMatcherInterface
  46.      */
  47.     private $conditionMatcher;
  48.     /**
  49.      * @var DelegatingActionHandler|ActionHandlerInterface
  50.      */
  51.     private $actionHandler;
  52.     /**
  53.      * @var Connection
  54.      */
  55.     private $db;
  56.     /**
  57.      * @var EventDispatcherInterface
  58.      */
  59.     private $eventDispatcher;
  60.     /**
  61.      * @var Rule[]|null
  62.      */
  63.     private $targetingRules;
  64.     /**
  65.      * @var bool|null
  66.      */
  67.     private $targetingConfigured;
  68.     public function __construct(
  69.         TargetingStorageInterface $targetingStorage,
  70.         VisitorInfoStorageInterface $visitorInfoStorage,
  71.         ConditionMatcherInterface $conditionMatcher,
  72.         ActionHandlerInterface $actionHandler,
  73.         Connection $db,
  74.         EventDispatcherInterface $eventDispatcher
  75.     ) {
  76.         $this->targetingStorage $targetingStorage;
  77.         $this->visitorInfoStorage $visitorInfoStorage;
  78.         $this->conditionMatcher $conditionMatcher;
  79.         $this->actionHandler $actionHandler;
  80.         $this->eventDispatcher $eventDispatcher;
  81.         $this->db $db;
  82.     }
  83.     public function resolve(Request $request): VisitorInfo
  84.     {
  85.         if ($this->visitorInfoStorage->hasVisitorInfo()) {
  86.             return $this->visitorInfoStorage->getVisitorInfo();
  87.         }
  88.         $visitorInfo VisitorInfo::fromRequest($request);
  89.         if (!$this->isTargetingConfigured()) {
  90.             return $visitorInfo;
  91.         }
  92.         $event = new TargetingResolveVisitorInfoEvent($visitorInfo);
  93.         $this->eventDispatcher->dispatch($eventTargetingEvents::PRE_RESOLVE);
  94.         $visitorInfo $event->getVisitorInfo();
  95.         $this->matchTargetingRuleConditions($visitorInfo);
  96.         $this->eventDispatcher->dispatch(new TargetingEvent($visitorInfo), TargetingEvents::POST_RESOLVE);
  97.         $this->visitorInfoStorage->setVisitorInfo($visitorInfo);
  98.         return $visitorInfo;
  99.     }
  100.     public function isTargetingConfigured(): bool
  101.     {
  102.         if (null !== $this->targetingConfigured) {
  103.             return $this->targetingConfigured;
  104.         }
  105.         $configuredRules $this->db->fetchOne(
  106.             'SELECT id FROM targeting_target_groups UNION SELECT id FROM targeting_rules LIMIT 1'
  107.         );
  108.         $this->targetingConfigured $configuredRules && (int)$configuredRules 0;
  109.         return $this->targetingConfigured;
  110.     }
  111.     private function matchTargetingRuleConditions(VisitorInfo $visitorInfo)
  112.     {
  113.         $rules $this->getTargetingRules();
  114.         foreach ($rules as $rule) {
  115.             if (Rule::SCOPE_SESSION === $rule->getScope()) {
  116.                 if ($this->ruleWasMatchedInSession($visitorInfo$rule)) {
  117.                     continue;
  118.                 }
  119.             } elseif (Rule::SCOPE_VISITOR === $rule->getScope()) {
  120.                 if ($this->ruleWasMatchedForVisitor($visitorInfo$rule)) {
  121.                     continue;
  122.                 }
  123.             }
  124.             $this->matchTargetingRuleCondition($visitorInfo$rule);
  125.         }
  126.     }
  127.     private function matchTargetingRuleCondition(VisitorInfo $visitorInfoRule $rule)
  128.     {
  129.         $scopeWithVariables Rule::SCOPE_SESSION_WITH_VARIABLES === $rule->getScope();
  130.         $this->startStopwatch('Targeting:match:' $rule->getName(), 'targeting');
  131.         $match $this->conditionMatcher->match(
  132.             $visitorInfo,
  133.             $rule->getConditions(),
  134.             $scopeWithVariables
  135.         );
  136.         $this->stopStopwatch('Targeting:match:' $rule->getName());
  137.         if (!$match) {
  138.             return;
  139.         }
  140.         if ($scopeWithVariables) {
  141.             $collectedVariables $this->conditionMatcher->getCollectedVariables();
  142.             // match only once with the same variables
  143.             if ($this->ruleWasMatchedInSessionWithVariables($visitorInfo$rule$collectedVariables)) {
  144.                 return;
  145.             }
  146.         }
  147.         if (Rule::SCOPE_SESSION === $rule->getScope()) {
  148.             // record the rule as matched for the current session
  149.             $this->markRuleAsMatchedInSession($visitorInfo$rule);
  150.         } elseif (Rule::SCOPE_VISITOR === $rule->getScope()) {
  151.             // record the rule as matched for the visitor
  152.             $this->markRuleAsMatchedForVisitor($visitorInfo$rule);
  153.         }
  154.         // store info about matched rule
  155.         $visitorInfo->addMatchingTargetingRule($rule);
  156.         $this->eventDispatcher->dispatch(new TargetingRuleEvent($visitorInfo$rule), TargetingEvents::PRE_RULE_ACTIONS);
  157.         // execute rule actions
  158.         $this->handleTargetingRuleActions($visitorInfo$rule);
  159.         $this->eventDispatcher->dispatch(new TargetingRuleEvent($visitorInfo$rule), TargetingEvents::POST_RULE_ACTIONS);
  160.     }
  161.     private function handleTargetingRuleActions(VisitorInfo $visitorInfoRule $rule)
  162.     {
  163.         $actions $rule->getActions();
  164.         if (!$actions || !is_array($actions)) {
  165.             return;
  166.         }
  167.         foreach ($actions as $action) {
  168.             if (!is_array($action)) {
  169.                 continue;
  170.             }
  171.             $this->actionHandler->apply($visitorInfo$action$rule);
  172.         }
  173.     }
  174.     /**
  175.      * @return Rule[]
  176.      */
  177.     private function getTargetingRules(): array
  178.     {
  179.         if (null !== $this->targetingRules) {
  180.             return $this->targetingRules;
  181.         }
  182.         /** @var Rule\Listing|Rule\Listing\Dao $list */
  183.         $list = new Rule\Listing();
  184.         $list->setCondition('active = 1');
  185.         $list->setOrderKey('prio');
  186.         $list->setOrder('ASC');
  187.         $this->targetingRules $list->load();
  188.         return $this->targetingRules;
  189.     }
  190.     private function ruleWasMatchedInSession(VisitorInfo $visitorInfoRule $rule): bool
  191.     {
  192.         return $this->ruleWasMatched(
  193.             $visitorInfo$rule,
  194.             TargetingStorageInterface::SCOPE_SESSIONself::STORAGE_KEY_MATCHED_SESSION_RULES
  195.         );
  196.     }
  197.     private function markRuleAsMatchedInSession(VisitorInfo $visitorInfoRule $rule)
  198.     {
  199.         $this->markRuleAsMatched(
  200.             $visitorInfo$rule,
  201.             TargetingStorageInterface::SCOPE_SESSIONself::STORAGE_KEY_MATCHED_SESSION_RULES
  202.         );
  203.     }
  204.     private function ruleWasMatchedForVisitor(VisitorInfo $visitorInfoRule $rule): bool
  205.     {
  206.         return $this->ruleWasMatched(
  207.             $visitorInfo$rule,
  208.             TargetingStorageInterface::SCOPE_VISITORself::STORAGE_KEY_MATCHED_VISITOR_RULES
  209.         );
  210.     }
  211.     private function markRuleAsMatchedForVisitor(VisitorInfo $visitorInfoRule $rule)
  212.     {
  213.         $this->markRuleAsMatched(
  214.             $visitorInfo$rule,
  215.             TargetingStorageInterface::SCOPE_VISITORself::STORAGE_KEY_MATCHED_VISITOR_RULES
  216.         );
  217.     }
  218.     private function ruleWasMatched(VisitorInfo $visitorInfoRule $rulestring $scopestring $storageKey): bool
  219.     {
  220.         $matchedRules $this->targetingStorage->get($visitorInfo$scope$storageKey, []);
  221.         return in_array($rule->getId(), $matchedRules);
  222.     }
  223.     private function markRuleAsMatched(VisitorInfo $visitorInfoRule $rulestring $scopestring $storageKey)
  224.     {
  225.         $matchedRules $this->targetingStorage->get($visitorInfo$scope$storageKey, []);
  226.         if (!in_array($rule->getId(), $matchedRules)) {
  227.             $matchedRules[] = $rule->getId();
  228.         }
  229.         $this->targetingStorage->set($visitorInfo$scope$storageKey$matchedRules);
  230.     }
  231.     private function ruleWasMatchedInSessionWithVariables(VisitorInfo $visitorInfoRule $rule, array $variables): bool
  232.     {
  233.         $hash sha1(serialize($variables));
  234.         $storedVariables $this->targetingStorage->get(
  235.             $visitorInfo,
  236.             TargetingStorageInterface::SCOPE_SESSION,
  237.             self::STORAGE_KEY_RULE_CONDITION_VARIABLES,
  238.             []
  239.         );
  240.         // hash was already matched
  241.         if (isset($storedVariables[$rule->getId()]) && $storedVariables[$rule->getId()] === $hash) {
  242.             return true;
  243.         }
  244.         // store hash to storage
  245.         $storedVariables[$rule->getId()] = $hash;
  246.         $this->targetingStorage->set(
  247.             $visitorInfo,
  248.             TargetingStorageInterface::SCOPE_SESSION,
  249.             self::STORAGE_KEY_RULE_CONDITION_VARIABLES,
  250.             $storedVariables
  251.         );
  252.         return false;
  253.     }
  254. }