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

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;
  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\EventDispatcher\EventDispatcherInterface;
  28. use Symfony\Component\HttpFoundation\Request;
  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[]
  62.      */
  63.     private $targetingRules;
  64.     /**
  65.      * @var bool
  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(
  94.             TargetingEvents::PRE_RESOLVE,
  95.             $event
  96.         );
  97.         $visitorInfo $event->getVisitorInfo();
  98.         $this->matchTargetingRuleConditions($visitorInfo);
  99.         $this->eventDispatcher->dispatch(
  100.             TargetingEvents::POST_RESOLVE,
  101.             new TargetingEvent($visitorInfo)
  102.         );
  103.         $this->visitorInfoStorage->setVisitorInfo($visitorInfo);
  104.         return $visitorInfo;
  105.     }
  106.     public function isTargetingConfigured(): bool
  107.     {
  108.         if (null !== $this->targetingConfigured) {
  109.             return $this->targetingConfigured;
  110.         }
  111.         $configuredRules $this->db->fetchColumn(
  112.             'SELECT id FROM targeting_target_groups UNION SELECT id FROM targeting_rules LIMIT 1'
  113.         );
  114.         $this->targetingConfigured $configuredRules && (int)$configuredRules 0;
  115.         return $this->targetingConfigured;
  116.     }
  117.     private function matchTargetingRuleConditions(VisitorInfo $visitorInfo)
  118.     {
  119.         $rules $this->getTargetingRules();
  120.         foreach ($rules as $rule) {
  121.             if (Rule::SCOPE_SESSION === $rule->getScope()) {
  122.                 if ($this->ruleWasMatchedInSession($visitorInfo$rule)) {
  123.                     continue;
  124.                 }
  125.             } elseif (Rule::SCOPE_VISITOR === $rule->getScope()) {
  126.                 if ($this->ruleWasMatchedForVisitor($visitorInfo$rule)) {
  127.                     continue;
  128.                 }
  129.             }
  130.             $this->matchTargetingRuleCondition($visitorInfo$rule);
  131.         }
  132.     }
  133.     private function matchTargetingRuleCondition(VisitorInfo $visitorInfoRule $rule)
  134.     {
  135.         $scopeWithVariables Rule::SCOPE_SESSION_WITH_VARIABLES === $rule->getScope();
  136.         $this->startStopwatch('Targeting:match:' $rule->getName(), 'targeting');
  137.         $match $this->conditionMatcher->match(
  138.             $visitorInfo,
  139.             $rule->getConditions(),
  140.             $scopeWithVariables
  141.         );
  142.         $this->stopStopwatch('Targeting:match:' $rule->getName());
  143.         if (!$match) {
  144.             return;
  145.         }
  146.         if ($scopeWithVariables) {
  147.             $collectedVariables $this->conditionMatcher->getCollectedVariables();
  148.             // match only once with the same variables
  149.             if ($this->ruleWasMatchedInSessionWithVariables($visitorInfo$rule$collectedVariables)) {
  150.                 return;
  151.             }
  152.         }
  153.         if (Rule::SCOPE_SESSION === $rule->getScope()) {
  154.             // record the rule as matched for the current session
  155.             $this->markRuleAsMatchedInSession($visitorInfo$rule);
  156.         } elseif (Rule::SCOPE_VISITOR === $rule->getScope()) {
  157.             // record the rule as matched for the visitor
  158.             $this->markRuleAsMatchedForVisitor($visitorInfo$rule);
  159.         }
  160.         // store info about matched rule
  161.         $visitorInfo->addMatchingTargetingRule($rule);
  162.         $this->eventDispatcher->dispatch(
  163.             TargetingEvents::PRE_RULE_ACTIONS,
  164.             new TargetingRuleEvent($visitorInfo$rule)
  165.         );
  166.         // execute rule actions
  167.         $this->handleTargetingRuleActions($visitorInfo$rule);
  168.         $this->eventDispatcher->dispatch(
  169.             TargetingEvents::POST_RULE_ACTIONS,
  170.             new TargetingRuleEvent($visitorInfo$rule)
  171.         );
  172.     }
  173.     private function handleTargetingRuleActions(VisitorInfo $visitorInfoRule $rule)
  174.     {
  175.         $actions $rule->getActions();
  176.         if (!$actions || !is_array($actions)) {
  177.             return;
  178.         }
  179.         foreach ($actions as $action) {
  180.             if (!is_array($action)) {
  181.                 continue;
  182.             }
  183.             $this->actionHandler->apply($visitorInfo$action$rule);
  184.         }
  185.     }
  186.     /**
  187.      * @return Rule[]
  188.      */
  189.     private function getTargetingRules(): array
  190.     {
  191.         if (null !== $this->targetingRules) {
  192.             return $this->targetingRules;
  193.         }
  194.         /** @var Rule\Listing|Rule\Listing\Dao $list */
  195.         $list = new Rule\Listing();
  196.         $list->setCondition('active = 1');
  197.         $list->setOrderKey('prio');
  198.         $list->setOrder('ASC');
  199.         $this->targetingRules $list->load();
  200.         return $this->targetingRules;
  201.     }
  202.     private function ruleWasMatchedInSession(VisitorInfo $visitorInfoRule $rule): bool
  203.     {
  204.         return $this->ruleWasMatched(
  205.             $visitorInfo$rule,
  206.             TargetingStorageInterface::SCOPE_SESSIONself::STORAGE_KEY_MATCHED_SESSION_RULES
  207.         );
  208.     }
  209.     private function markRuleAsMatchedInSession(VisitorInfo $visitorInfoRule $rule)
  210.     {
  211.         $this->markRuleAsMatched(
  212.             $visitorInfo$rule,
  213.             TargetingStorageInterface::SCOPE_SESSIONself::STORAGE_KEY_MATCHED_SESSION_RULES
  214.         );
  215.     }
  216.     private function ruleWasMatchedForVisitor(VisitorInfo $visitorInfoRule $rule): bool
  217.     {
  218.         return $this->ruleWasMatched(
  219.             $visitorInfo$rule,
  220.             TargetingStorageInterface::SCOPE_VISITORself::STORAGE_KEY_MATCHED_VISITOR_RULES
  221.         );
  222.     }
  223.     private function markRuleAsMatchedForVisitor(VisitorInfo $visitorInfoRule $rule)
  224.     {
  225.         $this->markRuleAsMatched(
  226.             $visitorInfo$rule,
  227.             TargetingStorageInterface::SCOPE_VISITORself::STORAGE_KEY_MATCHED_VISITOR_RULES
  228.         );
  229.     }
  230.     private function ruleWasMatched(VisitorInfo $visitorInfoRule $rulestring $scopestring $storageKey): bool
  231.     {
  232.         $matchedRules $this->targetingStorage->get($visitorInfo$scope$storageKey, []);
  233.         return in_array($rule->getId(), $matchedRules);
  234.     }
  235.     private function markRuleAsMatched(VisitorInfo $visitorInfoRule $rulestring $scopestring $storageKey)
  236.     {
  237.         $matchedRules $this->targetingStorage->get($visitorInfo$scope$storageKey, []);
  238.         if (!in_array($rule->getId(), $matchedRules)) {
  239.             $matchedRules[] = $rule->getId();
  240.         }
  241.         $this->targetingStorage->set($visitorInfo$scope$storageKey$matchedRules);
  242.     }
  243.     private function ruleWasMatchedInSessionWithVariables(VisitorInfo $visitorInfoRule $rule, array $variables): bool
  244.     {
  245.         $hash sha1(serialize($variables));
  246.         $storedVariables $this->targetingStorage->get(
  247.             $visitorInfo,
  248.             TargetingStorageInterface::SCOPE_SESSION,
  249.             self::STORAGE_KEY_RULE_CONDITION_VARIABLES,
  250.             []
  251.         );
  252.         // hash was already matched
  253.         if (isset($storedVariables[$rule->getId()]) && $storedVariables[$rule->getId()] === $hash) {
  254.             return true;
  255.         }
  256.         // store hash to storage
  257.         $storedVariables[$rule->getId()] = $hash;
  258.         $this->targetingStorage->set(
  259.             $visitorInfo,
  260.             TargetingStorageInterface::SCOPE_SESSION,
  261.             self::STORAGE_KEY_RULE_CONDITION_VARIABLES,
  262.             $storedVariables
  263.         );
  264.         return false;
  265.     }
  266. }