CRM-14727 - Fire case-change events which include Civi\CCase\Analyzer.
authorTim Otten <totten@civicrm.org>
Tue, 27 May 2014 01:34:19 +0000 (18:34 -0700)
committerTim Otten <totten@civicrm.org>
Wed, 28 May 2014 04:00:11 +0000 (21:00 -0700)
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
CRM/Case/XMLRepository.php
CRM/Utils/Hook.php
Civi/CCase/Analyzer.php [new file with mode: 0644]
Civi/CCase/CaseChangeListener.php [new file with mode: 0644]
Civi/CCase/Event/CaseChangeEvent.php [new file with mode: 0644]
Civi/CCase/Events.php [new file with mode: 0644]
Civi/CCase/Exception/MultipleActivityException.php [new file with mode: 0644]
Civi/Core/Container.php

index c1d2f98ed773d49630be024ed39957b038f77edb..4c4548cffb081390e8b97297eebec6fc6c38ac50 100644 (file)
@@ -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
    */
index 698393eb569bed467b366c9a62f7eb0ca742369c..d23ffcd994e0517073f46bc6c9438771e57da512 100644 (file)
@@ -158,7 +158,7 @@ class CRM_Case_XMLRepository {
   }
 
   /**
-   * @return array<string> symbolic names of case-types
+   * @return array<int,string> symbolic names of case-types
    */
   public function getAllCaseTypes() {
     if ($this->allCaseTypes === NULL) {
index f1a431c6f05ace032e3e04efec2a38249497b798..26bdc2bdc6f6f61cec2b1b461d1915c8ff1c1c93 100644 (file)
@@ -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 (file)
index 0000000..b16cedc
--- /dev/null
@@ -0,0 +1,204 @@
+<?php
+/*
+ +--------------------------------------------------------------------+
+ | CiviCRM version 4.5                                                |
+ +--------------------------------------------------------------------+
+ | Copyright CiviCRM LLC (c) 2004-2014                                |
+ +--------------------------------------------------------------------+
+ | This file is a part of CiviCRM.                                    |
+ |                                                                    |
+ | CiviCRM is free software; you can copy, modify, and distribute it  |
+ | under the terms of the GNU Affero General Public License           |
+ | Version 3, 19 November 2007 and the CiviCRM Licensing Exception.   |
+ |                                                                    |
+ | CiviCRM is distributed in the hope that it will be useful, but     |
+ | WITHOUT ANY WARRANTY; without even the implied warranty of         |
+ | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.               |
+ | See the GNU Affero General Public License for more details.        |
+ |                                                                    |
+ | You should have received a copy of the GNU Affero General Public   |
+ | License and the CiviCRM Licensing Exception along                  |
+ | with this program; if not, contact CiviCRM LLC                     |
+ | at info[AT]civicrm[DOT]org. If you have questions about the        |
+ | GNU Affero General Public License or the licensing of CiviCRM,     |
+ | see the CiviCRM license FAQ at http://civicrm.org/licensing        |
+ +--------------------------------------------------------------------+
+*/
+namespace Civi\CCase;
+
+class Analyzer {
+  /**
+   * @var int
+   */
+  private $caseId;
+
+  /**
+   * @var array per APIv3
+   */
+  private $case;
+
+  /**
+   * @var string
+   */
+  private $caseType;
+
+  /**
+   * @var array per APIv3
+   */
+  private $activities;
+
+  /**
+   * @var \SimpleXMLElement
+   */
+  private $xml;
+
+  /**
+   * @var array<string,array>
+   */
+  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 (file)
index 0000000..6e8764b
--- /dev/null
@@ -0,0 +1,31 @@
+<?php
+/*
+ +--------------------------------------------------------------------+
+ | CiviCRM version 4.5                                                |
+ +--------------------------------------------------------------------+
+ | Copyright CiviCRM LLC (c) 2004-2014                                |
+ +--------------------------------------------------------------------+
+ | This file is a part of CiviCRM.                                    |
+ |                                                                    |
+ | CiviCRM is free software; you can copy, modify, and distribute it  |
+ | under the terms of the GNU Affero General Public License           |
+ | Version 3, 19 November 2007 and the CiviCRM Licensing Exception.   |
+ |                                                                    |
+ | CiviCRM is distributed in the hope that it will be useful, but     |
+ | WITHOUT ANY WARRANTY; without even the implied warranty of         |
+ | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.               |
+ | See the GNU Affero General Public License for more details.        |
+ |                                                                    |
+ | You should have received a copy of the GNU Affero General Public   |
+ | License and the CiviCRM Licensing Exception along                  |
+ | with this program; if not, contact CiviCRM LLC                     |
+ | at info[AT]civicrm[DOT]org. If you have questions about the        |
+ | GNU Affero General Public License or the licensing of CiviCRM,     |
+ | see the CiviCRM license FAQ at http://civicrm.org/licensing        |
+ +--------------------------------------------------------------------+
+*/
+namespace Civi\CCase;
+
+interface CaseChangeListener {
+  function onCaseChange(\Civi\CCase\Event\CaseChangeEvent $event);
+}
\ No newline at end of file
diff --git a/Civi/CCase/Event/CaseChangeEvent.php b/Civi/CCase/Event/CaseChangeEvent.php
new file mode 100644 (file)
index 0000000..a4245cc
--- /dev/null
@@ -0,0 +1,43 @@
+<?php
+/*
+ +--------------------------------------------------------------------+
+ | CiviCRM version 4.5                                                |
+ +--------------------------------------------------------------------+
+ | Copyright CiviCRM LLC (c) 2004-2014                                |
+ +--------------------------------------------------------------------+
+ | This file is a part of CiviCRM.                                    |
+ |                                                                    |
+ | CiviCRM is free software; you can copy, modify, and distribute it  |
+ | under the terms of the GNU Affero General Public License           |
+ | Version 3, 19 November 2007 and the CiviCRM Licensing Exception.   |
+ |                                                                    |
+ | CiviCRM is distributed in the hope that it will be useful, but     |
+ | WITHOUT ANY WARRANTY; without even the implied warranty of         |
+ | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.               |
+ | See the GNU Affero General Public License for more details.        |
+ |                                                                    |
+ | You should have received a copy of the GNU Affero General Public   |
+ | License and the CiviCRM Licensing Exception along                  |
+ | with this program; if not, contact CiviCRM LLC                     |
+ | at info[AT]civicrm[DOT]org. If you have questions about the        |
+ | GNU Affero General Public License or the licensing of CiviCRM,     |
+ | see the CiviCRM license FAQ at http://civicrm.org/licensing        |
+ +--------------------------------------------------------------------+
+*/
+
+namespace Civi\CCase\Event;
+
+/**
+ * Class CaseChangeEvent
+ * @package Civi\API\Event
+ */
+class CaseChangeEvent extends \Symfony\Component\EventDispatcher\Event {
+  /**
+   * @var \Civi\CCase\Analyzer
+   */
+  public $analyzer;
+
+  function __construct($analyzer) {
+    $this->analyzer = $analyzer;
+  }
+}
diff --git a/Civi/CCase/Events.php b/Civi/CCase/Events.php
new file mode 100644 (file)
index 0000000..f1556c4
--- /dev/null
@@ -0,0 +1,70 @@
+<?php
+/*
+ +--------------------------------------------------------------------+
+ | CiviCRM version 4.5                                                |
+ +--------------------------------------------------------------------+
+ | Copyright CiviCRM LLC (c) 2004-2014                                |
+ +--------------------------------------------------------------------+
+ | This file is a part of CiviCRM.                                    |
+ |                                                                    |
+ | CiviCRM is free software; you can copy, modify, and distribute it  |
+ | under the terms of the GNU Affero General Public License           |
+ | Version 3, 19 November 2007 and the CiviCRM Licensing Exception.   |
+ |                                                                    |
+ | CiviCRM is distributed in the hope that it will be useful, but     |
+ | WITHOUT ANY WARRANTY; without even the implied warranty of         |
+ | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.               |
+ | See the GNU Affero General Public License for more details.        |
+ |                                                                    |
+ | You should have received a copy of the GNU Affero General Public   |
+ | License and the CiviCRM Licensing Exception along                  |
+ | with this program; if not, contact CiviCRM LLC                     |
+ | at info[AT]civicrm[DOT]org. If you have questions about the        |
+ | GNU Affero General Public License or the licensing of CiviCRM,     |
+ | see the CiviCRM license FAQ at http://civicrm.org/licensing        |
+ +--------------------------------------------------------------------+
+*/
+namespace Civi\CCase;
+
+class Events {
+  /**
+   * Following a change to an activity or case, fire the case-change event.
+   *
+   * @param \Civi\Core\Event\PostEvent $event
+   * @throws \CRM_Core_Exception
+   */
+  public static function fireCaseChange(\Civi\Core\Event\PostEvent $event) {
+    $caseId = NULL;
+    switch ($event->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 (file)
index 0000000..d4dc4b5
--- /dev/null
@@ -0,0 +1,6 @@
+<?php
+namespace Civi\CCase\Exception;
+
+class MultipleActivityException extends \CRM_Core_Exception {
+
+}
\ No newline at end of file
index f70369ad150eb5a373ef5c162ea39e32e720bd70..3c4c846e23ae33c94a01c7a8b9d563314deec9dc 100644 (file)
@@ -89,6 +89,9 @@ class Container {
    */
   public function createEventDispatcher() {
     $dispatcher = new \Symfony\Component\EventDispatcher\EventDispatcher();
+    $dispatcher->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;
   }