From: Tim Otten Date: Tue, 12 Aug 2014 07:20:34 +0000 (-0700) Subject: CRM-15097 - CaseType - Compute & enforce pseudo-properties, isForkable & isForked X-Git-Url: https://vcs.fsf.org/?a=commitdiff_plain;h=32f1c9179023804032667560a0dad300d03fec2b;p=civicrm-core.git CRM-15097 - CaseType - Compute & enforce pseudo-properties, isForkable & isForked --- diff --git a/CRM/Case/BAO/CaseType.php b/CRM/Case/BAO/CaseType.php index 44e2169e96..e101a4913d 100644 --- a/CRM/Case/BAO/CaseType.php +++ b/CRM/Case/BAO/CaseType.php @@ -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; + } } diff --git a/CRM/Case/XMLRepository.php b/CRM/Case/XMLRepository.php index 8560e022c6..067af0bd5e 100644 --- a/CRM/Case/XMLRepository.php +++ b/CRM/Case/XMLRepository.php @@ -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; } /** diff --git a/api/v3/CaseType.php b/api/v3/CaseType.php index d046c5a20c..654f20ce1a 100644 --- a/api/v3/CaseType.php +++ b/api/v3/CaseType.php @@ -51,12 +51,20 @@ */ 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; } diff --git a/tests/phpunit/CRM/Case/BAO/CaseTest.php b/tests/phpunit/CRM/Case/BAO/CaseTest.php index a76ac9e244..bb7c03d165 100644 --- a/tests/phpunit/CRM/Case/BAO/CaseTest.php +++ b/tests/phpunit/CRM/Case/BAO/CaseTest.php @@ -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 index 0000000000..170a4285dd --- /dev/null +++ b/tests/phpunit/CRM/Case/BAO/CaseTypeForkTest.php @@ -0,0 +1,99 @@ +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 index 0000000000..8a600805fe --- /dev/null +++ b/tests/phpunit/CRM/Case/BAO/ForkableCaseType.xml @@ -0,0 +1,43 @@ + + + + Forkable Case Type + + + Open Case + 1 + + + Change Case Type + + + Change Case Status + + + Change Case Start Date + + + Link Cases + + + + + standard_timeline + + true + + + Open Case + Completed + + + + + + + Homeless Services Coordinator + 1 + 1 + + + diff --git a/tests/phpunit/CRM/Case/BAO/UnforkableCaseType.xml b/tests/phpunit/CRM/Case/BAO/UnforkableCaseType.xml new file mode 100644 index 0000000000..9395ceb1e1 --- /dev/null +++ b/tests/phpunit/CRM/Case/BAO/UnforkableCaseType.xml @@ -0,0 +1,44 @@ + + + + Unforkable Case Type + 0 + + + Open Case + 1 + + + Change Case Type + + + Change Case Status + + + Change Case Start Date + + + Link Cases + + + + + standard_timeline + + true + + + Open Case + Completed + + + + + + + Homeless Services Coordinator + 1 + 1 + + + diff --git a/tests/phpunit/CiviTest/CiviCaseTestCase.php b/tests/phpunit/CiviTest/CiviCaseTestCase.php index 9fe873a62a..07eb0bf6bd 100644 --- a/tests/phpunit/CiviTest/CiviCaseTestCase.php +++ b/tests/phpunit/CiviTest/CiviCaseTestCase.php @@ -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