-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
<?php | ||
|
||
namespace Civi\Notification\Entity; | ||
|
||
abstract class AbstractEntity { | ||
|
||
private array $entityValues; | ||
Check failure on line 7 in Civi/Notification/Entity/AbstractEntity.php
|
||
|
||
public function __construct(array $entityValues) { | ||
Check failure on line 9 in Civi/Notification/Entity/AbstractEntity.php
|
||
$this->entityValues = $entityValues; | ||
} | ||
|
||
public function __get($name) { | ||
Check failure on line 13 in Civi/Notification/Entity/AbstractEntity.php
|
||
|
||
} | ||
|
||
public function getEntityValue(string $key) { | ||
Check failure on line 17 in Civi/Notification/Entity/AbstractEntity.php
|
||
// Check if the value is JSON-encoded and decode if so | ||
if (\CRM_Utils_JSON::isValidJSON($this->entityValues[$key])) { | ||
return json_decode($this->entityValues[$key], true); | ||
} | ||
|
||
return $this->entityValues[$key]; | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
<?php | ||
|
||
namespace Civi\Notification\Entity; | ||
|
||
use Civi\Api4\NotificationCondition; | ||
|
||
class ConditionEntity extends AbstractEntity { | ||
|
||
public static function loadByRuleId(int $ruleId): array { | ||
Check failure on line 9 in Civi/Notification/Entity/ConditionEntity.php
|
||
$notificationConditions = NotificationCondition::get() | ||
->addWhere('rule_id', '=', $ruleId) | ||
->execute(); | ||
|
||
// Convert results into array of ConditionEntity objects | ||
$conditions = []; | ||
foreach ($notificationConditions as $condition) { | ||
$conditions[] = new self($condition); | ||
Check failure on line 17 in Civi/Notification/Entity/ConditionEntity.php
|
||
} | ||
|
||
return $conditions; | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
<?php | ||
|
||
namespace Civi\Notification\Entity; | ||
|
||
use Civi\Api4\NotificationFieldMonitoring; | ||
|
||
class FieldMonitoringEntity extends AbstractEntity { | ||
|
||
public static function loadByRuleId(int $ruleId): array { | ||
Check failure on line 9 in Civi/Notification/Entity/FieldMonitoringEntity.php
|
||
$notificationFieldMonitorings = NotificationFieldMonitoring::get() | ||
->addWhere('rule_id', '=', $ruleId) | ||
->execute(); | ||
|
||
// Convert results into array of FieldMonitoringEntity objects | ||
$fieldMonitorings = []; | ||
foreach ($notificationFieldMonitorings as $fieldMonitoring) { | ||
$fieldMonitorings[] = new self($fieldMonitoring); | ||
Check failure on line 17 in Civi/Notification/Entity/FieldMonitoringEntity.php
|
||
} | ||
|
||
return $fieldMonitorings; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
<?php | ||
|
||
namespace Civi\Notification\Entity; | ||
|
||
use \Civi\Api4\NotificationRule; | ||
|
||
class RuleEntity extends AbstractEntity { | ||
|
||
private ?array $fieldMonitorings = NULL; | ||
Check failure on line 9 in Civi/Notification/Entity/RuleEntity.php
|
||
private ?array $conditions = NULL; | ||
|
||
public function __get($name) { | ||
if (!property_exists($this, $name)) { | ||
throw new \Exception("Property {$name} does not exist."); | ||
} | ||
|
||
if (!isset($this->$name)) { | ||
switch ($name) { | ||
case 'fieldMonitorings': | ||
$this->$name = FieldMonitoringEntity::loadByRuleId($this->getEntityValue('id')); | ||
break; | ||
case 'conditions': | ||
$this->$name = ConditionEntity::loadByRuleId($this->getEntityValue('id')); | ||
break; | ||
} | ||
} | ||
|
||
return $this->$name; | ||
} | ||
|
||
public static function loadByRuleSetId(int $ruleSetId): array { | ||
$notificationRules = NotificationRule::get() | ||
->addWhere('rule_set_id', '=', $ruleSetId) | ||
->addWhere('is_active', '=', TRUE) | ||
->addOrderBy('weight') | ||
->execute(); | ||
|
||
// Convert results into array of RuleEntity objects | ||
$rules = []; | ||
foreach ($notificationRules as $rule) { | ||
$rules[] = new self($rule); | ||
} | ||
|
||
return $rules; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
<?php | ||
|
||
namespace Civi\Notification\Entity; | ||
|
||
use Civi\Api4\NotificationRuleSet; | ||
|
||
class RuleSetEntity extends AbstractEntity { | ||
|
||
private ?array $rules = NULL; | ||
|
||
/** | ||
* Magic method to load properties dynamically, ex. rules | ||
*/ | ||
public function __get($name) { | ||
if (!property_exists($this, $name)) { | ||
throw new \Exception("Property {$name} does not exist."); | ||
} | ||
|
||
if (!isset($this->$name)) { | ||
switch ($name) { | ||
case 'rules': | ||
$this->$name = RuleEntity::loadByRuleSetId($this->getEntityValue('id')); | ||
break; | ||
} | ||
} | ||
|
||
return $this->$name; | ||
} | ||
|
||
public static function loadByEntityType(string $entityType): array { | ||
$notificationRuleSets = NotificationRuleSet::get() | ||
->addSelect('*') | ||
->addWhere('monitored_entity_type', '=', $entityType) | ||
->addWhere('is_active', '=', 1) | ||
->execute(); | ||
|
||
$ruleSets = []; | ||
|
||
foreach ($notificationRuleSets as $ruleSet) { | ||
$ruleSets[] = new self($ruleSet); | ||
} | ||
|
||
return $ruleSets; | ||
} | ||
|
||
public static function hasActiveRuleSets(string $entityType): bool { | ||
$result = NotificationRuleSet::get() | ||
->selectRowCount() | ||
->addWhere('monitored_entity_type', '=', $entityType) | ||
->addWhere('is_active', '=', TRUE) | ||
->execute(); | ||
|
||
return $result->count() > 0; | ||
} | ||
|
||
public function isExecuteOnlyFirstRule() { | ||
return $this->getEntityValue('is_execute_only_first_rule'); | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
<?php | ||
|
||
namespace Civi\Notification\EventSubscriber; | ||
|
||
use Civi\API\Request; | ||
use Civi\Core\Event\PreEvent; | ||
use Civi\Core\Event\PostEvent; | ||
use Civi\Notification\Entity\RuleSetEntity; | ||
use Civi\Notification\Handlers\RuleSetHandler; | ||
use Symfony\Component\EventDispatcher\EventSubscriberInterface; | ||
|
||
final class NotificationSubscriber implements EventSubscriberInterface { | ||
|
||
/** | ||
* Store the entity's old state in a cache | ||
* | ||
* @var array | ||
*/ | ||
private static array $entityCache = []; | ||
|
||
public static function getSubscribedEvents(): array { | ||
return [ | ||
'hook_civicrm_pre' => 'onPre', | ||
'hook_civicrm_postCommit' => 'onPostCommit', | ||
'hook_civicrm_postProcess' => 'postProcess', | ||
]; | ||
} | ||
|
||
public function postProcess() { | ||
// This is temporary for debugging. | ||
// die; | ||
} | ||
|
||
public function onPre(PreEvent $event): void { | ||
[ , $entity, $id, ] = $event->getHookValues(); | ||
|
||
if (RuleSetEntity::hasActiveRuleSets($entity)) { | ||
// Capture old values before the change | ||
$oldValues = $this->loadEntityValues($entity, $id); | ||
|
||
// Store the entity's old state in a cache | ||
self::$entityCache[$entity][$id] = $oldValues; | ||
} | ||
} | ||
|
||
public function onPostCommit(PostEvent $event): void { | ||
[ , $entity, $id, , ] = $event->getHookValues(); | ||
|
||
// Check if old values exist for this entity in the cache | ||
if (isset(self::$entityCache[$entity][$id])) { | ||
$oldValues = self::$entityCache[$entity][$id]; | ||
// Retrieve new values after the change | ||
$newValues = $this->loadEntityValues($entity, $id); | ||
|
||
$ruleSets = RuleSetEntity::loadByEntityType($entity); | ||
$ruleSetHandler = new RuleSetHandler(); | ||
|
||
foreach ($ruleSets as $ruleSet) { | ||
// Evaluate relevant rule sets | ||
$ruleSetHandler->evaluateRuleSet($ruleSet, $newValues, $oldValues); | ||
} | ||
|
||
// Clean up cache after processing | ||
unset(self::$entityCache[$entity][$id]); | ||
} | ||
} | ||
|
||
private function loadEntityValues(string $entityType, int $entityID): array { | ||
// Load entity data with API v4 | ||
$result = Request::create($entityType, 'get', [ | ||
'version' => 4, | ||
'where' => [['id', '=', $entityID]] | ||
]) | ||
->execute(); | ||
|
||
return $result->first() ?? []; | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
<?php | ||
|
||
namespace Civi\Notification\Handlers; | ||
|
||
use Civi\Notification\Entity\ConditionEntity; | ||
|
||
class ConditionHandler { | ||
|
||
public function evaluateCondition(ConditionEntity $condition, array $changeSet): bool { | ||
$operator = $condition->getEntityValue('operator'); | ||
$fieldName = $condition->getEntityValue('field_name'); | ||
$conditionValue = $condition->getEntityValue('value'); | ||
$changeSetValue = $changeSet[$fieldName] ?? NULL; // New value | ||
|
||
// Use the comparison method to evaluate condition | ||
return $this->compareValues($conditionValue, $changeSetValue, $operator); | ||
} | ||
|
||
// TODO: Move the method to avoid duplication | ||
private function compareValues($value1, $value2, string $operator): bool { | ||
switch ($operator) { | ||
case '=': | ||
return $value1 == $value2; | ||
case 'IN': | ||
return in_array($value2, $value1); | ||
default: | ||
throw new \InvalidArgumentException("Unsupported operator: $operator"); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
<?php | ||
|
||
namespace Civi\Notification\Handlers; | ||
|
||
use Civi\Notification\Entity\FieldMonitoringEntity; | ||
|
||
class FieldMonitoringHandler { | ||
|
||
public function evaluate(FieldMonitoringEntity $fieldMonitoring, array $oldValues, array $changeSet): bool { | ||
$operatorBefore = $fieldMonitoring->getEntityValue('operator_before'); | ||
$operatorAfter = $fieldMonitoring->getEntityValue('operator_after'); | ||
$conditionBeforeValue = $fieldMonitoring->getEntityValue('value_before'); | ||
$conditionAfterValue = $fieldMonitoring->getEntityValue('value_after'); | ||
$fieldName = $fieldMonitoring->getEntityValue('field_name'); | ||
$oldValue = $oldValues[$fieldName] ?? NULL; // Old value | ||
$newValue = $changeSet[$fieldName] ?? NULL; // New value | ||
|
||
// Use the comparison method to evaluate condition | ||
return | ||
$this->compareValues($conditionBeforeValue, $oldValue, $operatorBefore) | ||
&& $this->compareValues($conditionAfterValue, $newValue, $operatorAfter); | ||
} | ||
|
||
// TODO: Move the method to avoid duplication | ||
private function compareValues($value1, $value2, string $operator): bool { | ||
switch ($operator) { | ||
case '=': | ||
return $value1 == $value2; | ||
case 'IN': | ||
return in_array($value2, $value1); | ||
default: | ||
throw new \InvalidArgumentException("Unsupported operator: $operator"); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
<?php | ||
|
||
namespace Civi\Notification\Handlers; | ||
|
||
use Civi\Notification\Entity\RuleEntity; | ||
|
||
class RuleHandler { | ||
|
||
public function evaluateRule(RuleEntity $rule, array $oldValues, array $changeSet): bool { | ||
// Evaluate additional conditions | ||
foreach ($rule->conditions as $condition) { | ||
$conditionHandler = new ConditionHandler(); | ||
|
||
// Evaluate condition logic | ||
if (!$conditionHandler->evaluateCondition($condition, $changeSet)) { | ||
return FALSE; | ||
} | ||
} | ||
|
||
// Evaluate field monitoring rules | ||
foreach ($rule->fieldMonitorings as $fieldMonitoring) { | ||
$fieldMonitorHandler = new FieldMonitoringHandler(); | ||
// Evaluate field monitoring logic | ||
if (!$fieldMonitorHandler->evaluate($fieldMonitoring, $oldValues, $changeSet)) { | ||
return FALSE; | ||
} | ||
} | ||
|
||
// TODO: add logic for sending notification | ||
|
||
return TRUE; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
<?php | ||
|
||
namespace Civi\Notification\Handlers; | ||
|
||
use Civi\Notification\Entity\RuleSetEntity; | ||
|
||
class RuleSetHandler { | ||
|
||
public function evaluateRuleSet(RuleSetEntity $ruleSet, array $entityValues, array $changeSet): void { | ||
foreach ($ruleSet->rules as $rule) { | ||
// TODO: Add logic to check if rule set entity conditions are met (source_entity_type, source_entity_id) | ||
|
||
$ruleHandler = new RuleHandler(); | ||
|
||
if ($ruleHandler->evaluateRule($rule, $entityValues, $changeSet)) { | ||
if ($ruleSet->isExecuteOnlyFirstRule()) { | ||
break; | ||
} | ||
} | ||
} | ||
} | ||
} |