From 708d8fa2a0e4e1a47549cb79980f0b0e1c157b97 Mon Sep 17 00:00:00 2001 From: Tim Otten Date: Mon, 26 May 2014 18:34:19 -0700 Subject: [PATCH] CRM-14727 - Fire case-change events which include Civi\CCase\Analyzer. Notes: * The key underlying event (hook_civicrm_post) is already fired, but you have to bind to it a couple different ways (eg listen for a change to an "Activity" and then check the "case_id"). * A key goal is to allow multiple event-listeners to use the same instance of $analyzer. * One can bind to the case-change event in one of three ways: (a) hooks, (b) event-dispatcher, (c) CiviCase XML listeners. --- CRM/Case/XMLProcessor/Process.php | 16 ++ CRM/Case/XMLRepository.php | 2 +- CRM/Utils/Hook.php | 14 ++ Civi/CCase/Analyzer.php | 204 ++++++++++++++++++ Civi/CCase/CaseChangeListener.php | 31 +++ Civi/CCase/Event/CaseChangeEvent.php | 43 ++++ Civi/CCase/Events.php | 70 ++++++ .../Exception/MultipleActivityException.php | 6 + Civi/Core/Container.php | 3 + 9 files changed, 388 insertions(+), 1 deletion(-) create mode 100644 Civi/CCase/Analyzer.php create mode 100644 Civi/CCase/CaseChangeListener.php create mode 100644 Civi/CCase/Event/CaseChangeEvent.php create mode 100644 Civi/CCase/Events.php create mode 100644 Civi/CCase/Exception/MultipleActivityException.php diff --git a/CRM/Case/XMLProcessor/Process.php b/CRM/Case/XMLProcessor/Process.php index c1d2f98ed7..4c4548cffb 100644 --- a/CRM/Case/XMLProcessor/Process.php +++ b/CRM/Case/XMLProcessor/Process.php @@ -618,6 +618,22 @@ AND a.is_deleted = 0 return $this->caseRoles($xml->CaseRoles, TRUE); } + /** + * @param string $caseType + * @return array<\Civi\CCase\CaseChangeListener> + */ + function getListeners($caseType) { + $xml = $this->retrieve($caseType); + $listeners = array(); + if ($xml->Listeners && $xml->Listeners->Listener) { + foreach ($xml->Listeners->Listener as $listenerXML) { + $class = (string) $listenerXML; + $listeners[] = new $class(); + } + } + return $listeners; + } + /** * @return int */ diff --git a/CRM/Case/XMLRepository.php b/CRM/Case/XMLRepository.php index 698393eb56..d23ffcd994 100644 --- a/CRM/Case/XMLRepository.php +++ b/CRM/Case/XMLRepository.php @@ -158,7 +158,7 @@ class CRM_Case_XMLRepository { } /** - * @return array symbolic names of case-types + * @return array symbolic names of case-types */ public function getAllCaseTypes() { if ($this->allCaseTypes === NULL) { diff --git a/CRM/Utils/Hook.php b/CRM/Utils/Hook.php index f1a431c6f0..26bdc2bdc6 100644 --- a/CRM/Utils/Hook.php +++ b/CRM/Utils/Hook.php @@ -1680,4 +1680,18 @@ abstract class CRM_Utils_Hook { ); } + /** + * This hook fires whenever a record in a case changes. + * + * @param \Civi\CCase\Analyzer $analyzer + */ + static function caseChange(\Civi\CCase\Analyzer $analyzer) { + $event = new \Civi\CCase\Event\CaseChangeEvent($analyzer); + \Civi\Core\Container::singleton()->get('dispatcher')->dispatch("hook_civicrm_caseChange", $event); + + return self::singleton()->invoke(1, $angularModules, + self::$_nullObject, self::$_nullObject, self::$_nullObject, self::$_nullObject, self::$_nullObject, + 'civicrm_caseChange' + ); + } } diff --git a/Civi/CCase/Analyzer.php b/Civi/CCase/Analyzer.php new file mode 100644 index 0000000000..b16cedcd9f --- /dev/null +++ b/Civi/CCase/Analyzer.php @@ -0,0 +1,204 @@ + + */ + private $indices; + + public function __construct($caseId) { + $this->caseId = $caseId; + $this->flush(); + } + + /** + * Determine if case includes an activity of given type/status + * + * @param string $type eg "Phone Call", "Interview Prospect", "Background Check" + * @param string $status eg "Scheduled", "Completed" + * @return bool + */ + public function hasActivity($type, $status = NULL) { + $idx = $this->getActivityIndex(array('activity_type_id', 'status_id')); + $activityTypeGroup = civicrm_api3('option_group', 'get', array('name' => 'activity_type')); + $activityType = array( + 'name' => $type, + 'option_group_id' => $activityTypeGroup['id'], + ); + $activityTypeID = civicrm_api3('option_value', 'get', $activityType); + $activityTypeID = $activityTypeID['values'][$activityTypeID['id']]['value']; + if ($status) { + $activityStatusGroup = civicrm_api3('option_group', 'get', array('name' => 'activity_status')); + $activityStatus = array( + 'name' => $status, + 'option_group_id' => $activityStatusGroup['id'] + ); + $activityStatusID = civicrm_api3('option_value', 'get', $activityStatus); + $activityStatusID = $activityStatusID['values'][$activityStatusID['id']]['value']; + } + if ($status === NULL) { + return !empty($idx[$activityTypeID]); + } + else { + return !empty($idx[$activityTypeID][$activityStatusID]); + } + } + + /** + * Get a list of all activities in the case + * + * @return array list of activity records (api/v3 format) + */ + public function getActivities() { + if ($this->activities === NULL) { + $result = civicrm_api3('Activity', 'get', array('case_id' => $this->caseId)); + $this->activities = $result['values']; + } + return $this->activities; + } + + /** + * Get a single activity record by type + * + * @param string $type + * @throws \Civi\CCase\Exception\MultipleActivityException + * @return array|NULL, activity record (api/v3) + */ + public function getSingleActivity($type) { + $idx = $this->getActivityIndex(array('activity_type_id', 'id')); + $actTypes = array_flip(\CRM_Core_PseudoConstant::activityType(TRUE, TRUE, FALSE, 'name')); + $typeId = $actTypes[$type]; + $count = isset($idx[$typeId]) ? count($idx[$typeId]) : 0; + + if ($count === 0) { + return NULL; + } + elseif ($count === 1) { + foreach ($idx[$typeId] as $item) { + return $item; + } + } + else { + throw new \Civi\CCase\Exception\MultipleActivityException("Wrong quantity of [$type] records. Expected 1 but found " . $count); + } + } + + /** + * @return int + */ + public function getCaseId() { + return $this->caseId; + } + + /** + * @return array, Case record (api/v3 format) + */ + public function getCase() { + if ($this->case === NULL) { + $this->case = civicrm_api3('case', 'getsingle', array('id' => $this->caseId)); + } + return $this->case; + } + + /** + * @return string + */ + public function getCaseType() { + if ($this->caseType === NULL) { + $case = $this->getCase(); + $caseTypes = \CRM_Case_XMLRepository::singleton()->getAllCaseTypes(); + if (!isset($caseTypes[$case['case_type_id']])) { + throw new \CRM_Core_Exception("Case does not have a recognized case-type!"); + } + $this->caseType = $caseTypes[$case['case_type_id']]; + } + return $this->caseType; + } + + /** + * Get a list of all activities in the case (indexed by some property/properties) + * + * @param array $keys list of properties by which to index activities + * @return array list of activity records (api/v3 format), indexed by $keys + */ + public function getActivityIndex($keys) { + $key = implode(";", $keys); + if (!isset($this->indices[$key])) { + $this->indices[$key] = \CRM_Utils_Array::index($keys, $this->getActivities()); + } + return $this->indices[$key]; + } + + /** + * @return SimpleXMLElement|NULL + */ + public function getXml() { + if ($this->xml === NULL) { + $this->xml = \CRM_Case_XMLRepository::singleton()->retrieve($this->getCaseType()); + } + return $this->xml; + } + + /** + * Flush any cached information + * + * @return void + */ + public function flush() { + $this->case = NULL; + $this->caseType = NULL; + $this->activities = NULL; + $this->indices = array(); + } +} \ No newline at end of file diff --git a/Civi/CCase/CaseChangeListener.php b/Civi/CCase/CaseChangeListener.php new file mode 100644 index 0000000000..6e8764ba52 --- /dev/null +++ b/Civi/CCase/CaseChangeListener.php @@ -0,0 +1,31 @@ +analyzer = $analyzer; + } +} diff --git a/Civi/CCase/Events.php b/Civi/CCase/Events.php new file mode 100644 index 0000000000..f1556c490f --- /dev/null +++ b/Civi/CCase/Events.php @@ -0,0 +1,70 @@ +entity) { + case 'Activity': + if ($event->object->case_id) { + $caseId = $event->object->case_id; + } + break; + case 'Case': + $caseId = $event->id; + break; + default: + throw new \CRM_Core_Exception("CRM_Case_Listener does not support entity {$event->entity}"); + } + + if ($caseId) { + $analyzer = new \Civi\CCase\Analyzer($caseId); + \CRM_Utils_Hook::caseChange($analyzer); + } + } + + /** + * Find any extra listeners declared in XML and pass the event along to them + * + * @param Event\CaseChangeEvent $event + */ + public static function delegateToXmlListeners(\Civi\CCase\Event\CaseChangeEvent $event) { + $p = new \CRM_Case_XMLProcessor_Process(); + $listeners = $p->getListeners($event->analyzer->getCaseType()); + foreach ($listeners as $listener) { + /** @var $listener \Civi\CCase\CaseChangeListener */ + $listener->onCaseChange($event); + } + } +} \ No newline at end of file diff --git a/Civi/CCase/Exception/MultipleActivityException.php b/Civi/CCase/Exception/MultipleActivityException.php new file mode 100644 index 0000000000..d4dc4b5240 --- /dev/null +++ b/Civi/CCase/Exception/MultipleActivityException.php @@ -0,0 +1,6 @@ +addListener('hook_civicrm_post::Activity', array('\Civi\CCase\Events', 'fireCaseChange')); + $dispatcher->addListener('hook_civicrm_post::Case', array('\Civi\CCase\Events', 'fireCaseChange')); + $dispatcher->addListener('hook_civicrm_caseChange', array('\Civi\CCase\Events', 'delegateToXmlListeners')); return $dispatcher; } -- 2.25.1