CRM-15097 - CaseType - Compute & enforce pseudo-properties, isForkable & isForked
authorTim Otten <totten@civicrm.org>
Tue, 12 Aug 2014 07:20:34 +0000 (00:20 -0700)
committerTim Otten <totten@civicrm.org>
Tue, 12 Aug 2014 08:17:19 +0000 (01:17 -0700)
CRM/Case/BAO/CaseType.php
CRM/Case/XMLRepository.php
api/v3/CaseType.php
tests/phpunit/CRM/Case/BAO/CaseTest.php
tests/phpunit/CRM/Case/BAO/CaseTypeForkTest.php [new file with mode: 0644]
tests/phpunit/CRM/Case/BAO/ForkableCaseType.xml [new file with mode: 0644]
tests/phpunit/CRM/Case/BAO/UnforkableCaseType.xml [new file with mode: 0644]
tests/phpunit/CiviTest/CiviCaseTestCase.php

index 44e2169e96334521a7658621898356db9407a014..e101a4913d4024153a416c52a15b0da07db54f54 100644 (file)
@@ -76,9 +76,11 @@ class CRM_Case_BAO_CaseType extends CRM_Case_DAO_CaseType {
       throw new CRM_Core_Exception("Cannot create new case-type with malformed name [{$params['name']}]");
     }
 
+    $caseTypeName = (isset($params['name'])) ? $params['name'] : CRM_Core_DAO::getFieldValue('CRM_Case_DAO_CaseType', $params['id'], 'name', 'id', TRUE);
+
     // function to format definition column
     if (isset($params['definition']) && is_array($params['definition'])) {
-      $params['definition'] = self::convertDefinitionToXML($params['name'], $params['definition']);
+      $params['definition'] = self::convertDefinitionToXML($caseTypeName, $params['definition']);
       CRM_Core_ManagedEntities::scheduleReconcilation();
     }
 
@@ -340,4 +342,38 @@ class CRM_Case_BAO_CaseType extends CRM_Case_DAO_CaseType {
   static function isValidName($caseType) {
     return preg_match('/^[a-zA-Z0-9_]+$/',  $caseType);
   }
+
+  /**
+   * Determine if the case-type has *both* DB and file-based definitions.
+   *
+   * @param int $caseTypeId
+   * @return bool|null TRUE if there are *both* DB and file-based definitions
+   */
+  static function isForked($caseTypeId) {
+    $caseTypeName = CRM_Core_DAO::getFieldValue('CRM_Case_DAO_CaseType', $caseTypeId, 'name', 'id', TRUE);
+    if ($caseTypeName) {
+      $dbDefinition = CRM_Core_DAO::getFieldValue('CRM_Case_DAO_CaseType', $caseTypeId, 'definition', 'id', TRUE);
+      $fileDefinition = CRM_Case_XMLRepository::singleton()->retrieveFile($caseTypeName);
+      return $fileDefinition && $dbDefinition;
+    }
+    return NULL;
+  }
+
+  /**
+   * Determine if modifications are allowed on the case-type
+   *
+   * @param int $caseTypeId
+   * @return bool TRUE if the definition can be modified
+   */
+  static function isForkable($caseTypeId) {
+    $caseTypeName = CRM_Core_DAO::getFieldValue('CRM_Case_DAO_CaseType', $caseTypeId, 'name', 'id', TRUE);
+    if ($caseTypeName) {
+      // if file-based definition explicitly disables "forkable" option, then don't allow changes to definition
+      $fileDefinition = CRM_Case_XMLRepository::singleton()->retrieveFile($caseTypeName);
+      if ($fileDefinition && isset($fileDefinition->forkable) && $fileDefinition->forkable == 0) {
+        return FALSE;
+      }
+    }
+    return TRUE;
+  }
 }
index 8560e022c6c0730e25637df6e38973e186181507..067af0bd5e4c95e7af157fcf41569c6137173cd5 100644 (file)
@@ -91,30 +91,44 @@ class CRM_Case_XMLRepository {
     //}
 
     if (!CRM_Utils_Array::value($caseType, $this->xml)) {
-      $fileName = NULL;
-
-      if (CRM_Case_BAO_CaseType::isValidName($caseType)) {
-        // Search for a file based directly on the $caseType name
-        $fileName = $this->findXmlFile($caseType);
+      $fileXml = $this->retrieveFile($caseType);
+      if ($fileXml) {
+        $this->xml[$caseType] = $fileXml;
+      } else {
+        return FALSE;
       }
+    }
+    return $this->xml[$caseType];
+  }
 
-      // For backward compatibility, also search for double-munged file names
-      // TODO In 4.6 or 5.0, remove support for loading double-munged file names
-      if (!$fileName || !file_exists($fileName)) {
-        $fileName = $this->findXmlFile(CRM_Case_XMLProcessor::mungeCaseType($caseType));
-      }
+  /**
+   * @param string $caseType
+   * @return SimpleXMLElement|FALSE
+   */
+  public function retrieveFile($caseType) {
+    $fileName = NULL;
+    $fileXml = NULL;
 
-      if (!$fileName || !file_exists($fileName)) {
-        return FALSE;
-      }
+    if (CRM_Case_BAO_CaseType::isValidName($caseType)) {
+      // Search for a file based directly on the $caseType name
+      $fileName = $this->findXmlFile($caseType);
+    }
 
+    // For backward compatibility, also search for double-munged file names
+    // TODO In 4.6 or 5.0, remove support for loading double-munged file names
+    if (!$fileName || !file_exists($fileName)) {
+      $fileName = $this->findXmlFile(CRM_Case_XMLProcessor::mungeCaseType($caseType));
+    }
+
+    if ($fileName && file_exists($fileName)) {
       // read xml file
       $dom = new DomDocument();
       $dom->load($fileName);
       $dom->xinclude();
-      $this->xml[$caseType] = simplexml_import_dom($dom);
+      $fileXml = simplexml_import_dom($dom);
     }
-    return $this->xml[$caseType];
+
+    return $fileXml;
   }
 
   /**
index d046c5a20cf4f5245a8f979c591cec84dd4bea26..654f20ce1a715ed371672124fbedd9f3a6d1acea 100644 (file)
  */
 function civicrm_api3_case_type_create($params) {
   civicrm_api3_verify_mandatory($params, _civicrm_api3_get_DAO(__FUNCTION__));
+  unset($params['is_forkable']); // computed property
+  unset($params['is_forked']); // computed property
 
   if (!array_key_exists('is_active', $params) && empty($params['id'])) {
     $params['is_active'] = TRUE;
   }
-  return _civicrm_api3_basic_create(_civicrm_api3_get_BAO(__FUNCTION__), $params, 'CaseType');
-
+  if (!empty($params['id']) // this is an existing case-type
+    && !CRM_Case_BAO_CaseType::isForked($params['id']) // which is not yet forked
+    && !CRM_Case_BAO_CaseType::isForkable($params['id']) // for which new forks are prohibited
+  ) {
+    unset($params['definition']);
+  }
+  $result = _civicrm_api3_basic_create(_civicrm_api3_get_BAO(__FUNCTION__), $params, 'CaseType');
+  return _civicrm_api3_case_type_get_formatResult($result);
 }
 
 /**
@@ -83,12 +91,15 @@ function civicrm_api3_case_type_get($params) {
  */
 function _civicrm_api3_case_type_get_formatResult(&$result) {
   foreach ($result['values'] as $key => $caseType) {
-    $xml = CRM_Case_XMLRepository::singleton()->retrieve($caseType['name']);
+    $caseTypeName = (isset($caseType['name'])) ? $caseType['name'] : CRM_Core_DAO::getFieldValue('CRM_Case_DAO_CaseType', $caseType['id'], 'name', 'id', TRUE);
+    $xml = CRM_Case_XMLRepository::singleton()->retrieve($caseTypeName);
     if ($xml) {
       $result['values'][$key]['definition'] = CRM_Case_BAO_CaseType::convertXmlToDefinition($xml);
     } else {
       $result['values'][$key]['definition'] = array();
     }
+    $result['values'][$key]['is_forkable'] = CRM_Case_BAO_CaseType::isForkable($result['values'][$key]['id']);
+    $result['values'][$key]['is_forked'] = CRM_Case_BAO_CaseType::isForked($result['values'][$key]['id']);
   }
   return $result;
 }
index a76ac9e2443bb57e72141c64d550517ece844f91..bb7c03d165008857dea4fdf96fe9c91040294d51 100644 (file)
@@ -19,11 +19,33 @@ class CRM_Case_BAO_CaseTest extends CiviUnitTestCase {
   function setUp() {
     parent::setUp();
 
+    $this->tablesToTruncate = array(
+      'civicrm_activity',
+      'civicrm_contact',
+      'civicrm_custom_group',
+      'civicrm_custom_field',
+      'civicrm_case',
+      'civicrm_case_contact',
+      'civicrm_case_activity',
+      'civicrm_case_type',
+      'civicrm_activity_contact',
+      'civicrm_managed',
+      'civicrm_relationship',
+      'civicrm_relationship_type',
+    );
+
+    $this->quickCleanup($this->tablesToTruncate);
+
     $this->loadAllFixtures();
 
     CRM_Core_BAO_ConfigSetting::enableComponent('CiviCase');
   }
 
+  protected function tearDown() {
+    parent::tearDown();
+    $this->quickCleanup($this->tablesToTruncate, TRUE);
+  }
+
   function testAddCaseToContact() {
     $params = array(
       'case_id' => 1,
diff --git a/tests/phpunit/CRM/Case/BAO/CaseTypeForkTest.php b/tests/phpunit/CRM/Case/BAO/CaseTypeForkTest.php
new file mode 100644 (file)
index 0000000..170a428
--- /dev/null
@@ -0,0 +1,99 @@
+<?php
+
+require_once 'CiviTest/CiviCaseTestCase.php';
+
+/**
+ * Case Types support an optional forking mechanism wherein the local admin
+ * creates a custom DB-based definition that deviates from the file-based definition.
+ */
+class CRM_Case_BAO_CaseTypeForkTest extends CiviCaseTestCase {
+  public function setUp() {
+    parent::setUp();
+    CRM_Core_ManagedEntities::singleton(TRUE)->reconcile();
+  }
+
+  function tearDown() {
+    parent::tearDown();
+    CRM_Core_ManagedEntities::singleton(TRUE)->reconcile();
+  }
+
+
+  /**
+   * Edit the definition of ForkableCaseType
+   */
+  function testForkable() {
+    $caseTypeId = CRM_Core_DAO::getFieldValue('CRM_Case_DAO_CaseType', 'ForkableCaseType', 'id', 'name');
+    $this->assertTrue(is_numeric($caseTypeId) && $caseTypeId > 0);
+
+    $this->assertDBNull('CRM_Case_BAO_CaseType', $caseTypeId, 'definition', 'id', "Should not have DB-based definition");
+    $this->assertTrue(CRM_Case_BAO_CaseType::isForkable($caseTypeId));
+    $this->assertFalse(CRM_Case_BAO_CaseType::isForked($caseTypeId));
+
+    $this->callAPISuccess('CaseType', 'create', array(
+      'id' => $caseTypeId,
+      'definition' => array(
+        'activityTypes' => array(
+          array('name' => 'First act'),
+          array('name' => 'Second act'),
+        ),
+      )
+    ));
+
+    $this->assertTrue(CRM_Case_BAO_CaseType::isForkable($caseTypeId));
+    $this->assertTrue(CRM_Case_BAO_CaseType::isForked($caseTypeId));
+    $this->assertDBNotNull('CRM_Case_BAO_CaseType', $caseTypeId, 'definition', 'id', "Should not have DB-based definition");
+
+    $this->callAPISuccess('CaseType', 'create', array(
+      'id' => $caseTypeId,
+      'definition' => 'null',
+    ));
+
+    $this->assertDBNull('CRM_Case_BAO_CaseType', $caseTypeId, 'definition', 'id', "Should not have DB-based definition");
+    $this->assertTrue(CRM_Case_BAO_CaseType::isForkable($caseTypeId));
+    $this->assertFalse(CRM_Case_BAO_CaseType::isForked($caseTypeId));
+  }
+
+  /**
+   * Attempt to edit the definition of UnforkableCaseType. This fails.
+   */
+  function testUnforkable() {
+    $caseTypeId = CRM_Core_DAO::getFieldValue('CRM_Case_DAO_CaseType', 'UnforkableCaseType', 'id', 'name');
+    $this->assertTrue(is_numeric($caseTypeId) && $caseTypeId > 0);
+    $this->assertDBNull('CRM_Case_BAO_CaseType', $caseTypeId, 'definition', 'id', "Should not have DB-based definition");
+
+    $this->assertFalse(CRM_Case_BAO_CaseType::isForkable($caseTypeId));
+    $this->assertFalse(CRM_Case_BAO_CaseType::isForked($caseTypeId));
+
+    $this->callAPISuccess('CaseType', 'create', array(
+      'id' => $caseTypeId,
+      'definition' => array(
+        'activityTypes' => array(
+          array('name' => 'First act'),
+          array('name' => 'Second act'),
+        ),
+      )
+    ));
+
+    $this->assertFalse(CRM_Case_BAO_CaseType::isForkable($caseTypeId));
+    $this->assertFalse(CRM_Case_BAO_CaseType::isForked($caseTypeId));
+    $this->assertDBNull('CRM_Case_BAO_CaseType', $caseTypeId, 'definition', 'id', "Should not have DB-based definition");
+  }
+
+  /**
+   * @param $caseTypes
+   * @see \CRM_Utils_Hook::caseTypes
+   */
+  function hook_caseTypes(&$caseTypes) {
+    $caseTypes['ForkableCaseType'] = array(
+      'module' => 'civicrm',
+      'name' => 'ForkableCaseType',
+      'file' => __DIR__ . '/ForkableCaseType.xml',
+    );
+    $caseTypes['UnforkableCaseType'] = array(
+      'module' => 'civicrm',
+      'name' => 'UnforkableCaseType',
+      'file' => __DIR__ . '/UnforkableCaseType.xml',
+    );
+  }
+
+}
\ No newline at end of file
diff --git a/tests/phpunit/CRM/Case/BAO/ForkableCaseType.xml b/tests/phpunit/CRM/Case/BAO/ForkableCaseType.xml
new file mode 100644 (file)
index 0000000..8a60080
--- /dev/null
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="iso-8859-1" ?>
+
+<CaseType>
+  <name>Forkable Case Type</name>
+  <ActivityTypes>
+    <ActivityType>
+      <name>Open Case</name>
+      <max_instances>1</max_instances>
+    </ActivityType>
+    <ActivityType>
+      <name>Change Case Type</name>
+    </ActivityType>
+    <ActivityType>
+      <name>Change Case Status</name>
+    </ActivityType>
+    <ActivityType>
+      <name>Change Case Start Date</name>
+    </ActivityType>
+    <ActivityType>
+      <name>Link Cases</name>
+    </ActivityType>
+  </ActivityTypes>
+  <ActivitySets>
+    <ActivitySet>
+      <name>standard_timeline</name>
+      <label>Standard Timeline</label>
+      <timeline>true</timeline>
+      <ActivityTypes>
+        <ActivityType>
+          <name>Open Case</name>
+          <status>Completed</status>
+        </ActivityType>
+      </ActivityTypes>
+    </ActivitySet>
+  </ActivitySets>
+  <CaseRoles>
+    <RelationshipType>
+        <name>Homeless Services Coordinator</name>
+        <creator>1</creator>
+        <manager>1</manager>
+    </RelationshipType>
+ </CaseRoles>
+</CaseType>
diff --git a/tests/phpunit/CRM/Case/BAO/UnforkableCaseType.xml b/tests/phpunit/CRM/Case/BAO/UnforkableCaseType.xml
new file mode 100644 (file)
index 0000000..9395ceb
--- /dev/null
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="iso-8859-1" ?>
+
+<CaseType>
+  <name>Unforkable Case Type</name>
+  <forkable>0</forkable>
+  <ActivityTypes>
+    <ActivityType>
+      <name>Open Case</name>
+      <max_instances>1</max_instances>
+    </ActivityType>
+    <ActivityType>
+      <name>Change Case Type</name>
+    </ActivityType>
+    <ActivityType>
+      <name>Change Case Status</name>
+    </ActivityType>
+    <ActivityType>
+      <name>Change Case Start Date</name>
+    </ActivityType>
+    <ActivityType>
+      <name>Link Cases</name>
+    </ActivityType>
+  </ActivityTypes>
+  <ActivitySets>
+    <ActivitySet>
+      <name>standard_timeline</name>
+      <label>Standard Timeline</label>
+      <timeline>true</timeline>
+      <ActivityTypes>
+        <ActivityType>
+          <name>Open Case</name>
+          <status>Completed</status>
+        </ActivityType>
+      </ActivityTypes>
+    </ActivitySet>
+  </ActivitySets>
+  <CaseRoles>
+    <RelationshipType>
+        <name>Homeless Services Coordinator</name>
+        <creator>1</creator>
+        <manager>1</manager>
+    </RelationshipType>
+ </CaseRoles>
+</CaseType>
index 9fe873a62a11f26903f30083eaff44f612d6e10c..07eb0bf6bd92c2ea9e1d8964124c62bb416d01b1 100644 (file)
@@ -52,6 +52,7 @@ class CiviCaseTestCase extends CiviUnitTestCase {
     $hooks = \CRM_Utils_Hook::singleton();
     $hooks->setHook('civicrm_caseTypes', array($this, 'hook_caseTypes'));
     \CRM_Case_XMLRepository::singleton(TRUE);
+    \CRM_Case_XMLProcessor::flushStaticCaches();
 
     // CRM-9404 - set-up is a bit cumbersome but had to put something in place to set up activity types & case types
     //. Using XML was causing breakage as id numbers were changing over time