<?php
declare(strict_types=1);
/**
* Pimcore
*
* This source file is available under two different licenses:
* - GNU General Public License version 3 (GPLv3)
* - Pimcore Commercial License (PCL)
* Full copyright and license information is available in
* LICENSE.md which is distributed with this source code.
*
* @copyright Copyright (c) Pimcore GmbH (http://www.pimcore.org)
* @license http://www.pimcore.org/license GPLv3 and PCL
*/
namespace Pimcore\Targeting;
use Pimcore\Targeting\Condition\ConditionInterface;
use Pimcore\Targeting\Condition\EventDispatchingConditionInterface;
use Pimcore\Targeting\Condition\VariableConditionInterface;
use Pimcore\Targeting\ConditionMatcher\ExpressionBuilder;
use Pimcore\Targeting\Model\VisitorInfo;
use Psr\Log\LoggerInterface;
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
class ConditionMatcher implements ConditionMatcherInterface
{
/**
* @var ConditionFactoryInterface
*/
private $conditionFactory;
/**
* @var DataLoaderInterface
*/
private $dataLoader;
/**
* @var EventDispatcherInterface
*/
private $eventDispatcher;
/**
* @var ExpressionLanguage
*/
private $expressionLanguage;
/**
* @var LoggerInterface
*/
private $logger;
/**
* @var array
*/
private $collectedVariables = [];
public function __construct(
ConditionFactoryInterface $conditionFactory,
DataLoaderInterface $dataLoader,
EventDispatcherInterface $eventDispatcher,
ExpressionLanguage $expressionLanguage,
LoggerInterface $logger
) {
$this->conditionFactory = $conditionFactory;
$this->dataLoader = $dataLoader;
$this->eventDispatcher = $eventDispatcher;
$this->expressionLanguage = $expressionLanguage;
$this->logger = $logger;
}
/**
* {@inheritdoc}
*/
public function match(VisitorInfo $visitorInfo, array $conditions, bool $collectVariables = false): bool
{
// reset internal state
$this->collectedVariables = [];
$count = count($conditions);
if (0 === $count) {
// no conditions -> rule matches
return true;
} elseif (1 === $count) {
// no need to build up expression if there's only one condition
return $this->matchCondition($visitorInfo, $conditions[0], $collectVariables);
}
$expressionBuilder = new ExpressionBuilder();
foreach ($conditions as $conditionConfig) {
$conditionResult = $this->matchCondition($visitorInfo, $conditionConfig, $collectVariables);
$expressionBuilder->addCondition($conditionConfig, $conditionResult);
}
$expression = $expressionBuilder->getExpression();
$values = $expressionBuilder->getValues();
$result = $this->expressionLanguage->evaluate($expression, $values);
return (bool)$result;
}
/**
* {@inheritdoc}
*/
public function getCollectedVariables(): array
{
return $this->collectedVariables;
}
private function matchCondition(VisitorInfo $visitorInfo, array $config, bool $collectVariables = false): bool
{
try {
$condition = $this->conditionFactory->build($config);
} catch (\Throwable $e) {
$this->logger->error((string) $e);
return false;
}
// check prerequisites - e.g. a condition without a value
// (= all values match) does not need to fetch provider data
// as location or browser
if (!$condition->canMatch()) {
return false;
}
if ($condition instanceof DataProviderDependentInterface) {
$this->dataLoader->loadDataFromProviders($visitorInfo, $condition->getDataProviderKeys());
}
if ($condition instanceof EventDispatchingConditionInterface) {
$condition->preMatch($visitorInfo, $this->eventDispatcher);
}
$result = false;
try {
$result = $condition->match($visitorInfo);
} catch (\Throwable $e) {
$this->logger->error((string) $e);
return false;
}
if ($collectVariables) {
$this->collectConditionVariables($config, $condition);
}
if ($condition instanceof EventDispatchingConditionInterface) {
$condition->postMatch($visitorInfo, $this->eventDispatcher);
}
return $result;
}
private function collectConditionVariables(array $config, ConditionInterface $condition)
{
$data = [
'type' => $config['type'],
];
if ($condition instanceof VariableConditionInterface) {
$variables = $condition->getMatchedVariables();
if (!empty($variables)) {
$data['data'] = $variables;
}
}
$this->collectedVariables[] = $data;
}
}