You are here

civicrm_entity.module in CiviCRM Entity 7

Same filename and directory in other branches
  1. 8.3 civicrm_entity.module
  2. 7.2 civicrm_entity.module

Implement CiviCRM entities as a Drupal Entity.

File

civicrm_entity.module
View source
<?php

/**
 * @file
 * Implement CiviCRM entities as a Drupal Entity.
 */

/**
 * Implements hook_menu_later().
 *
 * Entity API creates all the fields page links but the basic 'manage' page is missing
 * so we use the /fields page at that url
 */
function civicrm_entity_menu_alter(&$items) {
  foreach (entity_get_info() as $entity_name => $entity_info) {
    if (!empty($entity_info['module']) && $entity_info['module'] == 'civicrm_entity') {
      foreach ($entity_info['bundles'] as $file_type => $bundle_info) {
        if (isset($bundle_info['admin'])) {

          // Get the base path and access.
          $path = $bundle_info['admin']['path'];
          $items[$path] = $items[$path . '/fields'];
          $items[$path]['type'] = MENU_NORMAL_ITEM;
          $items[$path]['title'] = $entity_info['label'];
          $items[$path]['description'] = t('CiviCRM @entity entity', array(
            '@entity' => $entity_name,
          ));
          $items[$path . '/fields']['type'] = MENU_DEFAULT_LOCAL_TASK;
        }
      }
    }
  }
}

/**
 * Implements hook_permission().
 */
function civicrm_entity_permission() {
  return array(
    'civicrm_entity.rules.administer' => array(
      'title' => t('Administer CiviCRM rule configurations'),
      'restrict access' => TRUE,
    ),
  );
}

/**
 * Entity access callback.
 *
 * @param $op
 * @param $entity
 * @param $account
 * @param $entity_type
 *
 * @return bool
 */
function civicrm_entity_access($op, $entity, $account, $entity_type) {
  if ($op == 'view' && $entity_type == 'civicrm_event') {
    return user_access('view event info');
  }
  elseif ($op == 'view' && $entity_type == 'civicrm_participant') {
    return user_access('view event participants');
  }
  else {
    return user_access('administer CiviCRM');
  }
}

/**
 * Implements hook_views_data_alter().
 */

/*
function civicrm_entity_views_data_alter($data) { }
*/

/**
 * Implements hook_schema_alter().
 *
 * Note we are just doing this in a very simple form relationship type
 * which is not defined by views at this stage. We have the problem
 * that the CiviCRM views integration uses the _data hook & would need
 * to use _data_alter hook to be compatible with entity views
 * integration
 *
 * @param $schema
 */
function civicrm_entity_schema_alter(&$schema) {
  $schema_entities = _civicrm_entity_enabled_entities();
  foreach ($schema_entities as $drupal_entity => $civicrm_entity) {
    $schema[$drupal_entity] = civicrm_entity_get_schema($drupal_entity);
  }
}

/**
 * Get schema for entities.
 *
 * This approach may not be required as using the schema_alter hook
 * (as opposed to schema_hook) seems to get around a bunch of the
 * reasons I used a separate schema.
 *
 * @param $table
 *
 * @return array
 */
function civicrm_entity_get_schema($table) {
  if (!civicrm_initialize(TRUE)) {
    return;
  }
  $schema = array();
  $schema[$table] = array(
    'description' => 'The base table for ' . $table,
    'primary key' => array(
      'id',
    ),
    'fields' => array(),
  );
  $civicrm_entity = substr($table, 8);
  $fields = civicrm_api($civicrm_entity, 'getfields', array(
    'version' => 3,
  ));
  $fields = $fields['values'];
  foreach ($fields as $fieldname => $field_spec) {
    if (empty($field_spec['name'])) {
      continue;
    }
    $unique_name = empty($field_spec['uniqueName']) ? $fieldname : $field_spec['uniqueName'];
    $schema[$table]['fields'][$unique_name] = array(
      'real_field' => $field_spec['name'],
      'description' => _civicrm_entity_get_title($field_spec),
      'unsigned' => TRUE,
      'not null' => TRUE,
    ) + civicrm_entity_get_field_type($field_spec);
  }
  return empty($schema[$table]) ? array() : $schema[$table];
}

/**
 * Please document this function.
 *
 * @param $field_spec
 *
 * @return array
 */
function civicrm_entity_get_field_type($field_spec) {
  if ($field_spec['name'] == 'id') {
    return array(
      'type' => 'serial',
    );
  }
  switch ($field_spec['type']) {
    case CRM_Utils_Type::T_INT:
    case CRM_Utils_Type::T_BOOLEAN:
      return array(
        'type' => 'integer',
      );
    case CRM_Utils_Type::T_MONEY:
    case CRM_Utils_Type::T_FLOAT:
      return array(
        'type' => 'float',
      );
    case CRM_Utils_Type::T_TEXT:
    case CRM_Utils_Type::T_STRING:
    case CRM_Utils_Type::T_LONGTEXT:
    case CRM_Utils_Type::T_CCNUM:
    case CRM_Utils_Type::T_EMAIL:
    case CRM_Utils_Type::T_URL:
      return array(
        'type' => 'text',
      );
    case CRM_Utils_Type::T_DATE:
    case CRM_Utils_Type::T_TIME:
      return array(
        'type' => 'varchar',
        'mysql_type' => 'datetime',
      );
    case CRM_Utils_Type::T_ENUM:
      return array(
        'type' => 'varchar',
        'mysql_type' => 'enum',
      );
    case CRM_Utils_Type::T_BLOB:
    case CRM_Utils_Type::T_MEDIUMBLOB:
      return array(
        'type' => 'blob',
      );
    case CRM_Utils_Type::T_TIMESTAMP:
      return array(
        'type' => 'varchar',
        'mysql_type' => 'timestamp',
      );
  }
  return array(
    'type' => $field_spec['type'],
  );
}

/**
 * Here we declare selected CiviCRM entities to Drupal.
 *
 * This is necessary for entity module to pick them up.
 */
function civicrm_entity_entity_info() {
  $entities = _civicrm_entity_enabled_entities();
  foreach ($entities as $drupal_entity => $civicrm_entity) {
    $info[$drupal_entity] = array(
      'description' => $civicrm_entity,
      'optional' => TRUE,
      'label' => "CiviCRM " . ucwords($civicrm_entity),
      'module' => 'civicrm_entity',
      'controller class' => 'CivicrmEntityController',
      'metadata controller class' => 'CivicrmEntityMetadataController',
      'views controller class' => 'CiviCRMEntityDefaultViewsController',
      'ui class' => 'RulesDataUIEntity',
      'fieldable' => TRUE,
      'extra fields controller class' => 'EntityDefaultExtraFieldsController',
      'access callback' => 'civicrm_entity_access',
      'admin ui' => array(
        'path' => $drupal_entity,
        'controller class' => 'CivicrmEntityUIController',
        'file' => 'civicrm_entity_controller.inc',
      ),
      'bundles' => array(
        $drupal_entity => array(
          'label' => t('CiviCRM @entity', array(
            '@entity' => ucwords($civicrm_entity),
          )),
          'admin' => array(
            'path' => 'admin/structure/types/manage/' . $drupal_entity,
            'access arguments' => array(
              'administer CiviCRM',
            ),
          ),
        ),
      ),
      'view modes' => array(
        'full' => array(
          'label' => t('Default'),
          'custom settings' => TRUE,
        ),
      ),
      'entity keys' => array(
        'id' => 'id',
        'label' => _civicrm_entity_labels($drupal_entity),
      ),
      'base table' => $drupal_entity,
    );
    $label_callback = 'civicrm_entity_' . $drupal_entity . '_label_callback';
    if (function_exists($label_callback)) {
      $info[$drupal_entity]['label callback'] = $label_callback;
    }
  }

  // OK - so we are not doing this for the ones declared in views
  // until more testing has been done.
  $info['civicrm_relationship_type']['views controller class'] = 'CiviCRMEntityDefaultViewsController';
  return $info;
}

/**
 * Here we declare Selected CiviCRM entities fields to Drupal.
 *
 * Some trickiness here as declaring the 'schema' via our special civi
 * schema function seems to cause fields to be declared twice if we us
 * property_info rather than property_info_alter.
 *
 * At the moment civicrm_relationship_type is the only entity being
 * managed through 'our' schema.
 *
 * @param $info
 *
 * @return
 */
function civicrm_entity_entity_property_info_alter(&$info) {
  if (!civicrm_initialize(TRUE)) {
    return;
  }

  // We'll start with a few basic entities but we could get them the
  // same way the API explorer does.
  $entities = _civicrm_entity_enabled_entities();
  foreach ($entities as $drupal_entity => $civicrm_entity) {
    $info[$drupal_entity]['properties'] = _civicrm_entity_getproperties($civicrm_entity, 'property_info');

    // $info[$drupal_entity]['bundles'] = array();
  }

  // This makes the drupal user available when chaining from a rule.
  $info['civicrm_contact']['properties']['civicrm_user'] = array(
    'label' => 'Drupal User',
    'description' => 'Drupal User for contact',
    'type' => 'user',
  );

  // Attach a CiviCRM Contact property to drupal users.
  $info['user']['properties']['civicrm_contact'] = array(
    'label' => 'CiviCRM Contact',
    'description' => 'CiviCRM Contact for user',
    'type' => 'civicrm_contact',
    'field' => FALSE,
    'translatable' => FALSE,
    'getter callback' => 'civicrm_entity_user_contact_get',
  );
  return $info;
}

/**
 * Whitelist of enabled entities. We don't have a compelling reason for not including all entities
 * but some entities are fairly non-standard and of course the rule hook would instantiate rules
 * more often if all were enabled.
 *
 * The whitelist approach is mostly out of caution
 *
 * @return array of enabled entities keyed by the drupal entity name
 */
function _civicrm_entity_enabled_entities() {
  $whitelist = array(
    'civicrm_address' => 'address',
    'civicrm_event' => 'event',
    'civicrm_contact' => 'contact',
    'civicrm_contribution' => 'contribution',
    'civicrm_participant' => 'participant',
    'civicrm_relationship' => 'relationship',
    'civicrm_relationship_type' => 'relationship_type',
    'civicrm_activity' => 'activity',
    'civicrm_entity_tag' => 'entity_tag',
    'civicrm_membership' => 'membership',
    'civicrm_membership_type' => 'membership_type',
    'civicrm_group' => 'group',
    'civicrm_grant' => 'grant',
    'civicrm_tag' => 'tag',
  );

  //dirty check for whether financialType exists
  if (!method_exists('CRM_Contribute_PseudoConstant', 'contributionType')) {
    $whitelist['civicrm_financial_type'] = 'financial_type';
  }
  return $whitelist;
}

/**
 * Please document this function.
 */
function _civicrm_entity_chained_fks() {
  return array(
    'CRM_Contact_DAO_Contact' => 'contact',
    'CRM_Event_DAO_Event' => 'event',
  );
}

/**
 * Provide label (column) for each entity types.
 *
 * @TODO Use the CiviCRM 4.5 getlist function - possibly ported into this module to support 4.4
 *
 * @see http://api.drupal.org/api/drupal/modules!system!system.api.php/function/hook_entity_info/7
 *
 * @param $entity
 *
 * @return
 */
function _civicrm_entity_labels($entity) {
  $labels = array(
    'civicrm_activity' => 'subject',
    'civicrm_address' => 'address_name',
    'civicrm_contact' => 'display_name',
    'civicrm_contribution' => 'source',
    'civicrm_event' => 'title',
    'civicrm_participant' => 'source',
    'civicrm_relationship' => 'description',
    'civicrm_relationship_type' => 'description',
    // OK, I'm just putting in something that won't error for now.
    'civicrm_entity_tag' => 'tag_id',
    'civicrm_financial_type' => 'description',
    // Ditto.
    'civicrm_membership' => 'id',
    'civicrm_membership_type' => 'name',
    'civicrm_group' => 'name',
    'civicrm_grant' => 'id',
    'civicrm_tag' => 'name',
  );
  return $labels[$entity];
}
function _civicrm_entity_get_title($field_specs, $entity_type = '') {
  if (!empty($entity_type)) {
    $label_field = _civicrm_entity_labels($entity_type);
  }
  else {
    $label_field = 'title';
  }
  if (empty($field_specs[$label_field])) {
    if (array_key_exists('label', $field_specs)) {
      $label_field = 'label';
    }
    else {
      if (array_key_exists('name', $field_specs)) {
        $label_field = 'name';
      }
    }
  }
  $title = empty($field_specs[$label_field]) ? 'Title not defined in schema' : $field_specs[$label_field];
  return $title;
}

/**
 * Label callback for civicrm_contact entity type.
 *
 *   drupal_alter('civicrm_entity_' . $entity, $, $alterable2, $context);
 *
 * @param $entity
 * @param $entity_type
 *
 * @return null|string
 */
function civicrm_entity_civicrm_contact_label_callback($entity, $entity_type) {
  $label = isset($entity->display_name) ? $entity->display_name : '';

  // drupal_alter('civicrm_entity_contact_label', $label, $entity);
  if (isset($entity->email) && !empty($entity->email)) {
    $label = t('!label <!email>', array(
      '!label' => $label,
      '!email' => $entity->email,
    ));
  }
  elseif (isset($entity->phone) && !empty($entity->phone)) {
    $label = t('!label <!phone>', array(
      '!label' => $label,
      '!phone' => $entity->phone,
    ));
  }
  return $label;
}

/**
 * Calculate fields for entities
 *
 * @param $civicrm_entity
 * @param string $context
 *
 * @return array
 */
function _civicrm_entity_getproperties($civicrm_entity, $context = '') {
  $info = array();
  if ($civicrm_entity == 'contact') {
    $info['civi_user'] = array(
      'label' => 'Drupal User',
      'type' => 'user',
    );
  }
  $fields = civicrm_api($civicrm_entity, 'getfields', array(
    'version' => 3,
    'action' => 'create',
  ));
  foreach ($fields['values'] as $fieldname => $field_specs) {

    // Type is empty for custom fields - we should sort that out but
    // skipping for now we are only doing 'integers' at this stage.
    $types = array(
      1 => 'integer',
      2 => 'text',
      32 => 'text',
      16 => 'integer',
    );
    if (!empty($field_specs['type']) && array_key_exists($field_specs['type'], $types)) {
      $info[$fieldname] = array(
        'label' => _civicrm_entity_get_title($field_specs),
        'type' => $types[$field_specs['type']],
        'sanitize' => 'check_plain',
        'setter callback' => 'entity_property_verbatim_set',
      );
      if (!empty($field_specs['api.required'])) {
        $info[$fieldname]['required'] = TRUE;
      }
      if ($field_specs['type'] == 16) {
        $info[$fieldname]['size'] = 'tiny';
      }

      // This is a semi-reliable way of distinguishing 'real' fields
      // from pseudo fields and custom fields and impacts on views
      // (which is only implemented in a very minor way at this stage
      // because it is 'blocked' by the default views install using
      // hook_views_data rather than hook_views_data_alter.
      if (!empty($field_specs['name'])) {
        $info[$fieldname]['schema field'] = $field_specs['name'];
      }

      // We will add contact as a related entity for FK references to
      // contact. This could be expanded to all FKs e.g event_id in
      // Participant. Could load the event at the moment we are being
      // cautious.
      if (CRM_Utils_Array::value('FKClassName', $field_specs)) {
        $fks = _civicrm_entity_chained_fks();
        if (array_key_exists($field_specs['FKClassName'], $fks)) {
          $fks_entity = $fks[$field_specs['FKClassName']];
          $info[$fieldname . '_' . $fks_entity] = array(
            'label' => _civicrm_entity_get_title($field_specs),
            'type' => 'civicrm_' . $fks_entity,
            'property_info' => array(
              'field' => $fieldname,
              'entity' => $fks_entity,
            ),
            'getter callback' => 'civicrm_entity_metadata_civicrm_entity_get_properties',
          );
        }
      }

      // @TODO We are treating contact as the only possible entity
      // which is not great - need to figure out better approach - can
      // we have more than one? Define 'civicrm_entity'?
      if ($fieldname == 'entity_id') {
        $fks_entity = 'contact';
        $info[$fieldname . '_' . $fks_entity] = array(
          'label' => _civicrm_entity_get_title($field_specs),
          'type' => 'civicrm_' . $fks_entity,
          'property_info' => array(
            'field' => $fieldname,
            'entity' => $fks_entity,
          ),
          'getter callback' => 'civicrm_entity_metadata_civicrm_entity_get_properties',
        );
      }
      if (!empty($field_specs['options'])) {

        // $info[$fieldname]['type'] = 'list<integer>';
        $info[$fieldname]['options list'] = '_civicrm_entity_rules_attach_options';
        $info[$fieldname]['options data'] = $field_specs['options'];
        if ($context == 'property_info') {
          $info[$fieldname]['property defaults']['options list'] = $field_specs['options'];
        }
      }
      $info['type'] = array(
        'label' => t('Type'),
        'description' => t('Dummy field for bundle key'),
        'type' => 'token',
        'setter callback' => 'entity_property_verbatim_set',
        'required' => FALSE,
        'property defaults' => array(
          'civicrm_' . strtolower($civicrm_entity),
        ),
      );
    }
  }
  return $info;
}
function civicrm_entity_form($form, &$form_state, $entity, $op, $entity_type) {

  // Add the field related form elements.
  $form_state['entity'] = new CivicrmEntity((array) $entity, $entity_type);
  field_attach_form($entity_type, $entity, $form, $form_state);

  //not quite sure why these are not being added but ....
  $wrapper = entity_metadata_wrapper($entity_type);
  foreach ($wrapper as $name => $child) {
    $info = $child
      ->info();
    $form[$name] = array(
      '#type' => $info['type'],
      '#title' => $info['label'],
      '#description' => !empty($info['description']) ? $info['description'] : '',
    );
  }
  $form['actions'] = array(
    '#type' => 'container',
    '#attributes' => array(
      'class' => array(
        'form-actions',
      ),
    ),
    '#weight' => 400,
  );
  $form['#validate'] = array();
  $form['#submit'] = array();
  $form['#validate'][] = 'civicrm_entity_form_validate';
  $form['#submit'][] = 'civicrm_entity_form_submit';

  // We add the form's #submit array to this button along with the actual submit
  // handler to preserve any submit handlers added by a form callback_wrapper.
  $submit = array();
  if (!empty($form['#submit'])) {
    $submit += $form['#submit'];
  }
  $form['actions']['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Save'),
    '#submit' => $submit + array(),
  );
  return $form;
}

/**
 * Form API validate callback for the entity form
 */
function civicrm_entity_form_validate(&$form, &$form_state) {
  $entity = $form_state['entity'];
  $entity_type = $form_state['entity_type'];

  // Notify field widgets to validate their data.
  field_attach_form_validate($entity_type, $entity, $form, $form_state);
}

/**
 * Form API submit callback for the entity form.
 *
 */
function civicrm_entity_form_submit(&$form, &$form_state) {
  $entity_type = $form_state['entity_type'];

  //@todo - what is the correct way to do this - taken from bangpound but then I had to do casting
  $entity = entity_ui_controller($entity_type)
    ->entityFormSubmitBuildEntity($form, $form_state);
  $entity = new CivicrmEntity((array) $entity, $entity_type);

  // Add in created and changed times.
  if ($entity->is_new = isset($entity->is_new) ? $entity->is_new : 0) {
    $entity->created = time();
  }
  $entity->changed = time();
  $entity
    ->save();
  $t_args = array(
    '%label' => entity_label($entity_type, $entity),
  );
  drupal_set_message(t('Drupal fields on %label have been updated.', $t_args));
}

/**
 * Implement getter callback.
 *
 * NB this is in a separate file called callbacks.inc in entity module
 * - I couldn't see how it was loaded so maybe the name has some
 * magic?
 *
 * @param $data
 * @param $options
 * @param $name
 * @param $type
 * @param $info
 *
 * @return object
 */
function civicrm_entity_metadata_civicrm_entity_get_properties($data, $options, $name, $type, $info) {
  $entity = civicrm_api($info['property_info']['entity'], 'get', array(
    'version' => 3,
    'id' => $data->{$info}['property_info']['field'],
    'sequential' => 1,
  ));
  return (object) $entity['values'][0];
}

/**
 * Condition Drupal User Account exists for contact.
 *
 * @param array $contact
 *   Contact array.
 *
 * @return object
 *   Drupal user object if success, FALSE on fail.
 */
function civicrm_entity_user_exists($contact) {
  return civicrm_entity_action_load_user($contact);
}

/**
 * Condition Drupal User Account can be created for contact (creates contact).
 *
 * @param array $contact
 *   contact array
 *
 * @return object
 *   Drupal user object if success, FALSE on Fail
 */
function civicrm_entity_user_creatable($contact) {
  return civicrm_entity_action_create_user($contact, TRUE);
}

/**
 * Condition Drupal User Account can be created or exists for contact.
 *
 * Ccreates contact if appropriate.
 *
 * @param array $contact
 *   contact array
 *
 * @return mixed
 *   Drupal user object if success, FALSE on fail.
 */
function civicrm_entity_user_exists_or_creatable($contact) {
  return civicrm_entity_action_load_create_user($contact);
}

/**
 * Given a contact object return the Drupal user.
 *
 * @param StdClass $entity
 *   Contact Std Object
 *
 * @return object
 *   Drupal user object.
 */
function civicrm_entity_action_load_user($entity) {
  $domain_id = civicrm_api('domain', 'getvalue', array(
    'version' => 3,
    'return' => 'id',
    'current_domain' => TRUE,
  ));
  $params = array(
    'version' => 3,
    'contact_id' => $entity->id,
    'return' => 'uf_id',
    'domain_id' => $domain_id,
  );
  $contact = civicrm_api('uf_match', 'getsingle', $params);
  if (empty($contact['is_error'])) {
    return array(
      'civicrm_user' => user_load($contact['uf_id']),
    );
  }
}

/**
 * Given a contact object, load or create then return a drupal user.
 *
 * @param object $contact
 *   CiviCRM Contact Object
 *
 * @param $is_active
 * @param bool $notify
 * @param bool $signin
 *
 * @throws Exception
 * @return object
 *   $user Drupal user object or FALSE.
 */
function civicrm_entity_action_create_user($contact, $is_active, $notify = FALSE, $signin = FALSE) {
  if (!is_array($contact)) {

    // Perhaps we should be accepting object rather than array here?
    $contact = (array) $contact;
  }

  // We'll use the civicrm sync mechanism to see if Civi can match the
  // contact to an existing user.
  //
  // Don't think this is a great approach but will use for now - could
  // just create the user but no great support for that yet.
  if (empty($contact['display_name']) || empty($contact['email'])) {
    $contact = civicrm_api('contact', 'getsingle', array(
      'version' => 3,
      'id' => $contact['id'],
      'sequential' => 1,
      'return' => 'email,display_name',
    ));
  }
  if (!is_string($contact['email']) && isset($contact['email'][0]->email)) {
    $contact['email'] = $contact['email'][0]->email;
  }

  // @TODO What happens if they don't have an email at this point?
  // An email is a pre-requisite for a Drupal account, so the action
  // fails if they don't have an email.
  $params = array(
    'name' => $contact['display_name'],
    'mail' => $contact['email'],
    'email' => $contact['email'],
    'init' => $contact['email'],
  );

  // Check if the requested username is available.
  $errors = array();
  $config = CRM_Core_Config::singleton();
  $config->userSystem
    ->checkUserNameEmailExists($params, $errors);
  if (!empty($errors)) {
    foreach ($errors as $error) {
      drupal_set_message(t($error), 'error');
    }
    return FALSE;
  }
  $params['cms_name'] = $params['name'] = $user['name'] = !empty($contact['display_name']) ? $contact['display_name'] : $params['mail'];
  $params['cms_pass'] = $user['pass'] = substr(str_shuffle("abcefghijklmnopqrstuvwxyz"), 0, 8);
  $params['status'] = $is_active;
  if ($notify) {
    $params['notify'] = TRUE;
  }
  $params['roles'] = array(
    DRUPAL_AUTHENTICATED_RID => 'authenticated user',
  );

  // Set $config->inCiviCRM = TRUE to prevent creating a duplicate
  // contact from user_save().
  $config = CRM_Core_Config::singleton();
  $config->inCiviCRM = TRUE;
  $user_object = user_save('', $params);
  $user_object->password = $user['pass'];
  $config->inCiviCRM = FALSE;

  // If selected in action configuration, notify the newly created
  // user & send registration link. Does not contain password in D7.
  if ($notify) {
    drupal_mail('user', 'register_no_approval_required', $params['mail'], NULL, array(
      'account' => $user_object,
    ), variable_get('site_mail', 'noreply@example..com'));
  }

  // CiviCRM doesn't do this when created off CiviCRM Form.
  //
  // Note that we 'pretend' to be logging in to make it do a ufmatch
  // on just the email.
  CRM_Core_BAO_UFMatch::synchronizeUFMatch($user_object, $user_object->uid, $contact['email'], 'drupal', NULL, NULL, TRUE);

  // If selected in action configuration, automatically sign in the
  // current user.
  if ($signin) {
    global $user;
    $user = user_load($user_object->uid);
    watchdog('civicrm_entity', 'User %name logged in via CiviCRM Entity rule execution.', array(
      '%name' => $user->name,
    ), WATCHDOG_INFO);
    $form_values = array(
      'uid' => $user->uid,
    );
    user_login_finalize($form_values);
  }
  return array(
    'civicrm_user' => $user_object,
  );
}
function civicrm_entity_query($type, $property, $value, $limit) {
  $return = entity_load($type, $ids = FALSE, array(
    $property => $value,
    'options' => array(
      'limit' => $limit,
    ),
  ));
  return array(
    'entity_fetched' => array_values($return),
  );
}

/**
 * Info alteration callback for the entity query action.
 * @todo this is copy of rules_action_entity_query_info_alter
 *
 * @param $element_info
 * @param RulesAbstractPlugin $element
 */
function civicrm_entity_query_info_alter(&$element_info, RulesAbstractPlugin $element) {
  $element->settings += array(
    'type' => NULL,
    'property' => NULL,
  );
  if ($element->settings['type']) {
    $element_info['parameter']['property']['options list'] = 'rules_action_entity_query_property_options_list';
    if ($element->settings['property']) {
      $wrapper = rules_get_entity_metadata_wrapper_all_properties($element);
      if (isset($wrapper->{$element->settings['property']}) && ($property = $wrapper->{$element->settings['property']})) {
        $element_info['parameter']['value']['type'] = $property
          ->type();
        $element_info['parameter']['value']['options list'] = $property
          ->optionsList() ? 'rules_action_entity_query_value_options_list' : FALSE;
      }
    }
  }
  $element_info['provides']['entity_fetched']['type'] = 'list<' . $element->settings['type'] . '>';
}

/**
 * Load or create user as appropriate.
 *
 * @param $entity
 * @param int $is_active
 * @param int $notify
 *
 * @return object
 */
function civicrm_entity_action_load_create_user($entity, $is_active = 0, $notify = 0) {
  if ($user = civicrm_entity_action_load_user($entity)) {
    if ($is_active && !$user['civicrm_user']->status) {
      $user['civicrm_user']->status = $is_active;
      $user['civicrm_user']->save;
    }
    return $user;
  }
  return civicrm_entity_action_create_user((array) $entity, $is_active, $notify);
}

/**
 * @param $user
 *
 * @param null $email
 *
 * @return mixed
 */
function civicrm_entity_action_load_create_contact($user, $email = NULL) {
  try {
    return civicrm_entity_action_load_contact($user);
  } catch (CiviCRM_API3_Exception $e) {
    $ufMatch = CRM_Core_BAO_UFMatch::synchronizeUFMatch($user, $user->uid, $email ? $email : $user->mail, 'Drupal', FALSE, 'Individual');
    $entities = entity_load('civicrm_contact', $ufMatch->contact_id);
    return array(
      'civicrm_contact' => reset($entities),
    );
  }
}

/**
 * @param $user
 *
 * @return mixed
 * @throws CiviCRM_API3_Exception
 */
function civicrm_entity_action_load_contact($user) {
  if (!civicrm_initialize()) {
    return;
  }
  $contact_id = civicrm_api3('uf_match', 'getvalue', array(
    'uf_id' => $user->uid,
    'return' => 'contact_id',
    'domain_id' => CRM_Core_Config::domainID(),
  ));
  $entities = entity_load('civicrm_contact', array(
    $contact_id,
  ));
  return array(
    'civicrm_contact' => reset($entities),
  );
}

/**
 * Implement the post hook and fire the corresponding rules event.
 *
 * @param $op
 * @param $object_name
 * @param $object_id
 * @param $object_ref
 */
function civicrm_entity_civicrm_post($op, $object_name, $object_id, &$object_ref) {
  if (!module_exists('rules')) {
    return;
  }
  $contact_types = array(
    'Individual',
    'Household',
    'Organization',
  );
  if (in_array($object_name, $contact_types)) {
    $object_name = 'Contact';
  }
  $valid_objects = _civicrm_entity_enabled_entities();
  $entity_name = _civicrm_entity_get_entity_name_from_camel($object_name);
  if (!in_array($entity_name, $valid_objects, TRUE)) {
    return;
  }
  $event_name = NULL;
  switch ($op) {
    case 'create':
    case 'edit':
    case 'delete':
      $event_name = 'civicrm_' . $entity_name . "_{$op}";
      break;
    default:
      break;
  }
  if ($entity_name == 'entity_tag') {

    // Argh entity tag is completely non-standard!!!
    // @see CRM-11933
    foreach ($object_ref[0] as $entity_tag) {
      $object = new CRM_Core_BAO_EntityTag();
      $object->entity_id = $entity_tag;
      $object->entity_table = 'civicrm_contact';
      $object->tag_id = $object_id;
      if ($object
        ->find(TRUE)) {

        // This find is probably not necessary but until more testing
        // on the tag create is done I will.
        rules_invoke_event($event_name, $object);
      }
    }
  }
  else {
    if ($event_name) {
      rules_invoke_event($event_name, $object_ref);
    }
  }
}

/**
 * Convert possibly camel name to underscore separated entity name.
 *
 * @see _civicrm_api_get_entity_name_from_camel()
 *
 * @TODO Why don't we just call the above function directly?
 * Because the function is officially 'likely' to change as it is an internal api function and calling api functions directly is explicitly not supported
 *
 * @param string $entity
 *   Entity name in various formats e.g:
 *     Contribution => contribution,
 *     OptionValue => option_value,
 *     UFJoin => uf_join.
 *
 * @return string
 *   $entity entity name in underscore separated format
 */
function _civicrm_entity_get_entity_name_from_camel($entity) {
  if ($entity == strtolower($entity)) {
    return $entity;
  }
  else {
    $entity = ltrim(strtolower(str_replace('U_F', 'uf', preg_replace('/(?=[A-Z])/', '_$0', $entity))), '_');
  }
  return $entity;
}

/**
 * Load contact entity according to user id.
 *
 * @param $data
 * @param array $options
 * @param $name
 * @param $type
 * @param $info
 *
 * @return null
 */
function civicrm_entity_user_contact_get($data, array $options, $name, $type, $info) {
  if (!module_exists('civicrm') || !function_exists('civicrm_initialize')) {
    return;
  }
  if (!civicrm_initialize()) {
    return;
  }
  $domain_id = civicrm_api('domain', 'getvalue', array(
    'version' => 3,
    'return' => 'id',
    'current_domain' => TRUE,
  ));
  $contact = civicrm_api('uf_match', 'getsingle', array(
    'version' => 3,
    'return' => 'contact_id',
    'uf_id' => $data->uid,
    'domain_id' => $domain_id,
  ));
  if (!empty($contact['contact_id'])) {
    $entity = entity_load('civicrm_contact', array(
      $contact['contact_id'],
    ));
    return $entity[$contact['contact_id']];
  }
  else {
    return NULL;
  }
}

/**
 * Implements hook_rules_action_info_alter
 * I can't seem to get my info_alter function called by hook so am doing this hacky intercept
 * to call my function (& then go back to the main function if not a CiviCRM entity
 */
function civicrm_entity_rules_action_info_alter(&$info) {
  $info['entity_create']['callbacks']['info_alter'] = 'civicrm_entity_rules_action_entity_create_info_alter';
}

/**
 * Info alteration callback for the entity create action.
 * Here we add a tonne of fields to civicrm entity create
 */
function civicrm_entity_rules_action_entity_create_info_alter(&$element_info, RulesAbstractPlugin $element) {
  if (empty($element->settings['type']) || substr($element->settings['type'], 0, 8) != 'civicrm_') {
    module_load_include('inc', 'rules', 'modules/entity.eval');
    return rules_action_entity_create_info_alter($element_info, $element);
  }
  if (!empty($element->settings['type']) && entity_get_info($element->settings['type'])) {
    $wrapper = entity_metadata_wrapper($element->settings['type']);

    // Add the data type's needed parameter for loading to the parameter info.
    foreach ($wrapper as $name => $child) {
      $info = $child
        ->info();
      $info += array(
        'type' => 'text',
      );

      // Prefix parameter names to avoid name clashes with existing parameters.
      $element_info['parameter']['param_' . $name] = array_intersect_key($info, array_flip(array(
        'type',
        'label',
        'description',
      )));
      $element_info['parameter']['param_' . $name]['options list'] = $child
        ->optionsList() ? 'rules_action_entity_parameter_options_list' : FALSE;
      $element_info['parameter']['param_' . $name]['optional'] = empty($info['required']);
    }
    unset($element_info['parameter']['type']);
    foreach (civicrm_entity_get_custom_fields('contribution') as $fieldname => $field) {
      $element_info['parameter'][$fieldname] = array(
        'type' => $field['type'],
        'label' => $field['label'],
        'optional' => TRUE,
        'default mode' => 'selector',
      );
    }
    $element_info['provides']['entity_created']['type'] = $element->settings['type'];
    if (($bundleKey = $wrapper
      ->entityKey('bundle')) && isset($element->settings['param_' . $bundleKey])) {
      $element_info['provides']['entity_created']['bundle'] = $element->settings['param_' . $bundleKey];
    }
  }
}

/**
 * @param $entity
 */
function civicrm_entity_get_custom_fields($entity, $types = array(
  'Integer' => 'integer',
  'String' => 'text',
  'Date' => 'date',
)) {
  if (!civicrm_initialize()) {
    return array();
  }
  $fields = civicrm_api3($entity, 'getfields', array(
    'action' => 'create',
    'getoptions' => TRUE,
  ));
  $fields = $fields['values'];
  foreach ($fields as $field_name => $field) {
    if (substr($field_name, 0, 7) != 'custom_' || !in_array($field['data_type'], array_keys($types))) {
      unset($fields[$field_name]);
    }
    else {
      $fields[$field_name]['type'] = $types[$field['data_type']];
    }
  }
  return $fields;
}

Functions

Namesort descending Description
civicrm_entity_access Entity access callback.
civicrm_entity_action_create_user Given a contact object, load or create then return a drupal user.
civicrm_entity_action_load_contact
civicrm_entity_action_load_create_contact
civicrm_entity_action_load_create_user Load or create user as appropriate.
civicrm_entity_action_load_user Given a contact object return the Drupal user.
civicrm_entity_civicrm_contact_label_callback Label callback for civicrm_contact entity type.
civicrm_entity_civicrm_post Implement the post hook and fire the corresponding rules event.
civicrm_entity_entity_info Here we declare selected CiviCRM entities to Drupal.
civicrm_entity_entity_property_info_alter Here we declare Selected CiviCRM entities fields to Drupal.
civicrm_entity_form
civicrm_entity_form_submit Form API submit callback for the entity form.
civicrm_entity_form_validate Form API validate callback for the entity form
civicrm_entity_get_custom_fields
civicrm_entity_get_field_type Please document this function.
civicrm_entity_get_schema Get schema for entities.
civicrm_entity_menu_alter Implements hook_menu_later().
civicrm_entity_metadata_civicrm_entity_get_properties Implement getter callback.
civicrm_entity_permission Implements hook_permission().
civicrm_entity_query
civicrm_entity_query_info_alter Info alteration callback for the entity query action. @todo this is copy of rules_action_entity_query_info_alter
civicrm_entity_rules_action_entity_create_info_alter Info alteration callback for the entity create action. Here we add a tonne of fields to civicrm entity create
civicrm_entity_rules_action_info_alter Implements hook_rules_action_info_alter I can't seem to get my info_alter function called by hook so am doing this hacky intercept to call my function (& then go back to the main function if not a CiviCRM entity
civicrm_entity_schema_alter Implements hook_schema_alter().
civicrm_entity_user_contact_get Load contact entity according to user id.
civicrm_entity_user_creatable Condition Drupal User Account can be created for contact (creates contact).
civicrm_entity_user_exists Condition Drupal User Account exists for contact.
civicrm_entity_user_exists_or_creatable Condition Drupal User Account can be created or exists for contact.
_civicrm_entity_chained_fks Please document this function.
_civicrm_entity_enabled_entities Whitelist of enabled entities. We don't have a compelling reason for not including all entities but some entities are fairly non-standard and of course the rule hook would instantiate rules more often if all were enabled.
_civicrm_entity_getproperties Calculate fields for entities
_civicrm_entity_get_entity_name_from_camel Convert possibly camel name to underscore separated entity name.
_civicrm_entity_get_title
_civicrm_entity_labels Provide label (column) for each entity types.