View source
<?php
namespace Drupal\business_rules\Util;
use Drupal\business_rules\BusinessRulesItemObject;
use Drupal\business_rules\Entity\Action;
use Drupal\business_rules\Entity\BusinessRule;
use Drupal\business_rules\Entity\Condition;
use Drupal\business_rules\Entity\Variable;
use Drupal\business_rules\Events\BusinessRulesEvent;
use Drupal\business_rules\VariableObject;
use Drupal\business_rules\VariablesSet;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Database\DatabaseExceptionWrapper;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Link;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\dbug\Dbug;
use Drupal\Core\Messenger\MessengerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\EventDispatcher\Event;
class BusinessRulesProcessor {
use StringTranslationTrait;
public $ruleBeingExecuted;
protected $actionManager;
protected $config;
protected $configFactory;
protected $debugArray = [];
protected $evaluatedVariables = [];
protected $eventDispatcher;
protected $processedRules = [];
protected $processId;
protected $variableManager;
private $conditionManager;
private $storage;
private $util;
protected $entityTypeManager;
protected $messenger;
protected $uuid;
public function __construct(ContainerInterface $container) {
$this->configFactory = $container
->get('config.factory');
$this->storage = $container
->get('config.storage');
$this->util = $container
->get('business_rules.util');
$this->actionManager = $container
->get('plugin.manager.business_rules.action');
$this->conditionManager = $container
->get('plugin.manager.business_rules.condition');
$this->variableManager = $container
->get('plugin.manager.business_rules.variable');
$this->config = $this->configFactory
->get('business_rules.settings');
$this->eventDispatcher = $container
->get('event_dispatcher');
$this->entityTypeManager = $container
->get('entity_type.manager');
$this->messenger = $container
->get('messenger');
$this->uuid = $container
->get('uuid');
}
public function process(BusinessRulesEvent $event) {
if ($this->config
->get('enable_safemode') == TRUE && $this->util->request
->get('brmode') == 'safe' || is_null($this->config
->get('enable_safemode')) && $this->util->request
->get('brmode') == 'safe') {
return;
}
if ($this
->avoidInfiniteLoop($event)) {
return;
}
$this->eventDispatcher
->dispatch('business_rules.before_process_event', $event);
if (!$event
->hasArgument('variables')) {
$event
->setArgument('variables', new VariablesSet());
}
$reacts_on_definition = $event
->getArgument('reacts_on');
$trigger = $reacts_on_definition['id'];
$triggered_rules = $this
->getTriggeredRules($event, $trigger);
$this
->processTriggeredRules($triggered_rules, $event);
$this
->saveDebugInfo();
$this->eventDispatcher
->dispatch('business_rules.after_process_event', $event);
}
private function avoidInfiniteLoop(BusinessRulesEvent $event) {
$processed_events =& drupal_static(__CLASS__ . '-process', []);
$loop_control = $event
->hasArgument('loop_control') ? $event
->getArgument('loop_control') : $event
->getSubject();
$serialized_data = json_encode($loop_control) . json_encode($event
->getArgument('reacts_on'));
if (count($processed_events)) {
foreach ($processed_events as $processed_event) {
if ($serialized_data == $processed_event) {
return TRUE;
}
}
}
$this->processId = $this->uuid
->generate();
$processed_events[$this->processId] = $serialized_data;
return FALSE;
}
public function getTriggeredRules(BusinessRulesEvent $event, $trigger) {
$entity_type = $event
->getArgument('entity_type_id');
$bundle = $event
->getArgument('bundle');
$rule_names = $this->storage
->listAll('business_rules.business_rule');
$rules = $this->storage
->readMultiple($rule_names);
$triggered_rules = [];
$this->eventDispatcher
->dispatch('business_rules.before_check_the_triggered_rules', $event);
foreach ($rules as $rule) {
$rule = new BusinessRule($rule);
if ($rule
->isEnabled() && $trigger == $rule
->getReactsOn() && ($entity_type == $rule
->getTargetEntityType() || empty($rule
->getTargetEntityType())) && ($bundle == $rule
->getTargetBundle() || empty($rule
->getTargetBundle()))) {
$triggered_rules[$rule
->id()] = $rule;
}
}
$this->eventDispatcher
->dispatch('business_rules.after_check_the_triggered_rules', $event);
return $triggered_rules;
}
public function processTriggeredRules(array $triggered_rules, BusinessRulesEvent $event) {
foreach ($triggered_rules as $rule) {
$items = $rule
->getItems();
$this->ruleBeingExecuted = $rule;
$this
->processItems($items, $event, $rule
->id());
$this->processedRules[$rule
->id()] = $rule
->id();
$this->debugArray['triggered_rules'][] = $rule;
}
}
public function saveDebugInfo() {
if ($this->config
->get('debug_screen')) {
$array = $this
->getDebugRenderArray();
$key_value = $this->util
->getKeyValueExpirable('debug');
$session_id = session_id();
$current = $key_value
->get($session_id);
if (isset($current['triggered_rules']) && count($current['triggered_rules'])) {
foreach ($current['triggered_rules'] as $key => $item) {
$array['triggered_rules'][$key] = $item;
}
}
$array = (object) $array;
$event = new Event($array);
$this->eventDispatcher
->dispatch('business_rules.before_save_debug_info_block', $event);
$array = (array) $array;
$key_value
->set($session_id, $array);
}
}
public function processItems(array $items, BusinessRulesEvent $event, $parent_id) {
$this->eventDispatcher
->dispatch('business_rules.before_process_items', $event);
foreach ($items as $item) {
if ($item
->getType() == BusinessRulesItemObject::ACTION) {
$action = Action::load($item
->getId());
if (!empty($action)) {
$this
->executeAction($action, $event);
$this->debugArray['actions'][$this->ruleBeingExecuted
->id()][] = [
'item' => $action,
'parent' => $parent_id,
];
}
else {
$this->util->logger
->error('Action id: %id not found', [
'%id' => $item
->getId(),
]);
$this->messenger
->addError($this
->t('Business Rules - Action id: %id not found.', [
'%id' => $item
->getId(),
]));
}
}
elseif ($item
->getType() == BusinessRulesItemObject::CONDITION) {
$condition = Condition::load($item
->getId());
if (empty($condition)) {
$this->util->logger
->error('Condition id: %id not found', [
'%id' => $item
->getId(),
]);
$this->messenger
->addError($this
->t('Business Rules Condition id: %id not found.', [
'%id' => $item
->getId(),
]));
}
else {
$success = $this
->isConditionValid($condition, $event);
if ($success) {
$condition_items = $condition
->getSuccessItems();
$this->debugArray['conditions'][$this->ruleBeingExecuted
->id()]['success'][] = [
'item' => $condition,
'parent' => $parent_id,
];
}
else {
$condition_items = $condition
->getFailItems();
$this->debugArray['conditions'][$this->ruleBeingExecuted
->id()]['fail'][] = [
'item' => $condition,
'parent' => $parent_id,
];
}
if (is_array($condition_items)) {
$this
->processItems($condition_items, $event, $condition
->id());
}
}
}
}
$this->eventDispatcher
->dispatch('business_rules.after_process_items', $event);
}
public function getDebugRenderArray() {
$triggered_rules = isset($this->debugArray['triggered_rules']) ? $this->debugArray['triggered_rules'] : [];
$evaluates_variables = isset($this->debugArray['variables']) ? $this->debugArray['variables'] : [];
$output = [];
if (!count($triggered_rules)) {
return $output;
}
foreach ($triggered_rules as $rule) {
$rule_link = Link::createFromRoute($rule
->id(), 'entity.business_rule.edit_form', [
'business_rule' => $rule
->id(),
]);
$output['triggered_rules'][$rule
->id()] = [
'#type' => 'details',
'#title' => $rule
->label(),
'#description' => $rule_link
->toString() . '<br>' . $rule
->getDescription(),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
];
if (isset($evaluates_variables[$rule
->id()]) && is_array($evaluates_variables[$rule
->id()])) {
$output['triggered_rules'][$rule
->id()]['variables'] = [
'#type' => 'details',
'#title' => $this
->t('Variables'),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
];
foreach ($evaluates_variables[$rule
->id()] as $evaluates_variable) {
$variable = Variable::load($evaluates_variable
->getId());
if ($variable instanceof Variable) {
$variable_link = Link::createFromRoute($variable
->id(), 'entity.business_rules_variable.edit_form', [
'business_rules_variable' => $variable
->id(),
]);
$variable_value = empty($evaluates_variable
->getValue()) ? 'NULL' : $evaluates_variable
->getValue();
if (!is_string($variable_value) && !is_numeric($variable_value)) {
$serialized = serialize($variable_value);
if (is_object($variable_value)) {
$arr = explode(':', $serialized);
$arr[0] = 'a';
unset($arr[1]);
unset($arr[2]);
$serialized = implode(':', $arr);
}
$unserialized = unserialize($serialized);
$variable_value = Dbug::debug($unserialized, 'array');
}
$output['triggered_rules'][$rule
->id()]['variables'][$evaluates_variable
->getId()] = [
'#type' => 'details',
'#title' => $variable
->label(),
'#description' => $variable_link
->toString() . '<br>' . $variable
->getDescription() . '<br>' . $this
->t('Value:') . '<br>',
'#collapsible' => TRUE,
'#collapsed' => TRUE,
];
$output['triggered_rules'][$rule
->id()]['variables'][$evaluates_variable
->getId()]['value'] = [
'#type' => 'markup',
'#markup' => $variable_value,
];
}
}
}
$output['triggered_rules'][$rule
->id()]['items'] = [
'#type' => 'details',
'#title' => $this
->t('Items'),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
];
$items = $rule
->getItems();
$output['triggered_rules'][$rule
->id()]['items'][] = $this
->getDebugItems($items, $rule
->id());
}
return $output;
}
public function executeAction(Action $action, BusinessRulesEvent $event) {
$this->eventDispatcher
->dispatch('business_rules.before_execute_action', new Event($event, $action));
$action_variables = $action
->getVariables();
$this
->evaluateVariables($action_variables, $event);
$result = $action
->execute($event);
$this->debugArray['action_result'][$this->ruleBeingExecuted
->id()][$action
->id()] = $result;
$this->eventDispatcher
->dispatch('business_rules.after_execute_action', new Event($event, $action));
return $result;
}
public function isConditionValid(Condition $condition, BusinessRulesEvent $event) {
$this->eventDispatcher
->dispatch('business_rules.before_check_if_condition_is_valid', new Event($event, $condition));
$condition_variables = $condition
->getVariables();
$this
->evaluateVariables($condition_variables, $event);
$result = $condition
->process($event);
$result = $condition
->isReverse() ? !$result : $result;
$this->eventDispatcher
->dispatch('business_rules.after_check_if_condition_is_valid', new Event($event, $condition));
return $result;
}
protected function getDebugItems(array $items, $parent_id) {
$actions_executed = isset($this->debugArray['actions'][$this->ruleBeingExecuted
->id()]) ? $this->debugArray['actions'][$this->ruleBeingExecuted
->id()] : [];
$conditions_success = isset($this->debugArray['conditions'][$this->ruleBeingExecuted
->id()]['success']) ? $this->debugArray['conditions'][$this->ruleBeingExecuted
->id()]['success'] : [];
$output = [];
foreach ($items as $item) {
if ($item
->getType() == BusinessRulesItemObject::ACTION) {
$action = Action::load($item
->getId());
if (!empty($action)) {
$action_link = Link::createFromRoute($action
->id(), 'entity.business_rules_action.edit_form', [
'business_rules_action' => $action
->id(),
]);
$style = 'fail';
foreach ($actions_executed as $executed) {
$action_parent = $executed['parent'];
$executed_action = $executed['item'];
if ($action_parent == $parent_id) {
$style = $executed_action
->id() == $action
->id() ? 'success' : 'fail';
if ($style == 'success') {
break;
}
}
}
$action_label = $this
->t('Action');
$output[$item
->getId()] = [
'#type' => 'details',
'#title' => $action_label . ': ' . $action
->label(),
'#description' => $action_link
->toString() . '<br>' . $action
->getDescription(),
'#attributes' => [
'class' => [
$style,
],
],
'#collapsible' => TRUE,
'#collapsed' => TRUE,
];
if (isset($this->debugArray['action_result'][$this->ruleBeingExecuted
->id()][$item
->getId()])) {
$output[$item
->getId()]['action_result'][$this->ruleBeingExecuted
->id()] = $this->debugArray['action_result'][$this->ruleBeingExecuted
->id()][$item
->getId()];
}
}
}
elseif ($item
->getType() == BusinessRulesItemObject::CONDITION) {
$condition = Condition::load($item
->getId());
if (!empty($condition)) {
$condition_link = Link::createFromRoute($condition
->id(), 'entity.business_rules_condition.edit_form', [
'business_rules_condition' => $condition
->id(),
]);
$style = 'fail';
foreach ($conditions_success as $success) {
$condition_parent = $success['parent'];
$executed_condition = $success['item'];
if ($condition_parent == $parent_id) {
$style = $executed_condition
->id() == $condition
->id() ? 'success' : 'fail';
if ($style == 'success') {
break;
}
}
}
$title = $condition
->isReverse() ? $this
->t('(Not)') . ' ' . $condition
->label() : $condition
->label();
$condition_label = $this
->t('Condition');
$output[$item
->getId()] = [
'#type' => 'details',
'#title' => $condition_label . ': ' . $title,
'#description' => $condition_link
->toString() . '<br>' . $condition
->getDescription(),
'#attributes' => [
'class' => [
$style,
],
],
'#collapsible' => TRUE,
'#collapsed' => TRUE,
];
$success_items = $condition
->getSuccessItems();
if (is_array($success_items) && count($success_items)) {
$output[$item
->getId()]['success'] = [
'#type' => 'details',
'#title' => $this
->t('Success items'),
'#attributes' => [
'class' => [
$style,
],
],
'#collapsible' => TRUE,
'#collapsed' => TRUE,
];
$output[$item
->getId()]['success'][] = $this
->getDebugItems($success_items, $condition
->id());
}
$fail_items = $condition
->getFailItems();
if (is_array($fail_items) && count($fail_items)) {
$output[$item
->getId()]['fail'] = [
'#type' => 'details',
'#title' => $this
->t('Fail items'),
'#attributes' => [
'class' => [
$style == 'success' ? 'fail' : 'success',
],
],
'#collapsible' => TRUE,
'#collapsed' => TRUE,
];
$output[$item
->getId()]['fail'][] = $this
->getDebugItems($fail_items, $condition
->id());
}
}
}
}
return $output;
}
public function evaluateVariables(VariablesSet $variablesSet, BusinessRulesEvent $event) {
$this->eventDispatcher
->dispatch('business_rules.before_evaluate_variables', new Event($event, $variablesSet));
if ($variablesSet
->count()) {
foreach ($variablesSet
->getVariables() as $variable) {
$varObject = Variable::load($variable
->getId());
if ($varObject instanceof Variable) {
if (!array_key_exists($variable
->getId(), $this->evaluatedVariables)) {
$this
->evaluateVariable($varObject, $event);
}
}
}
}
$this->eventDispatcher
->dispatch('business_rules.after_evaluate_variables', new Event($event, $variablesSet));
}
public function evaluateVariable(Variable $variable, BusinessRulesEvent $event) {
if (array_key_exists($variable
->id(), $this->evaluatedVariables)) {
return NULL;
}
$eventVariables = $event
->getArgument('variables');
$variable_variables = $variable
->getVariables();
$this
->evaluateVariables($variable_variables, $event);
$value = $variable
->evaluate($event);
if ($value instanceof VariableObject) {
$this->evaluatedVariables[$variable
->id()] = $variable
->id();
$eventVariables
->append($value);
$this->debugArray['variables'][$this->ruleBeingExecuted
->id()][$variable
->id()] = $value;
return $value;
}
elseif ($value instanceof VariablesSet) {
if ($value
->count()) {
foreach ($value
->getVariables() as $item) {
$this->evaluatedVariables[$item
->getId()] = $item
->getId();
$eventVariables
->append($item);
$this->debugArray['variables'][$this->ruleBeingExecuted
->id()][$item
->getId()] = $item;
}
}
return $value;
}
else {
throw new \Exception(get_class($value) . '::evaluate should return instance of ' . get_class(new VariableObject()) . ' or ' . get_class(new VariablesSet()) . '.');
}
}
public function ruleExists($reacts_on, EntityInterface $entity) {
if (!$entity instanceof ContentEntityInterface) {
return FALSE;
}
$business_rule_entity = $this->entityTypeManager
->getStorage('business_rule')
->loadByProperties([
'reacts_on' => $reacts_on,
'target_entity_type' => $entity
->getEntityTypeId(),
'target_bundle' => $entity
->bundle(),
]);
return !empty($business_rule_entity);
}
public function __destruct() {
if ($this->config
->get('clear_render_cache')) {
Cache::invalidateTags([
'rendered',
]);
}
}
}