From 96f09dda89038691c80d67e8a9c4fc13bb6c36d7 Mon Sep 17 00:00:00 2001 From: Coleman Watts Date: Thu, 1 Apr 2021 18:41:23 -0400 Subject: [PATCH] Add APIv4 CaseType entity The `definition` field requires special formatting; so this change allows api fields to specify `output_formatters`, which are per-field callback functions for altering the output of a `get` request. --- CRM/Case/BAO/CaseType.php | 42 ++++++++---- CRM/Case/DAO/CaseType.php | 7 +- .../Incremental/sql/5.37.alpha1.mysql.tpl | 2 + Civi/Api4/CaseType.php | 33 ++++++++++ Civi/Api4/Generic/BasicGetFieldsAction.php | 4 ++ Civi/Api4/Service/Spec/FieldSpec.php | 32 ++++++++++ .../Spec/Provider/CaseTypeGetSpecProvider.php | 43 +++++++++++++ Civi/Api4/Utils/FormattingUtil.php | 17 +++++ api/v3/CaseType.php | 28 +------- .../api/v4/DataSets/ConformanceTest.json | 64 ++++++++++++++++++- .../phpunit/api/v4/Entity/ConformanceTest.php | 1 + .../Service/TestCreationParameterProvider.php | 5 +- xml/schema/Case/CaseType.xml | 3 +- 13 files changed, 236 insertions(+), 45 deletions(-) create mode 100644 Civi/Api4/CaseType.php create mode 100644 Civi/Api4/Service/Spec/Provider/CaseTypeGetSpecProvider.php diff --git a/CRM/Case/BAO/CaseType.php b/CRM/Case/BAO/CaseType.php index e10c70da07..e6a2510c32 100644 --- a/CRM/Case/BAO/CaseType.php +++ b/CRM/Case/BAO/CaseType.php @@ -86,6 +86,18 @@ class CRM_Case_BAO_CaseType extends CRM_Case_DAO_CaseType { } } + public static function formatOutputDefinition(&$value, $row) { + if ($value) { + [$xml] = CRM_Utils_XML::parseString($value); + $value = $xml ? self::convertXmlToDefinition($xml) : []; + } + elseif (!empty($row['id']) || !empty($row['name'])) { + $caseTypeName = $row['name'] ?? CRM_Core_DAO::getFieldValue('CRM_Case_DAO_CaseType', $row['id']); + $xml = CRM_Case_XMLRepository::singleton()->retrieve($caseTypeName); + $value = $xml ? self::convertXmlToDefinition($xml) : []; + } + } + /** * Format / convert submitted array to xml for case type definition * @@ -371,12 +383,22 @@ class CRM_Case_BAO_CaseType extends CRM_Case_DAO_CaseType { */ public static function &create(&$params) { $transaction = new CRM_Core_Transaction(); - - if (!empty($params['id'])) { - CRM_Utils_Hook::pre('edit', 'CaseType', $params['id'], $params); - } - else { - CRM_Utils_Hook::pre('create', 'CaseType', NULL, $params); + // Computed properties. + unset($params['is_forkable']); + unset($params['is_forked']); + + $action = empty($params['id']) ? 'create' : 'edit'; + + CRM_Utils_Hook::pre($action, 'CaseType', $params['id'] ?? NULL, $params); + + // This is an existing case-type. + if ($action === 'edit' && isset($params['definition']) + // which is not yet forked + && !self::isForked($params['id']) + // for which new forks are prohibited + && !self::isForkable($params['id']) + ) { + unset($params['definition']); } $caseType = self::add($params); @@ -386,12 +408,8 @@ class CRM_Case_BAO_CaseType extends CRM_Case_DAO_CaseType { return $caseType; } - if (!empty($params['id'])) { - CRM_Utils_Hook::post('edit', 'CaseType', $caseType->id, $case); - } - else { - CRM_Utils_Hook::post('create', 'CaseType', $caseType->id, $case); - } + CRM_Utils_Hook::post($action, 'CaseType', $caseType->id, $case); + $transaction->commit(); CRM_Case_XMLRepository::singleton(TRUE); CRM_Core_OptionGroup::flushAll(); diff --git a/CRM/Case/DAO/CaseType.php b/CRM/Case/DAO/CaseType.php index e5ff269f08..239bf3dc82 100644 --- a/CRM/Case/DAO/CaseType.php +++ b/CRM/Case/DAO/CaseType.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Case/CaseType.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:2d6717d85561fa14908152df42d9f6ce) + * (GenCodeChecksum:e97808735d244f6b2c7e84c9dd375fe7) */ /** @@ -60,7 +60,7 @@ class CRM_Case_DAO_CaseType extends CRM_Core_DAO { public $description; /** - * Is this entry active? + * Is this case type enabled? * * @var bool */ @@ -178,8 +178,9 @@ class CRM_Case_DAO_CaseType extends CRM_Core_DAO { 'name' => 'is_active', 'type' => CRM_Utils_Type::T_BOOLEAN, 'title' => ts('Case Type Is Active'), - 'description' => ts('Is this entry active?'), + 'description' => ts('Is this case type enabled?'), 'where' => 'civicrm_case_type.is_active', + 'default' => '1', 'table_name' => 'civicrm_case_type', 'entity' => 'CaseType', 'bao' => 'CRM_Case_BAO_CaseType', diff --git a/CRM/Upgrade/Incremental/sql/5.37.alpha1.mysql.tpl b/CRM/Upgrade/Incremental/sql/5.37.alpha1.mysql.tpl index 773d4db962..8bf68b984d 100644 --- a/CRM/Upgrade/Incremental/sql/5.37.alpha1.mysql.tpl +++ b/CRM/Upgrade/Incremental/sql/5.37.alpha1.mysql.tpl @@ -7,3 +7,5 @@ UPDATE civicrm_state_province s INNER JOIN civicrm_country c on c.id = s.country_id AND c.name = 'United Kingdom' AND s.name = 'Carmarthenshire' AND s.abbreviation = 'CRF' SET s.abbreviation = 'CMN'; + +ALTER TABLE `civicrm_case_type` CHANGE `is_active` `is_active` tinyint DEFAULT 1 COMMENT 'Is this case type enabled?'; diff --git a/Civi/Api4/CaseType.php b/Civi/Api4/CaseType.php new file mode 100644 index 0000000000..ada029a598 --- /dev/null +++ b/Civi/Api4/CaseType.php @@ -0,0 +1,33 @@ + 'readonly', 'data_type' => 'Boolean', ], + [ + 'name' => 'output_formatters', + 'data_type' => 'Array', + ], ]; } diff --git a/Civi/Api4/Service/Spec/FieldSpec.php b/Civi/Api4/Service/Spec/FieldSpec.php index 3aa807a572..2b24df11af 100644 --- a/Civi/Api4/Service/Spec/FieldSpec.php +++ b/Civi/Api4/Service/Spec/FieldSpec.php @@ -117,6 +117,11 @@ class FieldSpec { */ protected $readonly = FALSE; + /** + * @var callable[] + */ + protected $outputFormatters = []; + /** * Aliases for the valid data types * @@ -366,6 +371,33 @@ class FieldSpec { return $this; } + /** + * @return callable[] + */ + public function getOutputFormatters() { + return $this->outputFormatters; + } + + /** + * @param callable[] $outputFormatters + * @return $this + */ + public function setOutputFormatters($outputFormatters) { + $this->outputFormatters = $outputFormatters; + + return $this; + } + + /** + * @param callable $outputFormatter + * @return $this + */ + public function addOutputFormatter($outputFormatter) { + $this->outputFormatters[] = $outputFormatter; + + return $this; + } + /** * @return bool */ diff --git a/Civi/Api4/Service/Spec/Provider/CaseTypeGetSpecProvider.php b/Civi/Api4/Service/Spec/Provider/CaseTypeGetSpecProvider.php new file mode 100644 index 0000000000..cd746a5585 --- /dev/null +++ b/Civi/Api4/Service/Spec/Provider/CaseTypeGetSpecProvider.php @@ -0,0 +1,43 @@ +getFieldByName('definition')->addOutputFormatter('CRM_Case_BAO_CaseType::formatOutputDefinition'); + } + + /** + * @param string $entity + * @param string $action + * + * @return bool + */ + public function applies($entity, $action) { + return $entity === 'CaseType' && $action === 'get'; + } + +} diff --git a/Civi/Api4/Utils/FormattingUtil.php b/Civi/Api4/Utils/FormattingUtil.php index f6a7e49c4d..888aafb4b7 100644 --- a/Civi/Api4/Utils/FormattingUtil.php +++ b/Civi/Api4/Utils/FormattingUtil.php @@ -209,6 +209,10 @@ class FormattingUtil { if (!$field) { continue; } + if (!empty($field['output_formatters'])) { + self::applyFormatters($result, $fieldName, $field, $value); + $dataType = NULL; + } // Evaluate pseudoconstant suffixes $suffix = strrpos($fieldName, ':'); if ($suffix) { @@ -298,6 +302,19 @@ class FormattingUtil { return is_array($value) ? $matches : $matches[0] ?? NULL; } + private static function applyFormatters($result, $fieldName, $field, &$value) { + $row = []; + $prefix = substr($fieldName, 0, strpos($fieldName, $field['name'])); + foreach ($result as $key => $val) { + if (!$prefix || strpos($key, $prefix) === 0) { + $row[substr($key, strlen($prefix))] = $val; + } + } + foreach ($field['output_formatters'] as $formatter) { + $formatter($value, $row, $field); + } + } + /** * @param mixed $value * @param string $dataType diff --git a/api/v3/CaseType.php b/api/v3/CaseType.php index 9cdcbc8a00..3caa7fba6d 100644 --- a/api/v3/CaseType.php +++ b/api/v3/CaseType.php @@ -27,22 +27,7 @@ */ function civicrm_api3_case_type_create($params) { civicrm_api3_verify_mandatory($params, _civicrm_api3_get_DAO(__FUNCTION__)); - // Computed properties. - unset($params['is_forkable']); - unset($params['is_forked']); - if (!array_key_exists('is_active', $params) && empty($params['id'])) { - $params['is_active'] = TRUE; - } - // This is an existing case-type. - if (!empty($params['id']) && isset($params['definition']) - // which is not yet forked - && !CRM_Case_BAO_CaseType::isForked($params['id']) - // for which new forks are prohibited - && !CRM_Case_BAO_CaseType::isForkable($params['id']) - ) { - unset($params['definition']); - } $result = _civicrm_api3_basic_create(_civicrm_api3_get_BAO(__FUNCTION__), $params, 'CaseType'); return _civicrm_api3_case_type_get_formatResult($result); } @@ -76,16 +61,9 @@ function civicrm_api3_case_type_get($params) { */ function _civicrm_api3_case_type_get_formatResult(&$result, $options = []) { foreach ($result['values'] as $key => &$caseType) { - if (!empty($caseType['definition'])) { - list($xml) = CRM_Utils_XML::parseString($caseType['definition']); - $caseType['definition'] = $xml ? CRM_Case_BAO_CaseType::convertXmlToDefinition($xml) : []; - } - else { - if (empty($options['return']) || !empty($options['return']['definition'])) { - $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); - $caseType['definition'] = $xml ? CRM_Case_BAO_CaseType::convertXmlToDefinition($xml) : []; - } + if (!empty($caseType['definition']) || empty($options['return']) || !empty($options['return']['definition'])) { + $caseType += ['definition' => NULL]; + CRM_Case_BAO_CaseType::formatOutputDefinition($caseType['definition'], $caseType); } $caseType['is_forkable'] = CRM_Case_BAO_CaseType::isForkable($caseType['id']); $caseType['is_forked'] = CRM_Case_BAO_CaseType::isForked($caseType['id']); diff --git a/tests/phpunit/api/v4/DataSets/ConformanceTest.json b/tests/phpunit/api/v4/DataSets/ConformanceTest.json index f34be53340..217138e077 100644 --- a/tests/phpunit/api/v4/DataSets/ConformanceTest.json +++ b/tests/phpunit/api/v4/DataSets/ConformanceTest.json @@ -5,11 +5,73 @@ "last_name": "Voss", "contact_type": "Individual", "@ref": "test_contact_1" + }, + { + "first_name": "Jim", + "last_name": "Henson", + "contact_type": "Individual", + "@ref": "test_contact_2" + } + ], + "CaseType": [ + { + "name": "test_case_type", + "title": "Test Case Type", + "definition": { + "activityTypes": [ + { + "name": "Open Case", + "max_instances": "1" + }, + { + "name": "Follow up" + } + ], + "activitySets": [ + { + "name": "standard_timeline", + "label": "Standard Timeline", + "timeline": 1, + "activityTypes": [ + { + "name": "Open Case", + "status": "Completed" + }, + { + "name": "Follow up", + "reference_activity": "Open Case", + "reference_offset": "3", + "reference_select": "newest" + } + ] + } + ], + "timelineActivityTypes": [ + { + "name": "Open Case", + "status": "Completed" + }, + { + "name": "Follow up", + "reference_activity": "Open Case", + "reference_offset": "3", + "reference_select": "newest" + } + ], + "caseRoles": [ + { + "name": "Parent of", + "creator": "1", + "manager": "1" + } + ] + }, + "@ref": "test_case_type_1" } ], "Case": [ { - "case_type_id": 1, + "case_type_id": "@ref test_case_type_1.id", "status_id": 1, "contact_id": "@ref test_contact_1.id", "creator_id": "@ref test_contact_1.id" diff --git a/tests/phpunit/api/v4/Entity/ConformanceTest.php b/tests/phpunit/api/v4/Entity/ConformanceTest.php index db0767550d..a523447fb5 100644 --- a/tests/phpunit/api/v4/Entity/ConformanceTest.php +++ b/tests/phpunit/api/v4/Entity/ConformanceTest.php @@ -43,6 +43,7 @@ class ConformanceTest extends UnitTestCase { */ public function setUp(): void { $tablesToTruncate = [ + 'civicrm_case_type', 'civicrm_custom_group', 'civicrm_custom_field', 'civicrm_group', diff --git a/tests/phpunit/api/v4/Service/TestCreationParameterProvider.php b/tests/phpunit/api/v4/Service/TestCreationParameterProvider.php index 95284d8352..2ab8361942 100644 --- a/tests/phpunit/api/v4/Service/TestCreationParameterProvider.php +++ b/tests/phpunit/api/v4/Service/TestCreationParameterProvider.php @@ -45,9 +45,8 @@ class TestCreationParameterProvider { $createSpec = $this->gatherer->getSpec($entity, 'create', FALSE); $requiredFields = array_merge($createSpec->getRequiredFields(), $createSpec->getConditionalRequiredFields()); - if ($entity === 'Contact') { - $requiredFields[] = $createSpec->getFieldByName('first_name'); - $requiredFields[] = $createSpec->getFieldByName('last_name'); + if ($entity === 'Case') { + $requiredFields[] = $createSpec->getFieldByName('creator_id'); } $requiredParams = []; diff --git a/xml/schema/Case/CaseType.xml b/xml/schema/Case/CaseType.xml index 8640e62268..ba361ba9e1 100644 --- a/xml/schema/Case/CaseType.xml +++ b/xml/schema/Case/CaseType.xml @@ -60,7 +60,8 @@ is_active Case Type Is Active boolean - Is this entry active? + Is this case type enabled? + 1 4.5 -- 2.25.1