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();
}
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;
+ }
}
//}
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;
}
/**
*/
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);
}
/**
*/
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;
}
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,
--- /dev/null
+<?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
--- /dev/null
+<?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>
--- /dev/null
+<?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>
$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