3

EDIT: this is done automatically by CiviCRM from 5.18+

I have a custom fieldset on Contributions that uses an option list. The option list has a default value set, but when contributions are created by non-UI processes (e.g. API) they do not have this default value; the field is empty/null/not there.

The fieldset is created by an extension. How could I ensure the default is set for all contributions?

Seems the options might be:

  • Use hook_civicrm_post then use the API to make a call.
  • Or could hook_civicrm_pre be used for this? Can you set custom fields in this part of the process?
  • or a horrible way would be to write an API job to somehow create default entries and run this by cron. Perhaps this might be necessary to set a default for existing contributions though?

Anyone done this? If so, how d'ya do it?

  • think you should update your answer too - perhaps insert at top of it – petednz - fuzion Nov 7 at 21:56
  • @petednz-fuzion erm, I did already! – artfulrobot Nov 8 at 9:48
  • odd. i must be tired. I see "EDIT: this is done automatically by CiviCRM from 5.18+" in your question but don't see equivalent in your answer – petednz - fuzion Nov 9 at 0:42
  • @petednz-fuzion super weird. caching issue somewhere? my answer literally starts with "EDIT please see edit below" and "below" there's a heading "EDIT" with a (rather long winded) description of why it's not now needed. – artfulrobot Nov 9 at 10:43
3

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:

  1. User creates a contribution record.
  2. Contribution record created.
  3. Our hook runs, no custom value exists so it creates the default record.
  4. 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.18, possibly earlier, now properly creates default custom data. So this hack is simply not needed any more.

1

Based on the answer of @artfulrobot (thanks!), i have made a version of the code that use the definition of the custom field itself. If we have a default value, then force the creation of the field, otherwise, just ignore. Of course, it's probably a bit slower because we do some extra api calls, but it means we could change the defaults in the UI without changing the code.


function myext_civicrm_post($op, $objectName, $objectId, &$objectRef) {

  if ($objectName == 'Contribution') {

    $result = civicrm_api3('CustomField', 'get', [
      'sequential' => 1,
      'return' => ["default_value", "custom_group_id", "name"],
      'default_value' => ['IS NOT NULL' => 1],
      // use your own custom group id
      'custom_group_id.id' => 1,
    ]); 
    if ($result['count']) {
      // use the first one (any will do because we know that we are in one custom group)
      $check_field = 'custom_' . $result['values'][0]['id'];

      // See if we have a field that belongs to the fieldset.
      $existResult = civicrm_api3('CustomValue', 'get', [
        'sequential' => 1,
        'entity_id' => $objectId,
        'entity_type' => "Contribution",
        'return.' . $check_field => 1,
      ]); 

      if ($existResult['count'] == 0) {
        // insert a new entry with defaults values
        $p = [ 
          'sequential' => 1,
          'entity_id' => $objectId,
          'entity_type' => 'Contribution'
        ];  
        foreach ($result['values'] as $row) {
          $id = $row['id'];
          $p['custom_' . $id] = $row['default_value'];
        }   

        // Create the default entry.                                                                                                        
        $result = civicrm_api3('CustomValue', 'create', $p);
      }   
    }

  }

}

I guess with a bit of extra effort, one could make a generic extension that works for any custom fields that attach to any entity type.

  • Nb. I think CiviCRM does this stuff now. – artfulrobot Nov 6 at 14:00
  • Ok, good to know thanks! Do you have any reference of the issue / pull request ? – samuelsov Nov 15 at 15:58
  • No, I just noticed it at 5.18. – artfulrobot Nov 15 at 17:35
0

I've handled this using hook_civicrm_custom. This fires after the db write, which isn't ideal, but has a much simpler set of params than hook_civicrm_pre.

  • Does this call when Contributions are created? My db is not showing any record being written to the custom data table, which I think would imply that this hook would not fire (because the docs say "This hook is called AFTER the db write on a custom table" and no write has happened)? – artfulrobot Jan 5 '17 at 17:49
  • Ah - I guess in my instance, I had other custom fields that were being written. I don't think my solution will apply to your use case. – Jon G - Megaphone Tech Jan 5 '17 at 17:51
  • Nb. I think CiviCRM does this stuff now. – artfulrobot Nov 6 at 14:01

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.