You are here

salesforce_api.module in Salesforce Suite 7

Defines an API that enables modules to interact with the Salesforce server.

1. Get your security token. 2. Get the Toolkit. 3. Download your WSDL.

File

salesforce_api/salesforce_api.module
View source
<?php

/**
 * @file
 * Defines an API that enables modules to interact with the Salesforce server.
 *
 * 1. Get your security token.
 * 2. Get the Toolkit.
 * 3. Download your WSDL.
 */

// Define directory paths for the Toolkit and WSDL files.
define('SALESFORCE_DIR', drupal_get_path('module', 'salesforce_api'));
define('SALESFORCE_DIR_TOOLKIT', SALESFORCE_DIR . '/toolkit');
define('SALESFORCE_DIR_SOAPCLIENT', SALESFORCE_DIR_TOOLKIT . '/soapclient');
define('SALESFORCE_DIR_WSDL', SALESFORCE_DIR . '/wsdl');

// Define Drupal paths for various parts of the Salesforce UI.
define('SALESFORCE_PATH_ADMIN', 'admin/config/salesforce');
define('SALESFORCE_PATH_FIELDMAPS', SALESFORCE_PATH_ADMIN . '/fieldmap');
define('SALESFORCE_PATH_DEMO', SALESFORCE_PATH_ADMIN . '/demo');
define('SALESFORCE_PATH_OBJECT', SALESFORCE_PATH_ADMIN . '/object');

// Salesforce schema properties.
// Not all these are in use yet.
define('SALESFORCE_FIELD_CREATEABLE', 1);
define('SALESFORCE_FIELD_DEFAULTEDONCREATE', 2);
define('SALESFORCE_FIELD_DEPRECATEDANDHIDDEN', 4);
define('SALESFORCE_FIELD_IDLOOKUP', 8);
define('SALESFORCE_FIELD_NILLABLE', 16);
define('SALESFORCE_FIELD_RESTRICTEDPICKLIST', 32);
define('SALESFORCE_FIELD_UNIQUE', 64);
define('SALESFORCE_FIELD_UPDATEABLE', 128);

// Bitmasks for the fields above
define('SALESFORCE_FIELD_SOURCE_ONLY', ~SALESFORCE_FIELD_CREATEABLE);
define('SALESFORCE_FIELD_REQUIRED', SALESFORCE_FIELD_CREATEABLE & ~SALESFORCE_FIELD_NILLABLE & ~SALESFORCE_FIELD_DEFAULTEDONCREATE);
define('SALESFORCE_FIELD_OPTIONAL', ~SALESFORCE_FIELD_REQUIRED & ~SALESFORCE_FIELD_SOURCE_ONLY);

// Define reporting levels for watchdog messages.
define('SALESFORCE_LOG_NONE', 0);
define('SALESFORCE_LOG_SOME', 5);
define('SALESFORCE_LOG_ALL', 10);
if (!function_exists('is_sfid')) {

  // Without a roundtrip to salesforce.com, checking the string length is the
  // best we can do to verify a SalesForce ID.
  function is_sfid($sfid) {
    if (strlen($sfid) == 15 || strlen($sfid) == 18) {
      return TRUE;
    }
    return FALSE;
  }
}

/**
 * Implements hook_menu().
 */
function salesforce_api_menu() {
  $items[SALESFORCE_PATH_ADMIN] = array(
    'title' => 'Salesforce',
    'description' => 'Administer settings related to your Salesforce integration.',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'salesforce_api_settings_form',
    ),
    'access arguments' => array(
      'administer salesforce',
    ),
    'type' => MENU_NORMAL_ITEM,
    'file' => 'salesforce_api.admin.inc',
  );
  $items[SALESFORCE_PATH_ADMIN . '/settings'] = array(
    'title' => 'Settings',
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'weight' => -10,
    'file' => 'salesforce_api.admin.inc',
  );
  $items[SALESFORCE_PATH_DEMO] = array(
    'title' => 'Test/Demo',
    'page callback' => 'salesforce_api_demo',
    'access arguments' => array(
      'administer salesforce',
    ),
    'type' => MENU_LOCAL_TASK,
    'file' => 'salesforce_api.admin.inc',
  );
  $items[SALESFORCE_PATH_FIELDMAPS] = array(
    'title' => 'Fieldmaps',
    'description' => 'Administer fieldmap relationships between Drupal objects and Salesforce objects.',
    'page callback' => 'salesforce_api_fieldmap_admin',
    'access arguments' => array(
      'administer salesforce',
    ),
    'type' => MENU_LOCAL_TASK,
    'file' => 'salesforce_api.admin.inc',
  );
  $items[SALESFORCE_PATH_FIELDMAPS . '/list'] = array(
    'title' => 'List',
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'access arguments' => array(
      'administer salesforce',
    ),
    'weight' => 0,
    'file' => 'salesforce_api.admin.inc',
  );
  $items[SALESFORCE_PATH_FIELDMAPS . '/add'] = array(
    'title' => 'Add',
    'description' => 'Create a new fieldmap.',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'salesforce_api_fieldmap_add_form',
    ),
    'access arguments' => array(
      'administer salesforce',
    ),
    'type' => MENU_LOCAL_TASK,
    'weight' => 10,
    'file' => 'salesforce_api.admin.inc',
  );
  $items[SALESFORCE_PATH_FIELDMAPS . '/%/edit'] = array(
    'title' => 'Edit fieldmap',
    'description' => 'Edit an existing fieldmap.',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'salesforce_api_fieldmap_edit_form',
      4,
    ),
    'access arguments' => array(
      'administer salesforce',
    ),
    'type' => MENU_CALLBACK,
    'file' => 'salesforce_api.admin.inc',
  );
  $items[SALESFORCE_PATH_FIELDMAPS . '/%/clone'] = array(
    'title' => 'Clone a fieldmap',
    'description' => 'Clone an existing fieldmap.',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'salesforce_api_fieldmap_clone_form',
      4,
    ),
    'access arguments' => array(
      'administer salesforce',
    ),
    'type' => MENU_CALLBACK,
    'file' => 'salesforce_api.admin.inc',
  );
  $items[SALESFORCE_PATH_FIELDMAPS . '/%/delete'] = array(
    'title' => 'Delete fieldmap',
    'description' => 'Delete an existing fieldmap.',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'salesforce_api_fieldmap_delete_form',
      4,
    ),
    'access arguments' => array(
      'administer salesforce',
    ),
    'type' => MENU_CALLBACK,
    'file' => 'salesforce_api.admin.inc',
  );
  $items[SALESFORCE_PATH_OBJECT] = array(
    'title' => 'Object setup',
    'description' => 'Define which SalesForce objects you would like to be available in your Drupal site.',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'salesforce_api_admin_object',
    ),
    'access arguments' => array(
      'administer salesforce',
    ),
    'type' => MENU_LOCAL_TASK,
    'file' => 'salesforce_api.admin.inc',
  );
  $items[SALESFORCE_PATH_OBJECT . '/%'] = array(
    'title' => 'Object setup',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'salesforce_api_admin_object_settings',
      count(explode('/', SALESFORCE_PATH_OBJECT)),
    ),
    'access arguments' => array(
      'administer salesforce',
    ),
    'type' => MENU_CALLBACK,
    'file' => 'salesforce_api.admin.inc',
  );
  return $items;
}

/**
 * Implements hook_permission().
 */
function salesforce_api_permission() {
  return array(
    'administer salesforce' => array(
      'title' => t('Administer SalesForce'),
      'description' => t('Administer SalesForce'),
      'restrict access' => TRUE,
    ),
  );
}

/**
 * Creates an object used for communicating with the Salesforce server and
 *   performs a login to verify the API credentials.
 *
 * @param $username
 *   Username for Salesforce. An email address, most likely. If none passed,
 *     sitewide creds will be used
 * @param $password
 *   Password to Salesforce account.
 * @param $token
 *   Security token from Salesforce.
 * @param $reconnect
 *   By default, subsequent calls to this function will return the same, already
 *     connected Salesforce object as preceding calls. Setting this variable to
 *     TRUE will cause a new connection to be established instead.
 * @return
 *   The DrupalSalesforce object used to communicate with the Salesforce server
 *     if successful or FALSE if a connection could not be established.
 */
function salesforce_api_connect($username = FALSE, $password = FALSE, $token = FALSE, $reconnect = FALSE) {
  static $sf = FALSE;
  module_load_include('inc', 'salesforce_api', 'salesforce.class');

  // Return the previously connected object.
  if ($sf && !$reconnect) {
    return $sf;
  }

  // Boolean, whether we are connecting with the default website user or not.
  $default_site_user = $username == variable_get('salesforce_api_username', FALSE);

  // Load up the sitewide API credentials if no others were provided:
  $username = $username ? $username : variable_get('salesforce_api_username', '');
  $password = $password ? $password : variable_get('salesforce_api_password', '');
  $token = $token ? $token : variable_get('salesforce_api_token', '');

  // Fail early if we didn't receive an API username, password, or token.
  if (empty($username) || empty($password) || empty($token)) {
    DrupalSalesforce::watchdog(SALESFORCE_LOG_SOME, 'Connection to Salesforce
      failed because API credentials have not been set.', array(), WATCHDOG_ERROR);
    return FALSE;
  }

  // Create a new Salesforce object with the API credentials.
  $sf = new DrupalSalesforce($username, $password, $token);
  if (!is_object($sf)) {
    DrupalSalesforce::watchdog(SALESFORCE_LOG_SOME, 'Connection to Salesforce
      failed. Failed to create DrupalSalesforce wrapper.', WATCHDOG_ERROR);
    return FALSE;
  }

  // Attempt a login.
  if ($sf
    ->login()) {

    // Mimick expired password state to debug.
    // $sf->login->passwordExpired = TRUE;
    if ($sf->login->passwordExpired) {
      if ($default_site_user) {
        salesforce_api_reset_expired_password($sf);
      }
      elseif (user_access('administer salesforce')) {
        drupal_set_message(t('Your Salesforce account password expired.  Please
         <a href="https://login.salesforce.com/">login to Salesforce.com</a> and
         change your password.'), 'error');
      }
      else {
        DrupalSalesforce::watchdog(SALESFORCE_LOG_SOME, 'Connection to Salesforce
          due to expired password for @user.', array(
          '@user' => $username,
        ), WATCHDOG_ERROR);
      }
    }
  }
  else {
    DrupalSalesforce::watchdog(SALESFORCE_LOG_SOME, 'Connection to Salesforce
      failed with response @resp.', array(
      '@resp' => $sf->client
        ->getLastResponse(),
    ), WATCHDOG_ERROR);

    // Or return FALSE to indicate the failure.
    $sf = FALSE;
  }
  return $sf;
}

/**
 * Helper function for salesforce_api_connect() to reset an expired password,
 *   for the website's default salesforce user only.
 *
 * @param $sf
 *   The logged in DrupalSalesforce object with an expired password.
 * @return
 *   void
 */
function salesforce_api_reset_expired_password($sf) {

  // Append one letter and one digit to the password to make sure we meet
  // salesforce's password validation requirements.
  $new_password = user_password() . 'z9';

  // setPassword() may throw InvalidIdFault or UnexpectedErrorFault exceptions.
  $sf->client
    ->setPassword($sf->login->userId, $new_password);
  variable_set('salesforce_api_password', $new_password);

  // Salesforce changes the security token when the password gets changed and
  // sends an email with the new security token.  The new security token can
  // not be retrieved via the API.
  variable_del('salesforce_api_token');

  // Log the event and alert admins about required steps to complete.
  $vars = array(
    '%user' => $sf->login->userInfo->userFullName,
    '%email' => $sf->login->userInfo->userEmail,
    '!uri' => url(SALESFORCE_PATH_ADMIN, array(
      'absolute' => TRUE,
    )),
  );
  DrupalSalesforce::watchdog(SALESFORCE_LOG_ALL, 'The password for the salesforce
    account %user expired.  Drupal changed it and saved the new password.
    Salesforce updated the security token and emailed it to %email.', $vars);
  DrupalSalesforce::watchdog(SALESFORCE_LOG_SOME, 'Provide the new security token
    at <a href="!uri">Drupal\'s Salesforce settings page</a>.  Salesforce
    emailed it to %email.', $vars, WATCHDOG_ALERT);

  // If salesforce connects on pages for anonymous users then these messages should not be displayed.
  if (user_access('administer salesforce')) {
    drupal_set_message(t('The password for the salesforce account %user expired.
      Drupal changed it and saved the new password.  Salesforce updated the
      security token and emailed it to %email.', $vars));
    drupal_set_message(t('Provide the new security token at
      <a href="!uri">Drupal\'s Salesforce settings page</a>.
      Salesforce emailed it to %email.', $vars), 'error');
  }
}

/**
 * Implements hook_fieldmap_objects().
 *
 * This will pull a cached version (if possible) of the available SF fields for
 * the object(s) in question. Prevent excess querying!
 */
function salesforce_api_fieldmap_objects($type = 'salesforce') {
  $objects = array();

  // Define the data fields available for Salesforce objects.
  if ($type == 'salesforce') {
    $cache = cache_get('salesforce_api_sf_objects');
    if (!$cache || $cache->data == '') {
      $objects = salesforce_api_cache_build();
    }
    else {

      // to mimic drupal 7's data structure -- entity->bundle->data -- add a
      // redundant layer of indirection here.
      $objects = $cache->data;
    }
  }
  return array(
    'salesforce' => $objects,
  );
}

/**
 * Recreate the salesforce object cache
 */
function salesforce_api_cache_build() {
  $sf_objects = variable_get('salesforce_api_enabled_objects', array(
    'Campaign',
    'Contact',
    'Lead',
  ));
  $sf = salesforce_api_connect();
  $result = salesforce_api_describeSObjects($sf_objects);
  foreach ($sf_objects as $i => $obj) {
    $objects[$obj] = salesforce_api_object_to_fieldmap_fields($result[$obj]);
  }

  // find the expiry time
  $lifetime = variable_get('salesforce_api_object_expire', CACHE_PERMANENT);
  $expire = $lifetime == CACHE_PERMANENT ? CACHE_PERMANENT : REQUEST_TIME + $lifetime;
  cache_set('salesforce_api_sf_objects', $objects, $table = 'cache', $expire, $headers = NULL);
  drupal_set_message(t('Salesforce object cache has been refreshed.'));
  return $objects;
}

/**
 * Returns an array of system fields that are retrievable from Salesforce.
 */
function salesforce_api_fieldmap_system_fields() {
  $fields = array(
    'Id' => array(
      'label' => t('Salesforce ID'),
    ),
    'IsDeleted' => array(
      'label' => t('Is the object deleted?'),
    ),
    'CreatedById' => array(
      'label' => t('User ID of the creator'),
    ),
    'CreatedDate' => array(
      'label' => t('Creation date and time'),
    ),
    'LastModifiedById' => array(
      'label' => t('User ID of the last modifier'),
    ),
    'LastModifiedDate' => array(
      'label' => t('Last user modification date and time'),
    ),
    'SystemModstamp' => array(
      'label' => t('Last user or system modification date and time'),
    ),
  );
  return $fields;
}

/**
 * Saves a fieldmap to the database.
 *
 * @param $map;
 *   An array containing the fieldmap data using the following keys and values:
 *   - fieldmap: the numeric index of the fieldmap.
 *   - drupal: the name of a Drupal object.
 *   - salesforce: the name of a Salesforce object.
 *   - automatic: whether or not the sync should be automatic
 *   - description: a short title or description of the fieldmap
 *   - fields: an array that maps source fields (as keys) to their corresponding
 *       target fields (as values).
 */
function salesforce_api_fieldmap_save(&$map) {
  $primary_key = !empty($map['fieldmap']) ? 'fieldmap' : NULL;
  if (is_array($map['fields'])) {
    $map['fields'] = serialize($map['fields']);
  }
  drupal_write_record('salesforce_field_map', $map, $primary_key);
}

/**
 * Loads a fieldmap from the database.
 *
 * @param $fieldmap
 *   The index of the fieldmap to load.
 * @return
 *   An array containing the fieldmap data.
 */
function salesforce_api_fieldmap_load($fieldmap) {
  static $maps;
  if (!isset($maps[$fieldmap]) && $fieldmap != '' && is_numeric($fieldmap)) {
    $map = db_select('salesforce_field_map', 's')
      ->fields('s')
      ->condition('fieldmap', $fieldmap)
      ->execute()
      ->fetch(PDO::FETCH_ASSOC);
    if (isset($map['fields'])) {
      $map['fields'] = unserialize($map['fields']);
    }
    $maps[$fieldmap] = $map;
  }
  return $maps[$fieldmap];
}

/**
 * Remove a field from all fieldmaps. This is particularly useful for implementations
 * of hook_content_fieldapi('delete instance'). May be use to delete an occurence
 * in a single fieldmap (by supplying entity/bundle and/or salesforce_type), or every
 * occurence in all fieldmaps (by supplying only fieldname).
 *
 * @param $fieldname
 *  The name of the field to be deleted. Either a CCK field, or a Salesforce field
 * @param $entity (optional)
 *  If given, limit deleting of the field to this Drupal content type
 * @param $bundle (optional)
 *  If given, limit deleting of the field to this Drupal content type
 * @param $salesforce_type (optional)
 *  If given, limit
 * @see sf_node/sf_node.module:sf_node_content_fieldapi
 * @todo I'm sure this can be done more elegantly, but I can't spend anymore braincells on it right now.
 */
function salesforce_api_fieldmap_field_delete($fieldname, $entity = NULL, $bundle = NULL, $salesforce_type = NULL) {
  $query = db_select('salesforce_field_map', 's');
  $query
    ->fields('s', array(
    'fieldmap',
  ));
  if ($entity) {
    $query
      ->condition('drupal_entity', $entity);
  }
  if ($bundle) {
    $query
      ->condition('drupal_bundle', $bundle);
  }
  if ($salesforce_type) {
    $query
      ->condition('salesforce', $salesforce_type);
  }
  $result = $query
    ->execute();
  while ($fieldmap_id = $result
    ->fetchField()) {
    $map = salesforce_api_fieldmap_load($fieldmap_id);

    // In the extremely unlikely event that a Salesforce field and a Drupal
    // field share the same name, this function handles both.
    if ($entity || $bundle) {
      $key1 = array_search($fieldname, $map['fields']);
      if ($key1) {
        unset($map['fields'][$key1]);
        drupal_set_message(t('Removed Drupal field from salesforce_api !link', array(
          '!link' => l('fieldmap ' . $fieldmap_id, SALESFORCE_PATH_OBJECT . '/' . $fieldmap_id),
        )));
        salesforce_api_fieldmap_save($map);
      }
    }
    if ($salesforce_type) {
      if (!empty($map['fields'][$key2])) {
        unset($map['fields'][$key2]);
        drupal_set_message(t('Removed Salesforce field from salesforce_api !link', array(
          '@link' => l('fieldmap ' . $fieldmap_id, SALESFORCE_PATH_OBJECT . '/' . $fieldmap_id),
        )));
        salesforce_api_fieldmap_save($map);
      }
    }
  }
}

/**
 * Clones a fieldmap.
 *
 * @param $fieldmap
 *   The index of the fieldmap to clone.
 * @return
 *   The newly created fieldmap or FALSE if the clone failed.
 */
function salesforce_api_fieldmap_clone($fieldmap) {

  // Load the fieldmap from the database.
  $map = salesforce_api_fieldmap_load($fieldmap);

  // Return FALSE if the source fieldmap does not exist.
  if (empty($map)) {
    return FALSE;
  }

  // Save the old fieldmap id, save a new one, and return the new one.
  unset($map['fieldmap']);
  salesforce_api_fieldmap_save($map);
  return !empty($map['fieldmap']) ? $map : FALSE;
}

/**
 * Deletes a fieldmap from the database.
 *
 * @param $fieldmap
 *   The index of the fieldmap to delete.
 */
function salesforce_api_fieldmap_delete($fieldmap) {
  db_delete('salesforce_field_map')
    ->condition('fieldmap', $fieldmap)
    ->execute();
  db_delete('salesforce_object_map')
    ->condition('fieldmap', $fieldmap)
    ->execute();
  if (function_exists('sf_prematch_match_by_delete')) {
    sf_prematch_match_by_delete($fieldmap);
  }
}

/**
 * Returns an array of fieldmaps for use as options in the Forms API.
 *
 * @param $entity
 *   Filters the fieldmaps by entity.
 * @param $bundle
 *   Filters the fieldmaps by bundle
 * @param $salesforce
 *   Filters the fieldmaps by Salesforce object.
 * @param $automatic
 *   Optional: Filter the fieldmaps to only pull those marked automatic.
 * @return
 *   A FAPI options array of all the matching fieldmaps.
 */
function salesforce_api_fieldmap_options($entity = NULL, $bundle = NULL, $salesforce = NULL) {
  $options = array();

  // This does not need to not be optimized for perfomance since it's only an admin interface.
  $query = db_select('salesforce_field_map', 's')
    ->fields('s');
  if (!empty($entity)) {
    $query
      ->condition('drupal_entity', $entity);
  }
  if (!empty($bundle)) {
    $query
      ->condition('drupal_bundle', $bundle);
  }
  if (!empty($salesforce)) {
    $query
      ->condition('salesforce', $salesforce, 'LIKE');
  }
  $result = $query
    ->execute();
  while ($map = $result
    ->fetch(PDO::FETCH_ASSOC)) {

    // Setup some replacement args for the label.
    $args = array(
      '@drupal' => salesforce_api_fieldmap_object_label('drupal', $map['drupal_bundle'], $map['drupal_entity']),
      '@salesforce' => salesforce_api_fieldmap_object_label('salesforce', 'salesforce', $map['salesforce']),
    );
    $options[$map['fieldmap']] = t('Drupal @drupal to Salesforce @salesforce', $args);
  }
  return $options;
}

/**
 * Returns all or a subset of the objects defined via hook_sf_fieldmap().
 *
 * @param (string) $type
 *   valid values: 'drupal' or 'salesforce'
 *   Specify a type to filter the return value to objects of that type.
 * @param (string) $entity
 *   valid values: if $type == 'salesforce', this should also be 'salesforce'
 *   if $type == 'drupal', this should be a valid entity name
 *   Specify an entity name to filter the return value to that entity alone.
 *   If this parameter is supplied, you must specify a type.
 * @param (string) $bundle
 *   Specify a bundle name to further filter the return value by bundle.
 *   If this parameter is supplied, you must specify an entity.
 * @return
 *  Return value structure depends on the arguments provided.
 *  If no arguments, all fieldmap objects will be returned.
 *  If $type is specified, only objects of that type will be returned, etc.
 */
function salesforce_api_fieldmap_objects_load($type = NULL, $entity = NULL, $bundle = NULL) {
  static $objects = array();

  // If we have not yet cached the object definitions...
  if (empty($objects)) {

    // Find all the Drupal objects defined by hook_sf_fieldmap().
    $objects['drupal'] = module_invoke_all('fieldmap_objects', 'drupal');

    // Get all the Salesforce objects defined by hook_sf_fieldmap().
    $objects['salesforce'] = module_invoke_all('fieldmap_objects', 'salesforce');

    // Allow other modules to modify the object definitions.
    foreach (module_implements('fieldmap_objects_alter') as $module) {
      $function = $module . '_fieldmap_objects_alter';
      $function($objects);
    }
  }

  // If a particular object type was specified...
  if (!empty($type)) {

    // And a particular object was specified...
    if (!empty($entity)) {

      // Return that object definition if it exists or FALSE if it does not.
      if (!empty($bundle)) {
        if (isset($objects[$type][$entity][$bundle])) {
          return $objects[$type][$entity][$bundle];
        }
        else {
          return FALSE;
        }
      }
      else {
        if (isset($objects[$type][$entity])) {
          return $objects[$type][$entity];
        }
        else {
          return FALSE;
        }
      }
    }
    else {
      if (isset($objects[$type])) {
        return $objects[$type];
      }
      else {
        return FALSE;
      }
    }
  }
  return $objects;
}

/**
 * Returns the label for the object of the specified type and name.
 * @see salesforce_api_fieldmap_objects_load()
 */
function salesforce_api_fieldmap_object_label($type, $entity, $bundle) {

  // Get the object definition.
  $object = salesforce_api_fieldmap_objects_load($type, $entity, $bundle);

  // If no label is specified, return the object name.
  if (empty($object['label'])) {
    return check_plain($entity . ': ' . $bundle);
  }
  return $object['label'];
}

/**
 * Returns a string of description text for the specified fieldmap.
 */
function salesforce_api_fieldmap_description($map) {
  return t('Fieldmap @index maps Salesforce %salesforce objects to Drupal %drupal objects.', array(
    '@index' => $map['fieldmap'],
    '%drupal' => salesforce_api_fieldmap_object_label('drupal', $map['drupal_entity'], $map['drupal_bundle']),
    '%salesforce' => salesforce_api_fieldmap_object_label('salesforce', 'salesforce', $map['salesforce']),
  ));
}

/**
 * Returns a FAPI options array for specifying a field from the source object to
 *   associate with the target field.
 *
 * @param $object
 *   The source object whose fields we need to filter into the options array.
 * @param $type
 *   The type of the target field's object.
 * @param $name
 *   The name of the target object.
 * @param $field
 *   The name of the target field.
 * @return
 *   A FAPI options array of all the available fields that can map to the
 *     target field.
 */
function salesforce_api_fieldmap_field_options($object, $type = NULL, $name = NULL, $field = NULL) {

  // Define the options array with a blank value.
  $options = array(
    '' => '',
  );

  // TODO: Consider filtering these based on the object definition.  For now
  // this function simply uses any field defined for the source object.
  // Loop through all the fields of the source object.
  foreach ($object['fields'] as $key => $data) {

    // Add the field to the options array in the right options group.
    if (!empty($data['group'])) {
      $options[$data['group']][$key] = $data['label'];
    }
    else {
      $options[t('Core fields')][$key] = $data['label'];
    }
  }
  return $options;
}

/**
 * Creates an object for export to Salesforce based on the supplied Drupal
 *   object and fieldmap.
 *
 * @param $fieldmap
 *   The index of the fieldmap used to filter the Drupal object into the export.
 * @param $drupal_data
 *   The Drupal object used to generate the export.
 * @return
 *   An object containing data ready for export to Salesforce or FALSE if
 *     the operation failed.
 */
function salesforce_api_fieldmap_export_create($fieldmap, $drupal_data = NULL) {

  // Load the fieldmap from the database.
  $map = salesforce_api_fieldmap_load($fieldmap);

  // Fail if the fieldmap does not exist.
  if (!$map) {
    return FALSE;
  }
  $drupal_object_definition = salesforce_api_fieldmap_objects_load('drupal', $map['drupal_entity'], $map['drupal_bundle']);
  $sf_object_definition = salesforce_api_fieldmap_objects_load('salesforce', 'salesforce', $map['salesforce']);
  $object = new stdClass();

  // Loop through the fields on the fieldmap.
  foreach ($map['fields'] as $sf_fieldname => $drupal_fieldname) {
    $sf_field_definition = $sf_object_definition['fields'][$sf_fieldname];
    $updateable = $sf_field_definition['type'] & SALESFORCE_FIELD_UPDATEABLE;
    $createable = $sf_field_definition['type'] & SALESFORCE_FIELD_CREATEABLE;

    // Don't try to update or create fields to which those actions do not apply.
    if (!$updateable && !$createable || empty($drupal_data->salesforce['sfid']) && !$createable || !empty($drupal_data->salesforce['sfid']) && !$updateable) {
      continue;
    }

    // If a handler is specified for retrieving a value for the Drupal field...
    if (isset($drupal_object_definition['fields'][$drupal_fieldname]['export'])) {
      $drupal_field_export_handler = $drupal_object_definition['fields'][$drupal_fieldname]['export'];
      $drupal_field_definition = $drupal_object_definition['fields'][$drupal_fieldname];

      // Get the value for the field from the handler function.
      $object->{$sf_fieldname} = htmlentities($drupal_field_export_handler($drupal_data, $drupal_fieldname, $drupal_field_definition, $sf_field_definition));
    }
    elseif (isset($drupal_data->{$drupal_fieldname})) {
      $object->{$sf_fieldname} = htmlentities($drupal_data->{$drupal_fieldname});
    }
  }

  // Before we return the object, we need to check for any fieldsToNull.
  // Leaving a field blank will not erase an existing value from Salesforce.
  // Any such value must be explicitly set to NULL.
  // TODO: Fields should be checked for nillable before getting added to fieldsToNull
  $props = get_object_vars($object);
  foreach ($props as $key => $value) {
    $sf_field_definition = $sf_object_definition['fields'][$sf_fieldname];

    // Salesforce treats the following values differently than NULL.
    if ($value === FALSE || $value === 0 || $value === '0' || $value === 'FALSE') {
      $object->{$key} = $value = 0;
    }
    elseif (empty($value)) {
      $nillable = $sf_field_definition['type'] & SALESFORCE_FIELD_NILLABLE;

      // If the field is not nillable, don't try to set it to NULL
      if (!$nillable) {
        switch ($sf_field_definition['salesforce']['type']) {
          case 'boolean':
            $object->{$key} = 0;
            break;
          case 'string':
            $object->{$key} = t('(blank)');
            break;
          default:
            unset($object->{$key});
            break;
        }
      }
      else {
        $object->fieldsToNull = $key;

        // Enterprise client can only NULL one field per transaction.
        // This is a bug beyond our control. For now, we just have to deal with it.
        // The following doesn't actually work:
        // if (!empty($object->fieldsToNull)) {
        //   $object->fieldsToNull .= '; ';
        // }
      }
    }
  }
  return $object;
}

/**
 * Loads the mapping data given identifying information.
 *
 * @param string $entity
 *   The entity type of the Drupal object you are requesting data for;
 *   e.g. node or user.
 * @param string $bundle
 *   The bundle the Drupal object belongs to
 * @param mixed $id_or_ids
 *   The associated unique ID or IDs used to identify the object in Drupal.
 * @return
 *   The fetched query result.
 */
function salesforce_api_id_load($entity, $bundle, $id_or_ids, $key = 'fieldmap') {

  // Query the main ID table for the associated data.
  $op = is_array($id_or_ids) ? 'IN' : '=';
  $query = db_select('salesforce_object_map', 's')
    ->fields('s', array(
    'fieldmap',
    'sfid',
    'oid',
  ))
    ->condition('drupal_entity', $entity)
    ->condition('oid', $id_or_ids, $op);
  if ($bundle) {
    $query
      ->condition('drupal_bundle', $bundle);
  }
  return $query
    ->execute()
    ->fetchAllAssoc($key, PDO::FETCH_ASSOC);
}

/**
 * Get an object id using the salesforce id
 *
 * @param $sfid
 *   A saleforce id
 * @param $type
 *   The type of the Drupal object you are requesting data for; node or user.
 * @return
 *   The associated unique ID used to identify the object in Drupal or FALSE.
 */
function salesforce_api_get_id_with_sfid($sfid, $entity, $bundle, $key = 'sfid') {
  return db_select('salesforce_object_map', 's')
    ->fields('s', array(
    'oid',
  ))
    ->condition('sfid', $sfid)
    ->condition('drupal_entity', $entity)
    ->condition('drupal_bundle', $bundle)
    ->execute()
    ->fetchAllAssoc($key);
}

/**
 * Saves the Salesforce ID and fieldmap index of a Drupal object.
 *
 * @param $entity
 *   The entity you are saving; e.g. node or user.
 * @param $bundle
 *   The bundle you are saving for; e.g. node type or vocab name
 * @param $oid
 *   The associated unique ID used to identify the object in Drupal.
 * @param $sfid
 *   The Salesforce ID of the associated object in the Salesforce database.
 * @param $fieldmap
 *   The index of the fieldmap used to generate the export.
 */
function salesforce_api_id_save($entity, $bundle, $oid, $sfid, $fieldmap) {
  db_delete('salesforce_object_map')
    ->condition('drupal_entity', $entity)
    ->condition('drupal_bundle', $bundle)
    ->condition('oid', $oid)
    ->execute();
  db_insert('salesforce_object_map')
    ->fields(array(
    'drupal_entity' => $entity,
    'drupal_bundle' => $bundle,
    'oid' => $oid,
    'sfid' => $sfid,
    'fieldmap' => $fieldmap,
  ))
    ->execute();
}
function salesforce_api_search_for_duplicates($direction, $object, $fieldmap_id) {

  // Call hook_sf_find_match to give opportunity to try to match existing sf object instead
  // of creating a new one. No hook_sf_find_match is defined out of the box. Developers must
  // implement their own logic for this one.
  return module_invoke_all('sf_find_match', $direction, $object, $fieldmap_id);
}

/**
 * Implements hook_theme().
 *
 * Registers theme callback for admin screen
 */
function salesforce_api_theme($existing, $type, $theme, $path) {
  return array(
    'salesforce_api_fieldmap_edit_form_table' => array(
      'render element' => 'form',
    ),
    'salesforce_api_object_options' => array(
      'render element' => 'element',
    ),
  );
}

/**
 * Wrapper for SOAP SforceBaseClient::describeGlobal
 * @return an SFQueryResult object (look at ->types for an array of SF object types)
 */
function salesforce_api_describeGlobal() {
  static $response;
  if (!empty($response)) {
    return $response;
  }
  $sf = salesforce_api_connect();
  if ($sf === FALSE) {
    $link = l('Please verify that you have completed your SalesForce credentials', SALESFORCE_PATH_ADMIN);
    drupal_set_message(t('Unable to connect to SalesForce. !link', array(
      '!link' => $link,
    )), 'error');
    return;
  }
  $response = $sf->client
    ->describeGlobal();
  if (isset($response->sobjects)) {
    $response->types = $response->sobjects;
    unset($response->sobjects);
  }
  return $response;
}

/**
 * Convert Salesforce object fields to fieldmap array for saving
 */
function salesforce_api_object_to_fieldmap_fields($object) {
  $fieldmap_object = array(
    'label' => t($object->label),
    'fields' => array(),
  );
  foreach ($object->fields as $field) {
    $properties = array(
      'name',
      'label',
      'type',
      'length',
      'soapType',
    );
    $booleans = array(
      'createable',
      'defaultedOnCreate',
      'deprecatedAndHidden',
      'idLookup',
      'nillable',
      'restrictedPicklist',
      'unique',
      'updateable',
    );
    $source = get_object_vars($field);
    $sf_definition = array_intersect_key($source, array_flip($properties));
    $sf_definition['sf_type'] = 0;
    foreach ($booleans as $bool) {
      $sf_definition['sf_type'] |= (int) $source[$bool] * constant('SALESFORCE_FIELD_' . strtoupper($bool));
    }
    $fieldmap_object['fields'][$field->name] = array(
      'name' => $sf_definition['name'],
      'label' => $sf_definition['label'],
      'type' => $sf_definition['sf_type'],
      'salesforce' => $sf_definition,
    );
  }
  return $fieldmap_object;
}

/**
 * Implements hook_cron().
 */
function salesforce_api_cron() {
  $cache = cache_get('salesforce_api_sf_objects');

  // if the cache has already been delete or is expired then rebuild
  if (!$cache || REQUEST_TIME > $cache->expire) {
    salesforce_api_cache_build();
  }
  return;
}

/**
 * Wrapper for SOAP SforceBaseClient::describeSObject
 * Given an sf object type, return the SF Object definition
 * @param string type : the machine-readable name of the SF object type.
 */
function salesforce_api_describeSObject($type) {
  if (!is_string($type)) {
    drupal_set_message(t('DescribeSObject expects a string. @type recieved.', array(
      '@type' => gettype($type),
    )), 'error');
    return FALSE;
  }
  $objects = salesforce_api_describeSObjects($type);
  if (!empty($objects[$type])) {
    return $objects[$type];
  }
  else {
    drupal_set_message(t('DescribeSObject failed to find @type.', array(
      '@type' => $type,
    )), 'error');
    return FALSE;
  }
}

/**
 * Wrapper for SOAP SforceBaseClient::describeSObjects
 * Given an array of sf object type, return an associative, normalized array of
 * SF object definitions, indexed on machine-readable names of SObjects
 * @param array types : an array of machine-readable names to SObjects
 */
function salesforce_api_describeSObjects($types) {
  static $objects;
  if (is_string($types)) {
    $types = array(
      $types,
    );
  }
  if (!is_array($types)) {
    drupal_set_message(t('DescribeSObjects expects an array. @types recieved.', array(
      '@types' => gettype($types),
    )), 'error');
    return FALSE;
  }

  // There is no reason to describe the same object twice in one HTTP request.
  // Use a static cache to save API calls and bandwidth.
  if (!empty($objects)) {
    $outstanding = array_diff($types, array_keys($objects));
    if (empty($outstanding)) {
      $ret = array();
      foreach ($types as $k) {
        $ret[$k] = $objects[$k];
      }
      return $ret;
    }
  }
  if (is_string($types)) {
    $types = array(
      $types,
    );
  }
  try {
    $sf = salesforce_api_connect();
    if ($sf === FALSE) {
      $link = l('Please verify that you have completed your SalesForce credentials', SALESFORCE_PATH_ADMIN);
      drupal_set_message(t('Unable to connect to SalesForce. !link', array(
        '!link' => $link,
      )), 'error');
      return;
    }
    $objects = $sf->client
      ->describeSObjects(array_values($types));
  } catch (Exception $e) {
    DrupalSalesforce::watchdog(SALESFORCE_LOG_SOME, 'Unable to establish Salesforce connection while issuing describeSObjects API call.', array(), WATCHDOG_ERROR);
  }
  if (empty($objects)) {
    return array();
  }

  // This is the normalization part: If only one object was described, SalesForce
  // returned an object instead of an array. ALWAYS return an array of objects.
  if (is_object($objects)) {
    $objects = array(
      $objects,
    );
  }

  // And make it an associative array for good measure.
  $tmp = array();
  foreach ($objects as $o) {
    $tmp[$o->name] = $o;
  }
  $objects = $tmp;
  return $objects;
}

Functions

Namesort descending Description
salesforce_api_cache_build Recreate the salesforce object cache
salesforce_api_connect Creates an object used for communicating with the Salesforce server and performs a login to verify the API credentials.
salesforce_api_cron Implements hook_cron().
salesforce_api_describeGlobal Wrapper for SOAP SforceBaseClient::describeGlobal
salesforce_api_describeSObject Wrapper for SOAP SforceBaseClient::describeSObject Given an sf object type, return the SF Object definition
salesforce_api_describeSObjects Wrapper for SOAP SforceBaseClient::describeSObjects Given an array of sf object type, return an associative, normalized array of SF object definitions, indexed on machine-readable names of SObjects
salesforce_api_fieldmap_clone Clones a fieldmap.
salesforce_api_fieldmap_delete Deletes a fieldmap from the database.
salesforce_api_fieldmap_description Returns a string of description text for the specified fieldmap.
salesforce_api_fieldmap_export_create Creates an object for export to Salesforce based on the supplied Drupal object and fieldmap.
salesforce_api_fieldmap_field_delete Remove a field from all fieldmaps. This is particularly useful for implementations of hook_content_fieldapi('delete instance'). May be use to delete an occurence in a single fieldmap (by supplying entity/bundle and/or salesforce_type), or…
salesforce_api_fieldmap_field_options Returns a FAPI options array for specifying a field from the source object to associate with the target field.
salesforce_api_fieldmap_load Loads a fieldmap from the database.
salesforce_api_fieldmap_objects Implements hook_fieldmap_objects().
salesforce_api_fieldmap_objects_load Returns all or a subset of the objects defined via hook_sf_fieldmap().
salesforce_api_fieldmap_object_label Returns the label for the object of the specified type and name.
salesforce_api_fieldmap_options Returns an array of fieldmaps for use as options in the Forms API.
salesforce_api_fieldmap_save Saves a fieldmap to the database.
salesforce_api_fieldmap_system_fields Returns an array of system fields that are retrievable from Salesforce.
salesforce_api_get_id_with_sfid Get an object id using the salesforce id
salesforce_api_id_load Loads the mapping data given identifying information.
salesforce_api_id_save Saves the Salesforce ID and fieldmap index of a Drupal object.
salesforce_api_menu Implements hook_menu().
salesforce_api_object_to_fieldmap_fields Convert Salesforce object fields to fieldmap array for saving
salesforce_api_permission Implements hook_permission().
salesforce_api_reset_expired_password Helper function for salesforce_api_connect() to reset an expired password, for the website's default salesforce user only.
salesforce_api_search_for_duplicates
salesforce_api_theme Implements hook_theme().

Constants