Skip to content

Commit 6b3a889

Browse files
authored
Merge pull request civicrm#33377 from lemniscus/write-custom-defaults-on-create
APIv4 - Ensure custom field default values are respected on new records
2 parents ad14948 + 322fcc7 commit 6b3a889

2 files changed

Lines changed: 108 additions & 0 deletions

File tree

Civi/Api4/Generic/Traits/DAOActionTrait.php

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
namespace Civi\Api4\Generic\Traits;
1414

15+
use Civi\Api4\Service\Spec\RequestSpec;
1516
use Civi\Api4\Utils\FormattingUtil;
1617
use Civi\Api4\Utils\CoreUtil;
1718
use Civi\Api4\Utils\ReflectionUtils;
@@ -160,6 +161,9 @@ protected function writeObjects($items) {
160161
$entityId = FormattingUtil::resolveContactID($idField, $entityId);
161162
}
162163
FormattingUtil::formatWriteParams($item, $this->entityFields());
164+
if (!$entityId) {
165+
self::ensureCustomFieldDefaultsAreWrittenOnCreate($item);
166+
}
163167
$this->formatCustomParams($item, $entityId);
164168

165169
if (!$entityId) {
@@ -281,6 +285,29 @@ protected function resolveFKValues(array &$record): void {
281285
}
282286
}
283287

288+
protected function ensureCustomFieldDefaultsAreWrittenOnCreate(array &$record): void {
289+
$specFilters = $record;
290+
// The following lines are adapted from \CRM_Custom_Form_CustomDataTrait::addCustomDataFieldsToForm
291+
// Reuse the same spec-gatherer from Api4.getFields
292+
$spec = new RequestSpec($this->getEntityName(), 'create', $specFilters);
293+
$fieldFilters = \Civi::service('spec_gatherer')->getCustomGroupFilters($spec);
294+
if ($fieldFilters === NULL) {
295+
return;
296+
}
297+
$customGroups = \CRM_Core_BAO_CustomGroup::getAll($fieldFilters);
298+
299+
foreach ($customGroups as $customGroup) {
300+
foreach ($customGroup['fields'] as $field) {
301+
$fieldName = "{$customGroup['name']}.{$field['name']}";
302+
if (isset($field['default_value']) && !isset($record[$fieldName])) {
303+
$record[$fieldName] = $field['default_value'];
304+
// Setting the value for one field in the group will ensure that all get written
305+
break;
306+
}
307+
}
308+
}
309+
}
310+
284311
/**
285312
* Converts params from flat array e.g. ['GroupName.Fieldname' => value] to the
286313
* hierarchy expected by the BAO, nested within $params['custom'].

tests/phpunit/api/v4/Custom/CreateCustomValueTest.php

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
use Civi\Api4\Contact;
2424
use Civi\Api4\CustomField;
2525
use Civi\Api4\CustomValue;
26+
use Civi\Api4\IM;
2627
use Civi\Api4\OptionGroup;
2728
use Civi\Api4\OptionValue;
2829
use Civi\Api4\Activity;
@@ -182,4 +183,84 @@ public function testEmptyValueArrayForCustomFields(): void {
182183
->execute();
183184
}
184185

186+
public function testCustomFieldDefaultsGetWrittenOnCreate(): void {
187+
$contactID = $this->createTestRecord('Contact')['id'];
188+
$this->createTestRecord('CustomGroup', [
189+
'title' => 'IMCustom',
190+
'extends' => 'IM',
191+
]);
192+
193+
// Set up custom field with default static string value
194+
$optionValues = ['defaultval' => 'Default', 'otherval' => 'Other'];
195+
$customSelectFieldDefault = 'defaultval';
196+
CustomField::create(FALSE)
197+
->addValue('custom_group_id:name', 'IMCustom')
198+
->addValue('label', 'Select Field')
199+
->addValue('name', 'im_select_field')
200+
->addValue('data_type', 'String')
201+
->addValue('html_type', 'Select')
202+
->addValue('option_values', $optionValues)
203+
->addValue('default_value', $customSelectFieldDefault)
204+
->execute();
205+
206+
// Set up custom field with default static boolean value
207+
$customBoolFieldDefault = FALSE;
208+
CustomField::create(FALSE)
209+
->addValue('custom_group_id:name', 'IMCustom')
210+
->addValue('label', 'Yes/No Field')
211+
->addValue('name', 'im_boolean_field')
212+
->addValue('data_type', 'Boolean')
213+
->addValue('html_type', 'Radio')
214+
->addValue('default_value', $customBoolFieldDefault)
215+
->execute();
216+
217+
// Set up custom field with string value generated by callback
218+
CustomField::create(FALSE)
219+
->addValue('custom_group_id:name', 'IMCustom')
220+
->addValue('label', 'Value from Callback')
221+
->addValue('name', 'im_callback_field')
222+
->addValue('data_type', 'String')
223+
->addValue('html_type', 'Text')
224+
->addValue('default_callback', ['CRM_Utils_System', 'version'])
225+
->execute();
226+
227+
// Core fields can have default values too, of course
228+
$coreFieldDefault = IM::getFields(TRUE)
229+
->addWhere('name', '=', 'is_billing')
230+
->addSelect('default_value')
231+
->execute()->single()['default_value'];
232+
233+
// Test: No custom field values given to Create action
234+
$result = IM::create()
235+
->addValue('contact_id', $contactID)
236+
->addValue('location_type_id:name', 'Home')
237+
->addChain('created_im', \Civi\Api4\IM::get()
238+
->addSelect('is_billing', 'custom.*')
239+
->addWhere('id', '=', '$id')
240+
)
241+
->execute()->single()['created_im'][0];
242+
243+
self::assertEquals($coreFieldDefault, $result['is_billing']);
244+
self::assertEquals($customSelectFieldDefault, $result['IMCustom.im_select_field']);
245+
self::assertEquals($customBoolFieldDefault, $result['IMCustom.im_boolean_field']);
246+
// Apparently 'default_callback' is not supported for custom fields?
247+
// self::assertEquals(\CRM_Utils_System::version(), $result['IMCustom.im_callback_field']);
248+
249+
// Test: One custom field value given to Create action, the other left blank
250+
$result = IM::create()
251+
->addValue('contact_id', $contactID)
252+
->addValue('location_type_id:name', 'Home')
253+
->addValue('IMCustom.im_select_field', 'otherval')
254+
->addChain('created_im', \Civi\Api4\IM::get()
255+
->addSelect('custom.*')
256+
->addWhere('id', '=', '$id')
257+
)
258+
->execute()->single()['created_im'][0];
259+
260+
self::assertEquals('otherval', $result['IMCustom.im_select_field']);
261+
self::assertEquals($customBoolFieldDefault, $result['IMCustom.im_boolean_field']);
262+
// Apparently 'default_callback' is not supported for custom fields?
263+
// self::assertEquals(\CRM_Utils_System::version(), $result['IMCustom.im_callback_field']);
264+
}
265+
185266
}

0 commit comments

Comments
 (0)