diff --git a/CRM/Case/Form/CustomData.php b/CRM/Case/Form/CustomData.php index ec2f6ddb04ce0807e4f73d7133ef8071cc6bd82d..f77fadb5c770bc208956117c6d92ed7bb63fea79 100644 --- a/CRM/Case/Form/CustomData.php +++ b/CRM/Case/Form/CustomData.php @@ -146,7 +146,7 @@ class CRM_Case_Form_CustomData extends CRM_Core_Form { $formattedDetails = []; foreach ($params as $customField => $newCustomValue) { if (substr($customField, 0, 7) == 'custom_') { - if ($this->_defaults[$customField] == $newCustomValue) { + if (($this->_defaults[$customField] ?? '') === $newCustomValue) { // Don't show values that did not change continue; } diff --git a/api/v3/CustomValue.php b/api/v3/CustomValue.php index 34f96a9f2daa350e2c3115255a74d9a0b3ccaf27..7c2a5a790b040fdff9bbfb1b918d1a2b99bcadf9 100644 --- a/api/v3/CustomValue.php +++ b/api/v3/CustomValue.php @@ -399,7 +399,7 @@ function _civicrm_api3_custom_value_getdisplayvalue_spec(&$spec) { ]; $spec['custom_field_value'] = [ 'title' => 'Custom Field value', - 'description' => 'Specify the value of the custom field to return as displayed value', + 'description' => 'Specify the value of the custom field to return as displayed value, or omit to use the current value.', 'type' => CRM_Utils_Type::T_STRING, 'api.required' => 0, ]; @@ -414,14 +414,15 @@ function _civicrm_api3_custom_value_getdisplayvalue_spec(&$spec) { * @throws \CiviCRM_API3_Exception */ function civicrm_api3_custom_value_getdisplayvalue($params) { - if (empty($params['custom_field_value'])) { + // Null or missing means use the current db value, but treat '0', 0, and '' as legitimate values to look up. + if (($params['custom_field_value'] ?? NULL) === NULL) { $params['custom_field_value'] = civicrm_api3('CustomValue', 'getsingle', [ 'return' => ["custom_{$params['custom_field_id']}"], 'entity_id' => $params['entity_id'], ]); $params['custom_field_value'] = $params['custom_field_value']['latest']; } - $values[$params['custom_field_id']]['display'] = CRM_Core_BAO_CustomField::displayValue($params['custom_field_value'], $params['custom_field_id'], CRM_Utils_Array::value('entity_id', $params)); + $values[$params['custom_field_id']]['display'] = CRM_Core_BAO_CustomField::displayValue($params['custom_field_value'], $params['custom_field_id'], $params['entity_id'] ?? NULL); $values[$params['custom_field_id']]['raw'] = $params['custom_field_value']; return civicrm_api3_create_success($values, $params, 'CustomValue', 'getdisplayvalue'); } diff --git a/tests/phpunit/CRM/Case/Form/CustomDataTest.php b/tests/phpunit/CRM/Case/Form/CustomDataTest.php new file mode 100644 index 0000000000000000000000000000000000000000..112de1e406dacc27694ac1f5cf03b825df608afc --- /dev/null +++ b/tests/phpunit/CRM/Case/Form/CustomDataTest.php @@ -0,0 +1,333 @@ +<?php +require_once 'CiviTest/CiviCaseTestCase.php'; + +/** + * Class CRM_Case_Form_CustomDataTest + * @group headless + */ +class CRM_Case_Form_CustomDataTest extends CiviCaseTestCase { + + protected $custom_group; + + public function setUp() { + parent::setUp(); + $this->custom_group = $this->customGroupCreate(['extends' => 'Case']); + $this->custom_group = $this->custom_group['values'][$this->custom_group['id']]; + } + + /** + * Test that changes to custom fields on cases generate the correct details + * body for ChangeCustomData. + * + * @dataProvider customDataProvider + * + * @param array $input + * @param array $expected + */ + public function testChangeCustomDataFormattedDetails(array $input, array $expected) { + // set up custom field, with any overrides from input params + $custom_field = $this->callAPISuccess('custom_field', 'create', array_merge([ + 'custom_group_id' => $this->custom_group['id'], + 'label' => 'What?', + 'data_type' => 'String', + 'html_type' => 'Text', + 'is_active' => 1, + ], $input['custom_field_params'])); + $custom_field = $custom_field['values'][$custom_field['id']]; + + // set up case and set the custom field initial value + $client_id = $this->individualCreate([], 0, TRUE); + $caseObj = $this->createCase($client_id, $this->_loggedInUser); + if (isset($input['custom_field_oldvalue'])) { + $this->callAPISuccess('CustomValue', 'create', [ + "custom_{$custom_field['id']}" => $input['custom_field_oldvalue'], + 'entity_id' => $caseObj->id, + ]); + } + + // set up form + $form = $this->getFormObject('CRM_Case_Form_CustomData'); + $form->set('groupID', $this->custom_group['id']); + $form->set('entityID', $caseObj->id); + $form->set('subType', $this->caseTypeId); + $form->set('cid', $client_id); + $form->preProcess(); + + // We need to do money conversion here because to do the creation above it + // needs to be in machine format, but then for the form stuff it needs to + // be in user format. + if (($input['custom_field_params']['data_type'] ?? '') === 'Money' && CRM_Core_I18n::singleton()->getLocale() !== 'en_US') { + $expected['string'] = $this->convertCurrency($expected['string']); + if (isset($input['custom_field_oldvalue'])) { + $input['custom_field_oldvalue'] = $this->convertCurrency($input['custom_field_oldvalue']); + } + $input['custom_field_newvalue'] = $this->convertCurrency($input['custom_field_newvalue']); + } + + // Simulate an edit with formValues. + // The suffix is always going to be '1' since we created it above and it's + // the first entry in the custom_value_XX table. If it doesn't exist yet + // then our new entry will still be the first and will have suffix 1. + $custom_field_name = "custom_{$custom_field['id']}_1"; + $formValues = [$custom_field_name => $input['custom_field_newvalue']]; + + // compute and compare + $output = $form->formatCustomDataChangesForDetail($formValues); + $this->assertEquals($expected['string'], $output); + } + + /** + * Same as testChangeCustomDataFormattedDetails but in a different locale. + * + * @dataProvider customDataProvider + * + * @param array $input + * @param array $expected + */ + public function testChangeCustomDataFormattedDetailsLocale(array $input, array $expected) { + CRM_Core_I18n::singleton()->setLocale('it_IT'); + CRM_Core_Config::singleton()->defaultCurrency = 'EUR'; + CRM_Core_Config::singleton()->monetaryThousandSeparator = ' '; + CRM_Core_Config::singleton()->monetaryDecimalPoint = ','; + + $this->testChangeCustomDataFormattedDetails($input, $expected); + + CRM_Core_Config::singleton()->defaultCurrency = 'USD'; + CRM_Core_Config::singleton()->monetaryThousandSeparator = ','; + CRM_Core_Config::singleton()->monetaryDecimalPoint = '.'; + CRM_Core_I18n::singleton()->setLocale('en_US'); + } + + /** + * data provider for testChangeCustomDataFormattedDetails + * + * @return array + */ + public function customDataProvider(): array { + return [ + 0 => [ + 'input' => [ + 'custom_field_params' => [ + 'html_type' => 'Select', + 'option_values' => [ + [ + 'name' => 'Red', + 'label' => 'Red', + 'value' => '1', + 'is_active' => 1, + 'weight' => 1, + ], + [ + 'name' => 'Green', + 'label' => 'Green', + 'value' => '2', + 'is_active' => 1, + 'weight' => 2, + ], + ], + ], + 'custom_field_oldvalue' => '1', + 'custom_field_newvalue' => '2', + ], + 'expected' => [ + 'string' => 'What?: Red => Green', + ], + ], + + 1 => [ + 'input' => [ + 'custom_field_params' => [ + 'html_type' => 'Select', + 'option_values' => [ + [ + 'name' => 'Red', + 'label' => 'Red', + 'value' => '1', + 'is_active' => 1, + 'weight' => 1, + ], + [ + 'name' => 'Green', + 'label' => 'Green', + 'value' => '2', + 'is_active' => 1, + 'weight' => 2, + ], + ], + ], + 'custom_field_oldvalue' => '', + 'custom_field_newvalue' => '2', + ], + 'expected' => [ + 'string' => 'What?: => Green', + ], + ], + + 2 => [ + 'input' => [ + 'custom_field_params' => [ + 'html_type' => 'Select', + 'option_values' => [ + [ + 'name' => 'Red', + 'label' => 'Red', + 'value' => '1', + 'is_active' => 1, + 'weight' => 1, + ], + [ + 'name' => 'Green', + 'label' => 'Green', + 'value' => '2', + 'is_active' => 1, + 'weight' => 2, + ], + ], + ], + 'custom_field_oldvalue' => '1', + 'custom_field_newvalue' => '', + ], + 'expected' => [ + 'string' => 'What?: Red => ', + ], + ], + + 3 => [ + 'input' => [ + 'custom_field_params' => [ + 'html_type' => 'Select', + 'option_values' => [ + [ + 'name' => 'Red', + 'label' => 'Red', + 'value' => '1', + 'is_active' => 1, + 'weight' => 1, + ], + [ + 'name' => 'Green', + 'label' => 'Green', + 'value' => '2', + 'is_active' => 1, + 'weight' => 2, + ], + ], + ], + // Note no old value, simulating as if we already have existing cases, but just added the field definition now. + 'custom_field_newvalue' => '2', + ], + 'expected' => [ + 'string' => 'What?: => Green', + ], + ], + + 4 => [ + 'input' => [ + 'custom_field_params' => [ + 'data_type' => 'Money', + ], + 'custom_field_oldvalue' => '1.23', + 'custom_field_newvalue' => '2.34', + ], + 'expected' => [ + 'string' => 'What?: 1.23 => 2.34', + ], + ], + + 5 => [ + 'input' => [ + 'custom_field_params' => [ + 'data_type' => 'Money', + ], + 'custom_field_oldvalue' => '', + 'custom_field_newvalue' => '2.34', + ], + 'expected' => [ + 'string' => 'What?: 0.00 => 2.34', + ], + ], + + 6 => [ + 'input' => [ + 'custom_field_params' => [ + 'data_type' => 'Money', + ], + 'custom_field_oldvalue' => '1.23', + 'custom_field_newvalue' => '', + ], + 'expected' => [ + 'string' => 'What?: 1.23 => ', + ], + ], + + 7 => [ + 'input' => [ + 'custom_field_params' => [ + 'data_type' => 'Money', + ], + 'custom_field_newvalue' => '2.34', + ], + 'expected' => [ + 'string' => 'What?: => 2.34', + ], + ], + + 8 => [ + 'input' => [ + 'custom_field_params' => [], + 'custom_field_oldvalue' => 'some text', + 'custom_field_newvalue' => 'some new text', + ], + 'expected' => [ + 'string' => 'What?: some text => some new text', + ], + ], + + 9 => [ + 'input' => [ + 'custom_field_params' => [], + 'custom_field_oldvalue' => '', + 'custom_field_newvalue' => 'some new text', + ], + 'expected' => [ + 'string' => 'What?: => some new text', + ], + ], + + 10 => [ + 'input' => [ + 'custom_field_params' => [], + 'custom_field_oldvalue' => 'some text', + 'custom_field_newvalue' => '', + ], + 'expected' => [ + 'string' => 'What?: some text => ', + ], + ], + + 11 => [ + 'input' => [ + 'custom_field_params' => [], + 'custom_field_newvalue' => 'some new text', + ], + 'expected' => [ + 'string' => 'What?: => some new text', + ], + ], + ]; + } + + /** + * Convert to locale currency format for purposes of these tests + * @param string $input + * @return string + */ + private function convertCurrency(string $input): string { + $conversion_table = [ + ',' => CRM_Core_Config::singleton()->monetaryThousandSeparator, + '.' => CRM_Core_Config::singleton()->monetaryDecimalPoint, + ]; + return strtr($input, $conversion_table); + } + +}