Thursday, February 4, 2010 - 14:40
Written by

Cross-posted at The Nerdy Adventures of Wes.

CiviCRM isn't always the most predictable codebase. Recently I needed to get and set some custom field values in a hook I was writing. The hook's job was to calculate some custom field values and create some contact references when a contribution was created or updated. As always, dlobo was a huge help (he's the CiviCRM guru, find him in #civicrm on Freenode). Here's what I did to set a couple of custom fields in my _pre hook:

$custom_fields = array('foo' => 'custom_1', 'bar' => 'custom_2');
function modulename_civicrm_pre ($op, objectName, $objectId, &$objectRef) {
  if ($objectName != 'Contribution' || ($op != 'edit' && $op != 'create')) {
  $contribution_id = $objectId;
  require_once 'CRM/Core/BAO/CustomValueTable.php';
  $my_foo = 'blah';
  $my_bar = 'baz';
  $set_params = array('entityID' => $contribution_id,
    $custom_fields['foo'] => $my_foo, $custom_fields['bar'] => $my_bar);

And here's an example for retrieving some custom field values from the contact object in the same hook:

$custom_fields = array('contact_foo' => 'custom_3', 'contact_bar' => 'custom_4');
function modulename_civicrm_pre ($op, objectName, $objectId, &$objectRef) {
  if ($objectName != 'Contribution' || ($op != 'edit' && $op != 'create')) {
  // set the field names to 1 that we want to get back
  $get_params = array('entityID' => $objectRef['contact_id'],
    $custom_fields['contact_foo'] => 1, $custom_fields['contact_bar'] => 1);
  require_once 'CRM/Core/BAO/CustomValueTable.php';
  $values = CRM_Core_BAO_CustomValueTable::getValues($get_params);
  $my_cfoo = $values[$custom_fields['contact_foo']];
  $my_cbar = $values[$custom_fields['contact_bar']];

So it's not ideal that you have to hard-code the custom field IDs; there should be a way to look them up (maybe there is). But it's not the worst thing in the world unless you're in the habit of destroying and recreating your custom fields from time to time. Probably you're not on a production system.

Filed under


I am in the habit of destroying and recreating my custom fields. :-) Therefore I wrote a function that looks up the custom table name and field names, so that I can use them in the 'real' SQL statements. The function relies on the label for the custom field group and the fields. So as long as the labels stay the same, the code works.

Here is my function, which could be improved on:

function getCustomTableFieldNames(){

//*** Start of section to get table and column names ***/
# Change the next 3 variables to match the labels of the custom field group.
$custom_field_group_label = "Extended Date Information";
$custom_field_birthdate_sunset_label = "Birth Date Before Sunset";
$custom_field_deathdate_sunset_label = "Death Date Before Sunset" ;

/* rest of the function */
$return_custom_birth_sunset = '';
$return_custom_death_sunset = '';
$error_msg = '';

// figure out the table names and field names for custom fields.
$tablename_query = "SELECT civicrm_custom_group.table_name as tablename from civicrm_custom_group
where title = '$custom_field_group_label' and extends = 'Individual' ";

$extended_date_table = '';

$table_dao =& CRM_Core_DAO::executeQuery( $tablename_query );
if ( $table_dao->fetch( ) ) {
$extended_date_table = $table_dao->tablename;
$error_msg = "Cannot find table for custom field group '$custom_field_group_label'";
$return_values = array( $error_msg, $return_custom_birth_sunset , $return_custom_death_sunset );
return $return_values;
$table_dao->free( );

if( $extended_date_table == ''){

$error_msg = "extended_date_table variable is empty";
$return_values = array( $error_msg, $return_custom_birth_sunset , $return_custom_death_sunset );
return $return_values;

$date_fields_query = " SELECT civicrm_custom_field.column_name as column_name, civicrm_custom_field.label as label
FROM civicrm_custom_group left join civicrm_custom_field
on = civicrm_custom_field.custom_group_id
where civicrm_custom_group.title = '$custom_field_group_label'
and civicrm_custom_group.extends = 'Individual'
and ( civicrm_custom_field.label = '$custom_field_birthdate_sunset_label' or
civicrm_custom_field.label = '$custom_field_deathdate_sunset_label' ) ";

//print "";

$fieldnames_dao =& CRM_Core_DAO::executeQuery( $date_fields_query );

while ( $fieldnames_dao->fetch( ) ) {

$tmp_label = $fieldnames_dao->label;
if($tmp_label == $custom_field_birthdate_sunset_label){
$extended_birth_date = $fieldnames_dao->column_name;
}else if($tmp_label == $custom_field_deathdate_sunset_label ){
$extended_death_date = $fieldnames_dao->column_name;

if($extended_birth_date == "" || $extended_death_date == ""){
$error_msg = "Cannot find custom field names for before sunset flags for date of birth or date of death";
$return_values = array( $error_msg, $return_custom_birth_sunset , $return_custom_death_sunset );
return $return_values;
$fieldnames_dao->free( );
//*** end of section to get table and column names ***/

$return_values = array( $error_msg, $extended_date_table, $extended_birth_date , $extended_death_date );
return $return_values;

what is the license? public domain and can be recopyrighted ? academic free ?

The code I pasted in my previous comment is GPL. Actually I would prefer to see more control given when creating a custom field group. Perhaps the option to do this could be collapsed for "begining" users and expanded for "advanced" users.

The problem I see even with the lookup routine that I wrote: If a end-user changes the label, then my code for custom hooks, reports, etc will break. If I had control over the actual table name and field name in the database, then the end-user could change the labels without worry.

Also for multi-lingual setups, I cannot rely on the label being a known value.

given the field label and an optional group title:

this will be part of 3.1.2

The code in my original post has a bug that makes it only work for updating existing objects. For new objects, it will fail because there is no object yet in a pre hook. You should use a post hook instead for this.

Note that the $objectRef is a reference to the BAO instance in a post hook, rather than an array.

Also note that for non-read-only fields, CiviCRM will clobber your hook changes after you make them in a post hook because post fires before the form changes are applied. You probably want a postProcess hook in that case.