EDIT please see edit below
Here's how I've opted to do it. Please do submit alternative answers if there's a better way.
Use the civicrm_post hook like this:
Note that you need to replace N
with your custom field's ID number
function myext_civicrm_post($op, $objectName, $objectId, &$objectRef) {
if ($objectId && $op == 'create' && $objectName == 'Contribution') {
// See if we have a field that belongs to the fieldset.
$result = civicrm_api3('CustomValue', 'get', [
'sequential' => 1,
'entity_id' => $objectId,
'entity_type' => "Contribution",
'return.custom_N' => 1,
]);
if ($result['count'] == 0) {
// Create the default entry.
$result = civicrm_api3('CustomValue', 'create', [
'sequential' => 1,
'entity_id' => $contribution_id,
'entity_type' => "Contribution",
'custom_N' => 'unknown',
// custom_M ...
]);
}
}
}
Then when you create a Contribution, the defaults are properly set.
Interesting observations
My interest was in creating the default record when the contribution was created by the API. But I thought I'd look into how this works with using the UI too.
If you use the web UI to create a contribution then the code above gets in before the main process creates/sets the custom values. i.e. This is the order of things:
- User creates a contribution record.
- Contribution record created.
- Our hook runs, no custom value exists so it creates the default record.
- The user-entered values (inc. the defaults that were set up in the form) overwrite the default created by our hook.
The same process happens when you use the API to create a contribution but include values for the custom data also.
EDIT
The above worked fine until sometime around CiviCRM version 5.18 at which point it stopped working. It stopped because during creation of a contribution this code would run, creating a custom data record, then the calling code would also try to create the same custom data record - causing a database error.
I found that I could acheive what I had been trying to do as follows:
function myext_civicrm_post($op, $objectName, $objectId, &$objectRef) {
if ($objectId && $op == 'create' && $objectName == 'Contribution') { CRM_Core_Transaction::addCallback( CRM_Core_Transaction::PHASE_POST_COMMIT, 'myfixercallback', [$objectId]); }
Then myfixercallback
would do the same work.
However! it seems that CiviCRM - at least from 5.19, possibly earlier, now properly creates default custom data. So this hack is simply not needed any more.