You are here

salesforce_api.module in Salesforce Suite 6.2

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

File

salesforce_api/salesforce_api.module
View source
<?php

/**
 * @file
 * Defines an API that enables modules to interact with the Salesforce server.
 *
 */
define('SALESFORCE_API_MINIMUM_VERSION', 1);
define('SALESFORCE_API_CURRENT_VERSION', 1);

// Define default directory paths for the Toolkit and WSDL files.
define('SALESFORCE_DIR', drupal_get_path('module', 'salesforce_api'));
define('SALESFORCE_DIR_TOOLKIT', function_exists('libraries_get_path') ? libraries_get_path('salesforce') : 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/settings/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');
define('SALESFORCE_PATH_UPDATE_WSDL', SALESFORCE_PATH_ADMIN . '/wsdl');

// Salesforce schema properties. Not all these are in use yet.
define('SALESFORCE_FIELD_CREATEABLE', 0x1);
define('SALESFORCE_FIELD_DEFAULTEDONCREATE', 0x2);
define('SALESFORCE_FIELD_DEPRECATEDANDHIDDEN', 0x4);
define('SALESFORCE_FIELD_IDLOOKUP', 0x8);
define('SALESFORCE_FIELD_NILLABLE', 0x10);
define('SALESFORCE_FIELD_RESTRICTEDPICKLIST', 0x20);
define('SALESFORCE_FIELD_UNIQUE', 0x40);
define('SALESFORCE_FIELD_UPDATEABLE', 0x80);
define('SALESFORCE_FIELD_SOURCE_ONLY', ~SALESFORCE_FIELD_CREATEABLE);

// Define reporting levels for watchdog messages.
define('SALESFORCE_LOG_NONE', 0);
define('SALESFORCE_LOG_SOME', 5);
define('SALESFORCE_LOG_ALL', 10);
define('SALESFORCE_AUTO_SYNC_OFF', 0x0);
define('SALESFORCE_AUTO_SYNC_CREATE', 0x1);
define('SALESFORCE_AUTO_SYNC_UPDATE', 0x2);
define('SALESFORCE_AUTO_SYNC_DELETE', 0x4);
define('SALESFORCE_DELETED_POLICY_NOOP', 0);
define('SALESFORCE_DELETED_POLICY_UPSERT', 1);
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 (!is_string($sfid)) {
      return FALSE;
    }
    if (strlen($sfid) == 15 || strlen($sfid) == 18) {
      return TRUE;
    }
    return FALSE;
  }
}
function salesforce_api_init() {
  if (!salesforce_api_toolkit_installed() && user_access('administer salesforce')) {
    drupal_set_message(t('Salesforce API installed, but missing Salesforce PHP Toolkit. Please make sure Salesforce PHP Toolkit is available at ' . SALESFORCE_DIR_SOAPCLIENT . '/SforceEnterpriseClient.php'), 'error');
    drupal_set_message(t('This message will appear as long as Salesforce API is enabled and the Salesforce PHP Toolkit is missing. Please refer to README.txt or INSTALL.txt for additional information.'), 'error');
  }
}

/**
 * Implements hook_help().
 */
function salesforce_api_help($section) {
  switch ($section) {
    case 'admin/help#salesforce_api':

      // Return a line-break version of the module README
      return filter_filter('process', 1, NULL, file_get_contents(drupal_get_path('module', 'salesforce_api') . "/../README.txt"));
  }
}

/**
 * Implementation of hook_features_api().
 */
function salesforce_api_features_api() {
  return array(
    'salesforce_field_map' => array(
      'name' => t('Salesforce Field Maps'),
      'feature_source' => TRUE,
      'default_hook' => 'default_salesforce_field_maps',
      'default_file' => FEATURES_DEFAULTS_INCLUDED,
      'file' => drupal_get_path('module', 'salesforce_api') . '/salesforce_api.features.inc',
    ),
  );
}

/**
 * Implementation of hook_menu().
 */
function salesforce_api_menu() {
  $map_id_arg = count(explode('/', SALESFORCE_PATH_FIELDMAPS));
  $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,
  );
  $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,
  );
  $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' => 5,
    'file' => 'salesforce_api.admin.inc',
  );
  $items[SALESFORCE_PATH_FIELDMAPS . '/%/edit'] = array(
    'title' => 'Edit',
    'description' => 'Edit an existing fieldmap.',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'salesforce_api_fieldmap_edit_form',
      $map_id_arg,
    ),
    'access callback' => '_salesforce_fieldmap_access',
    'access arguments' => array(
      'administer salesforce',
      'edit',
      $map_id_arg,
    ),
    'type' => MENU_LOCAL_TASK,
    'file' => 'salesforce_api.admin.inc',
    'weight' => 10,
  );
  $items[SALESFORCE_PATH_FIELDMAPS . '/%/remove/%'] = array(
    'title' => 'Edit',
    'description' => 'Edit an existing fieldmap.',
    'page callback' => 'salesforce_api_fieldmap_remove_field',
    'page arguments' => array(
      $map_id_arg,
      $map_id_arg + 2,
    ),
    'access callback' => '_salesforce_fieldmap_access',
    'access arguments' => array(
      'administer salesforce',
      'edit',
      $map_id_arg,
    ),
    'type' => MENU_CALLBACK,
    'file' => 'salesforce_api.admin.inc',
  );
  $items[SALESFORCE_PATH_FIELDMAPS . '/%/clone'] = array(
    'title' => 'Clone',
    'description' => 'Clone an existing fieldmap.',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'salesforce_api_fieldmap_clone_form',
      $map_id_arg,
    ),
    'access callback' => '_salesforce_fieldmap_access',
    'access arguments' => array(
      'administer salesforce',
      'clone',
      $map_id_arg,
    ),
    'type' => MENU_LOCAL_TASK,
    'file' => 'salesforce_api.admin.inc',
    'weight' => 15,
  );
  $items[SALESFORCE_PATH_FIELDMAPS . '/%/delete'] = array(
    'title' => 'Delete',
    'description' => 'Delete an existing fieldmap.',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'salesforce_api_fieldmap_delete_form',
      $map_id_arg,
    ),
    'access callback' => '_salesforce_fieldmap_access',
    'access arguments' => array(
      'administer salesforce',
      'delete',
      $map_id_arg,
    ),
    'type' => MENU_LOCAL_TASK,
    'file' => 'salesforce_api.admin.inc',
    'weight' => 20,
  );
  $items[SALESFORCE_PATH_FIELDMAPS . '/%/revert'] = $items[SALESFORCE_PATH_FIELDMAPS . '/%/delete'];
  $items[SALESFORCE_PATH_FIELDMAPS . '/%/revert']['title'] = 'Revert';
  unset($items[SALESFORCE_PATH_FIELDMAPS . '/%/revert']['type'], $items[SALESFORCE_PATH_FIELDMAPS . '/%/revert']['weight']);
  $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',
  );
  $items[SALESFORCE_PATH_UPDATE_WSDL] = array(
    'title' => 'WSDL',
    'description' => 'Upload a new WSDL XML file and set its location',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'salesforce_api_update_wsdl_form',
    ),
    'access arguments' => array(
      'administer salesforce',
    ),
    'type' => MENU_LOCAL_TASK,
    'file' => 'salesforce_api.admin.inc',
  );
  if (module_exists('ctools')) {
    $items[SALESFORCE_PATH_FIELDMAPS . '/%/export'] = array(
      'title' => 'Export',
      'description' => 'Export a fieldmap.',
      'page callback' => 'salesforce_api_export_salesforce_field_map',
      'page arguments' => array(
        $map_id_arg,
      ),
      'access callback' => '_salesforce_fieldmap_access',
      'access arguments' => array(
        'administer salesforce',
        'export',
        $map_id_arg,
      ),
      'type' => MENU_LOCAL_TASK,
      // 'file' => 'salesforce_api.admin.inc',
      'weight' => 25,
    );
    $items[SALESFORCE_PATH_FIELDMAPS . '/import'] = array(
      'title' => 'Import',
      'description' => 'Import a fieldmap.',
      'page callback' => 'drupal_get_form',
      'page arguments' => array(
        'salesforce_api_import_salesforce_field_map',
      ),
      'access arguments' => array(
        'administer salesforce',
      ),
      'type' => MENU_LOCAL_TASK,
      'file' => 'salesforce_api.admin.inc',
      'weight' => 30,
    );
  }
  return $items;
}

/**
 * Implementation of hook_perm().
 */
function salesforce_api_perm() {
  return array(
    'administer salesforce',
    'use php for salesforce fixed values',
  );
}

/**
 * Access callback
 *
 */
function _salesforce_fieldmap_access($perm, $op = 'edit', $id = NULL) {
  if (empty($id)) {
    return FALSE;
  }
  else {
    $maps = salesforce_api_salesforce_field_map_load_all();
    foreach ($maps as $map) {
      if ($id == $map->name) {
        return user_access($perm);
      }
    }
  }
  return FALSE;
}

/**
 * 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 Salesforce Client 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;

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

  // Load up the sitewide API credentials if none were provided.
  $encrypted = variable_get('salesforce_api_encrypt', FALSE);
  $default_username = $encrypted ? salesforce_api_decrypt(variable_get('salesforce_api_username', '')) : variable_get('salesforce_api_username', '');
  $username = $username ? $username : $default_username;
  $password = $password ? $password : ($encrypted ? salesforce_api_decrypt(variable_get('salesforce_api_password', '')) : variable_get('salesforce_api_password', ''));
  $token = $token ? $token : ($encrypted ? salesforce_api_decrypt(variable_get('salesforce_api_token', '')) : variable_get('salesforce_api_token', ''));

  // Boolean, whether we are connecting with the default website user or not.
  $default_site_user = $username == $default_username;
  if (!salesforce_api_toolkit_installed()) {
    return FALSE;
  }

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

  // Attempt a login.
  $sf = salesforce_api_login($username, $password, $token);
  if ($sf) {

    // 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 {
        salesforce_api_log(SALESFORCE_LOG_SOME, 'Connection to Salesforce
          due to expired password for @user.', array(
          '@user' => $username,
        ), WATCHDOG_ERROR);
      }
    }
  }
  else {
    salesforce_api_log(SALESFORCE_LOG_SOME, 'Connection to Salesforce failed.', array(), WATCHDOG_ERROR);

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

/**
 * Helper function for salesforce_api_connect(). You should probably not call
 * this function directly
 *
 * @param string $username
 * @param string $password
 * @param string $token
 * @return Salesforce Client object
 */
function salesforce_api_login($username, $password, $token) {
  require_once SALESFORCE_DIR_SOAPCLIENT . '/SforceEnterpriseClient.php';

  // Create a new Salesforce object with the API credentials.
  $sf = (object) array(
    'username' => $username,
    'password' => $password,
    'token' => $token,
    'client' => new SforceEnterpriseClient(),
  );

  // Default to the uploaded WSDL, then any wsdl in web path if available.
  if (!($dir = variable_get('salesforce_api_dir_wsdl', FALSE))) {
    $dir = SALESFORCE_DIR_WSDL;
  }
  $wsdl = $dir . '/enterprise.wsdl.xml';

  // Otherwise fall back to the one included with the Toolkit.
  if (!file_exists($wsdl)) {
    $wsdl = SALESFORCE_DIR_SOAPCLIENT . '/enterprise.wsdl.xml';
  }

  // Get proxy settings if on.
  $proxy = NULL;
  if (variable_get('salesforce_api_proxy', FALSE)) {
    $proxy = new ProxySettings();
    $proxy->host = variable_get('salesforce_api_proxy_host', '');
    $proxy->port = variable_get('salesforce_api_proxy_port', '');
    $proxy->login = variable_get('salesforce_api_proxy_login', '');
    $proxy->password = variable_get('salesforce_api_proxy_password', '');
  }

  // Connect to the server and login, logging any failures to the watchdog.
  try {

    // Connect to the server.
    // Ensure that the WSDL cache is not set.
    ini_set('soap.wsdl_cache_enabled', '0');
    ini_set('soap.wsdl_cache_ttl', '0');
    $sf->client
      ->createConnection($wsdl, $proxy);

    // Attempt a login with the credentials entered by the user.
    $sf->login = $sf->client
      ->login($username, $password . $token);

    // Log the login occurence.
    salesforce_api_log(SALESFORCE_LOG_ALL, '@user (@email) logged into Salesforce.', array(
      '@user' => $sf->login->userInfo->userFullName,
      '@email' => $sf->login->userInfo->userEmail,
    ));
  } catch (Exception $e) {

    // Log the error message.
    salesforce_api_log(SALESFORCE_LOG_SOME, 'Could not login to Salesforce: %message. <pre>!debug</pre>', array(
      '%message' => $e->faultstring,
      '!debug' => check_plain($sf->client
        ->getLastRequest()),
    ), WATCHDOG_ERROR);

    // Indicate the failed login.
    return FALSE;
  }

  // Indicate the successful login.
  return $sf;
}

/**
 * Wraps watchdog(). Logs a message to the watchdog based on the Salesforce log
 * settings.
 *
 * @param $level
 * @param $message
 * @param $vars
 * @param $severity
 * @param $link
 */
function salesforce_api_log($level, $message, $vars = array(), $severity = WATCHDOG_NOTICE, $link = NULL) {

  // Log nothing for notices if the related log level is not greater than or
  // equal to the level of this message.
  switch ($severity) {
    case WATCHDOG_NOTICE:
      if (variable_get('salesforce_api_activity_log', SALESFORCE_LOG_SOME) < $level) {
        return;
      }
      break;
    case WATCHDOG_WARNING:
    case WATCHDOG_ERROR:
      if (variable_get('salesforce_api_error_log', SALESFORCE_LOG_ALL) < $level) {
        return;
      }
      break;
    default:
      break;
  }

  // Log the message to the watchdog.
  watchdog('salesforce', $message, $vars, $severity, $link);
}

/**
 * Helper function for salesforce_api_connect() to reset an expired password,
 *   for the website's default salesforce user only.
 *
 * @param $sf
 *   The Salesforce client 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,
    )),
  );
  salesforce_api_log(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);
  salesforce_api_log(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');
  }
}

/**
 * Implementation of 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 (empty($cache->data)) {
      $objects = salesforce_api_cache_build();
    }
    else {
      $objects = $cache->data;
    }
  }
  return $objects;
}

/**
 * Recreate the salesforce object cache
 */
function salesforce_api_cache_build() {
  $sf_objects = variable_get('salesforce_api_enabled_objects', array(
    'Campaign',
    'Contact',
    'Lead',
  ));
  $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 : time() + $lifetime;
  cache_set('salesforce_api_sf_objects', $objects, $table = 'cache', $expire, $headers = NULL);
  if (user_access('administer salesforce')) {
    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;
}

/**
 * Salesforce does not accept email addresses with relative domains, like
 * root@localhost. This function is based on Drupal's valid_email_address.
 * Greater men than I have tried and failed to capture valid email addresses
 * with simple regular expressions. This function merely tries to mimic
 * Salesforce's validation rules, NOT to capture all valid email addresses.
 */
function salesforce_api_valid_email_address($mail) {
  $user = '[a-zA-Z0-9_\\-\\.\\+\\^!#\\$%&*+\\/\\=\\?\\`\\|\\{\\}~\']+';
  $domain = '(?:[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]*[a-zA-Z0-9])';
  $tld = '(?:[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]*[a-zA-Z0-9])+';
  $domain = '(?:' . $domain . '\\.)+' . $tld;
  $ipv4 = '[0-9]{1,3}(\\.[0-9]{1,3}){3}';
  return preg_match("/^{$user}@({$domain}|(\\[({$ipv4})\\]))\$/", $mail);
}

/**
 * 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) {
  if (!empty($map->fieldmap)) {
    $primary_key = 'name';
  }
  if (empty($map->name)) {
    $map->name = md5(microtime());
  }
  drupal_write_record('salesforce_field_map', $map, $primary_key);
}

/**
 * Loads a fieldmap from the database.
 *
 * @param $fieldmap
 *   The index or name of the fieldmap to load.
 * @return
 *   An array containing the fieldmap data.
 */
function salesforce_api_fieldmap_load($fieldmap) {
  return salesforce_api_salesforce_field_map_load($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 drupal_type 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 $drupal_type (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, $drupal_type = NULL, $salesforce_type = NULL) {
  if ($drupal_type && $salesforce_type) {
    $result = db_query('SELECT fieldmap, name FROM {salesforce_field_map} WHERE drupal = "%s" AND salesforce = "%s"', $drupal_type, $salesforce_type);
  }
  elseif ($drupal_type) {
    $result = db_query('SELECT fieldmap, name FROM {salesforce_field_map} WHERE drupal = "%s"', $drupal_type);
  }
  elseif ($salesforce_type) {
    $result = db_query('SELECT fieldmap, name FROM {salesforce_field_map} WHERE salesforce = "%s"', $salesforce_type);
  }
  else {
    $result = db_query('SELECT fieldmap, name FROM {salesforce_field_map}');
  }
  while ($row = db_fetch_array($result)) {
    $map = salesforce_api_fieldmap_load($row['name']);

    // In the extremely unlikely event that a Salesforce field and a Drupal
    // field share the same name, this function handles both.
    if ($drupal_type) {
      $key1 = array_search($fieldname, $map->fields);
      if ($key1) {
        unset($map->fields[$key1]);
        if (user_access('administer salesforce')) {
          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]);
        if (user_access('administer salesforce')) {
          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 or name 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 ids, save new ones, and return it.
  unset($map->fieldmap, $map->name);
  salesforce_api_fieldmap_save($map);
  return !empty($map->name) ? $map : FALSE;
}

/**
 * Deletes a fieldmap from the database.
 *
 * @param $fieldmap
 *   The name of the fieldmap to delete.
 */
function salesforce_api_fieldmap_delete($fieldmap) {
  if (is_string($fieldmap)) {
    $name = $fieldmap;
  }
  else {
    $name = db_result(db_query('SELECT name FROM {salesforce_field_map} WHERE fieldmap = %d', $fieldmap));
  }
  if (empty($name)) {
    return;
  }
  db_query('DELETE FROM {salesforce_field_map} WHERE name = "%s"', $name);
  db_query('DELETE FROM {salesforce_object_map} WHERE name = "%s"', $name);
  if (function_exists('sf_prematch_match_by_delete')) {
    sf_prematch_match_by_delete($name);
  }
}

/**
 * Given a drupal type and oid, delete an object mapping
 *
 * @param string $drupal_type
 * @param string $oid
 */
function salesforce_api_delete_object_map($drupal_type, $oid) {
  db_query('DELETE FROM {salesforce_object_map} WHERE drupal_type = "%s" AND oid = %d', $drupal_type, $oid);
}

/**
 * Returns an array of fieldmaps for use as options in the Forms API.
 *
 * @param $drupal
 *   Filters the fieldmaps by Drupal object.
 * @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($drupal = NULL, $salesforce = NULL) {
  $options = array();

  // This does not need to not be optimized for perfomance since it's only an admin interface.
  $maps = salesforce_api_salesforce_field_map_load_all();
  foreach ($maps as $map) {
    if ($drupal && $map->drupal != $drupal) {
      continue;
    }
    if ($salesforce && $map->salesforce != $salesforce) {
      continue;
    }
    if (is_array($map)) {
      $map = (object) $map;
    }

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

/**
 * Returns all or a subset of the objects defined via hook_sf_fieldmap().
 *
 * @param $type
 *   Specify a type to filter the return value to objects of that type.
 * @param $name
 *   Specify an object name to filter the return value to that object alone.
 *     If this parameter is supplied, you must specify the correct type as well.
 * @return
 *   An array of all objects sorted by type with no arguments.  Otherwise an
 *     array of objects filtered as specified by the parameters.
 */
function salesforce_api_fieldmap_objects_load($type = NULL, $name = NULL, $reset = FALSE) {
  static $objects = array();

  // If we have not yet cached the object definitions...
  if ($reset || 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($name)) {

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

      // If no object was specified, return all objects of the specified type or
      // FALSE if the type does not exist
      if (isset($objects[$type])) {
        return $objects[$type];
      }
      else {
        return FALSE;
      }
    }
  }
  return $objects;
}

/**
 * Simple check for Salesforce Toolkit.
 *
 * @return TRUE if toolkit is present, FALSE otherwise.
 */
function salesforce_api_toolkit_installed() {
  if (file_exists(SALESFORCE_DIR_SOAPCLIENT . '/SforceEnterpriseClient.php')) {
    return TRUE;
  }
  else {
    return FALSE;
  }
}

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

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

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

// Returns a string of description text for the specified fieldmap.
function salesforce_api_fieldmap_description($map) {
  return t('Maps Salesforce %salesforce objects to Drupal %drupal objects.', array(
    '%drupal' => salesforce_api_fieldmap_object_label('drupal', $map->drupal),
    '%salesforce' => salesforce_api_fieldmap_object_label('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($name, $drupal_data = NULL) {

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

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

  // Loop through the fields on the fieldmap.
  foreach ($map->fields as $sf_fieldname => $drupal_fieldname) {
    $value = '';
    $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;
    $nillable = $sf_field_definition['type'] & SALESFORCE_FIELD_NILLABLE;

    // 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;
    }

    // See if it's a special field
    if (is_array($map->fields[$sf_fieldname])) {
      switch ($map->fields[$sf_fieldname]['type']) {
        case 'fixed':
          if (isset($map->fields[$sf_fieldname]['value'])) {
            $value = $map->fields[$sf_fieldname]['value'];
          }
          break;
        case 'php':
          if (isset($map->fields[$sf_fieldname]['value'])) {
            $value = eval($map->fields[$sf_fieldname]['value']);
          }
          break;
      }
    }
    elseif (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.
      $value = $drupal_field_export_handler($drupal_data, $drupal_fieldname, $drupal_field_definition, $sf_field_definition);
    }
    elseif (isset($drupal_data->{$drupal_fieldname})) {
      $value = $drupal_data->{$drupal_fieldname};
    }

    // Ignore null values for non-nillable fields. We also explicitly set all
    // empty strings to NULL so they too can be subject to these checks.
    if ($value === "") {
      $value = NULL;
    }
    if (is_null($value) && !$nillable) {
      continue;
    }
    elseif (is_null($value) && $nillable && !empty($map->fields[$sf_fieldname])) {
      $fieldsToNull[] = $sf_fieldname;
      continue;
    }

    // Evaluate field-type-specific syntax rules. The point here is not to
    // massage data in any way - that should have been done in the export
    // handler. All we want to do is avoid creating a syntactically invalid
    // object for export. For example, an "id" or "reference" field must contain
    // a Salesforce ID. We do not check to make sure the given Salesforce ID
    // exists, merely that the format matches that of a Salesforce ID.
    //
    // For any errors, just continue to the next iteration and log the error.
    $type = $sf_field_definition['salesforce']['type'];
    $errors = array();
    switch ($type) {
      case 'boolean':
        if (empty($value)) {
          $object->{$sf_fieldname} = 0;
        }
        else {
          $object->{$sf_fieldname} = 1;
        }
        break;
      case 'time':
      case 'date':
      case 'datetime':
        $time = strtotime($value);
        if (empty($time)) {
          $errors[] = array(
            'message' => 'Salesforce cannot accept empty values for @type fields on fieldname @fieldname: "@value" value provided.',
            'vars' => array(
              '@type' => $type,
              '@value' => $value,
              '@fieldname' => $sf_fieldname,
            ),
          );
        }
        else {

          // The export handler should have handled this, but reformat to
          // DATE_ATOM, just in case.
          $object->{$sf_fieldname} = gmdate(DATE_ATOM, $time);
        }
        break;
      case 'email':

        // Remove spaces.
        $value = trim($value);
        if (salesforce_api_valid_email_address($value)) {
          $object->{$sf_fieldname} = $value;
        }
        else {
          $errors[] = array(
            'message' => 'Invalid email address provided for Salesforce export on fieldname @fieldname: @value',
            'vars' => array(
              '@fieldname' => $sf_fieldname,
              '@value' => $value,
            ),
          );
          continue;
        }
        break;
      case 'percent':
      case 'currency':
      case 'int':
      case 'double':
        if (is_numeric($value)) {
          $object->{$sf_fieldname} = $value;
        }
        else {
          $errors[] = array(
            'message' => 'Invalid value provided for Salesforce @type field on fieldname @fieldname: "@value".',
            'vars' => array(
              '@type' => $type,
              '@value' => $value,
              '@fieldname' => $sf_fieldname,
            ),
          );
        }
        break;
      case 'reference':
      case 'id':
        if (!is_sfid($value)) {
          $errors[] = array(
            'message' => 'Invalid value provided for Salesforce @type field on fieldname @fieldname: "@value".',
            'vars' => array(
              '@type' => $type,
              '@value' => $value,
              '@fieldname' => $sf_fieldname,
            ),
          );
        }
        else {
          $object->{$sf_fieldname} = $value;
        }
        break;
      case 'string':
      case 'picklist':
      case 'multipicklist':
      case 'combobox':
      case 'base64':
      case 'textarea':
      case 'phone':
      case 'url':
      case 'encryptedstring':
      default:
        $object->{$sf_fieldname} = $value;
        break;
    }
    $max_len = $sf_field_definition['salesforce']['length'];
    if ($max_len && isset($object->{$sf_fieldname}) && drupal_strlen($object->{$sf_fieldname}) > $max_len) {
      $object->{$sf_fieldname} = drupal_substr($object->{$sf_fieldname}, 0, $max_len);
    }
  }
  if (!empty($errors)) {
    foreach ($errors as $error) {
      salesforce_api_log(SALESFORCE_LOG_SOME, $error['message'], $error['vars'], WATCHDOG_ERROR);
    }
  }
  if (!empty($fieldsToNull)) {
    $object->fieldsToNull = $fieldsToNull;
  }
  return $object;
}

/**
 * Loads the Salesforce ID and fieldmap index of a Drupal object.
 *
 * @param $type
 *   The type of the Drupal object you are requesting data for; node or user.
 * @param $id
 *   The associated unique ID used to identify the object in Drupal.
 * @return
 *   An array containing the associated Salesforce object type and ID or an
 *     empty array if no data was found.
 */
function salesforce_api_id_load($type, $id) {

  // Query the main ID table for the associated data.
  $result = db_query("SELECT sfid, name FROM {salesforce_object_map} WHERE drupal_type = '%s' AND oid = %d", $type, $id);

  // Return an empty array if no data was found.
  if (!($data = db_fetch_object($result))) {
    return (object) array(
      'sfid' => NULL,
      'name' => NULL,
    );
  }
  else {

    // Otherwise return the Salesforce object type and ID.
    return $data;
  }
}

/**
 * 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, $type = 'node') {
  $result = db_query("SELECT oid FROM {salesforce_object_map} WHERE sfid = '%s' AND drupal_type = '%s'", $sfid, $type);
  return db_result($result);
}

/**
 * Saves the Salesforce ID and fieldmap index of a Drupal object.
 * This function will store the timestamp of creation of the object mapping
 * It will also store data about when the object was last exported to Salesforce
 * or imported to Drupal.
 * *
 * @param string$drupal_type
 *   The type of the Drupal object you are requesting data for; node or user.
 * @param int $oid
 *   The associated unique ID used to identify the object in Drupal.
 * @param string $sfid
 *   The Salesforce ID of the associated object in the Salesforce database.
 * @param string $name
 *   The name of the fieldmap used to generate the export.
 * @param string $op_type
 *   The type of operation being performed. Possible values are 'import',
 *   'export', and 'link'.
 * @return
 *   TRUE if was successfull in saving the link, FALSE otherwise.
 * @todo salesforce_api_id_save_multi()
 */
function salesforce_api_id_save($drupal_type = NULL, $oid = NULL, $sfid = NULl, $name = NULL, $op_type = NULL) {

  // Allow other modules to respond to salesforce_api_id_save being called
  foreach (module_implements('salesforce_api_id_save_alter') as $module) {
    $function = $module . '_salesforce_api_id_save_alter';
    $continue = $function($drupal_type, $oid, $sfid, $name, $op_type);
    if ($continue === FALSE) {
      return FALSE;
    }
  }
  if ($drupal_type && $oid && $sfid && $name && $op_type) {

    // Create our record for saving to the database
    $record = new stdClass();
    $record->drupal_type = $drupal_type;
    $record->name = $name;
    $record->oid = (int) $oid;

    // Set the last_import/last_export field depending on what type of operation
    // is being performed
    $op_type == 'export' ? $record->last_export = time() : ($record->last_import = time());
    $record->sfid = $sfid;
    $existing_record = db_fetch_array(db_query("SELECT oid, drupal_type, name, sfid, last_import, last_export, created FROM {salesforce_object_map} WHERE drupal_type = '%s' AND oid = %d", $drupal_type, $oid));
    if ($existing_record['sfid']) {

      // Update the record
      if (drupal_write_record('salesforce_object_map', $record, array(
        'drupal_type',
        'oid',
      ))) {
        salesforce_api_log(SALESFORCE_LOG_ALL, 'On !op, successfully re-saved mapping between Drupal !type !oid and Salesforce ID !sfid with fieldmap !name', array(
          '!op' => $op_type,
          '!type' => $drupal_type,
          '!oid' => $oid,
          '!sfid' => $sfid,
          '!name' => $name,
        ));
        return TRUE;
      }
      else {

        // Log an error if we failed to re-save the mapping
        salesforce_api_log(SALESFORCE_LOG_ALL, 'On !op, failed to re-save mapping between Drupal !type !oid with Salesforce ID !sfid with fieldmap !name', array(
          '!op' => $op_type,
          '!type' => $drupal_type,
          '!oid' => $oid,
          '!sfid' => $sfid,
          '!name' => $name,
        ), WATCHDOG_ERROR);
        return FALSE;
      }
    }
    else {

      // We will insert a new mapping, so set the value of created
      $record->created = time();
      if (drupal_write_record('salesforce_object_map', $record)) {
        salesforce_api_log(SALESFORCE_LOG_ALL, 'On !op, successfully saved new mapping between Drupal !type !oid and Salesforce ID !sfid with fieldmap !name', array(
          '!op' => $op_type,
          '!type' => $drupal_type,
          '!oid' => $oid,
          '!sfid' => $sfid,
          '!name' => $name,
        ));
        return TRUE;
      }
      else {

        // Log an error if the new mapping was not saved
        salesforce_api_log(SALESFORCE_LOG_ALL, 'On !op, failed to save new mapping between Drupal !type !oid with Salesforce ID !sfid with fieldmap !name', array(
          '!op' => $op_type,
          '!type' => $drupal_type,
          '!oid' => $oid,
          '!sfid' => $sfid,
          '!name' => $name,
        ), WATCHDOG_ERROR);
        return FALSE;
      }
    }
  }
  else {

    // salesforce_api_id_save was called with insufficient information
    salesforce_api_log(SALESFORCE_LOG_ALL, 'salesforce_api_id_save() called with insufficient data. Drupal type: !type, OID: !oid, SFID: !sfid, Fieldmap !name, Op !op', array(
      '!op' => $op_type,
      '!type' => $drupal_type,
      '!oid' => $oid,
      '!sfid' => $sfid,
      '!name' => $name,
    ), WATCHDOG_ERROR);
  }
}

/**
 * Removes a link between a Salesforce record and a Drupal object. Arguments
 * correspond to the columns in the salesforce_object_map table
 *
 * @param array $args
 *   Associative array of criteria for deletion. These criteria will be AND'ed
 *   together to create a sql DELETE query. Keys are:
 *     - 'oid'
 *        drupal id of the object (nid, uid, etc).
 *
 *     - 'name'
 *        machine name of the fieldmap corresponding to this linkage.
 *        'name' or 'drupal_type' MUST be set.
 *
 *     - 'drupal_type'
 *        type of drupal object corresponding to this linkage.
 *        e.g. "node", "user", etc.
 *        'name' or 'drupal_type' MUST be set.
 *
 *     - 'sfid'
 *        the salesforce id of the object
 *
 *  Keys can be supplied in various combinations, but $args must not be empty.
 *  EITHER "oid" must be set along with "drupal_type" or "name"
 *  OR
 *  "sfid" must be set
 *
 *  In other words, minimal valid key combinations are:
 *    - 'sfid'
 *    - 'drupal_type', 'oid'
 *    - 'name', 'oid'
 */
function salesforce_api_id_unlink($args) {
  $valid_args = !empty($args['sfid']) || !empty($args['oid']) && (!empty($args['drupal_type']) || !empty($args['name']));
  if (!$valid_args) {
    return FALSE;
  }
  $sql = "DELETE FROM {salesforce_object_map} ";
  $sql_args = array();
  $where = array();
  if (!empty($args['oid'])) {
    $where[] = ' oid = %d ';
    $sql_args[] = $args['oid'];
  }
  if (!empty($args['sfid'])) {
    $where[] = ' sfid = "%s" ';
    $sql_args[] = $args['sfid'];
  }
  if (!empty($args['name'])) {
    $where[] = ' name = "%s" ';
    $sql_args[] = $args['name'];
  }
  if (!empty($args['drupal_type'])) {
    $where[] = ' drupal_type = "%s" ';
    $sql_args[] = $args['drupal_type'];
  }
  $sql .= ' WHERE ' . implode(' AND ', $where);
  db_query($sql, $sql_args);
  module_invoke_all('salesforce_api_post_unlink', $args);
}

/**
 * Wrapper for SFBaseClient::delete
 *
 * @param string $sfid a Salesforce ID
 */
function salesforce_api_delete_salesforce_objects($sfids) {
  if (empty($sfids)) {
    return;
  }
  if (is_string($sfids)) {
    $sfids = array(
      $sfids,
    );
  }
  $real_sfids = array();
  foreach ($sfids as $i => $sfid) {
    if (is_sfid($sfid)) {
      $real_sfids[$i] = $sfid;
    }
  }
  if (empty($real_sfids)) {
    return FALSE;
  }
  try {
    $sf = salesforce_api_connect();
    return $sf->client
      ->delete($real_sfids);
  } catch (Exception $e) {
    salesforce_api_log(SALESFORCE_LOG_SOME, 'Failed to delete Salesforce objects with ids %s : %s.', array(
      implode(', ', $real_sfids),
      $e
        ->getMessage(),
    ), WATCHDOG_ERROR);
    return FALSE;
  }
}
function salesforce_api_search_for_duplicates($direction, $fieldmap_type, $object, $fieldmap_name) {

  // 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, $fieldmap_type, $object, $fieldmap_name);
}

/**
 * Implementation of 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(
      'file' => 'salesforce_api.admin.inc',
      'arguments' => array(
        'form' => NULL,
      ),
    ),
    'salesforce_api_object_options' => array(
      'arguments' => array(
        'element' => NULL,
      ),
    ),
    'salesforce_api_drupal_sfapi_automatic' => array(
      'arguments' => array(
        'element' => NULL,
      ),
    ),
  );
}

/**
 * Wraps SforceBaseClient::query. Queries Salesforce for a record or set of
 * records. For information about SOQL syntax, @see http://www.salesforce.com/us/developer/docs/api/Content/sforce_api_calls_soql_select.htm
 * @param string $query
 *  A SOQL query string.
 * @param array $options.
 *  An array of options for how the query should be done.
 *  Valid options are:
 *  queryAll (bool)
 *    Whether or not to include deleted records in this query. Default FALSE.
 *  queryMore (bool)
 *    Whether or not to include all records matching this query. Default FALSE.
 *  limit (integer)
 *    Set the SOQL Batch Size. This is NOT analagous to SOQL LIMIT (nor SQL
 *    LIMIT). Minimum value is 200. Maximum value is 2,000. Default varies. For
 *    more information about batch size, @see http://www.salesforce.com/us/developer/docs/api/Content/sforce_api_calls_soql_changing_batch_size.htm
 * @param object $sf
 *  A Salesforce connection. If not set, this function will connect.
 * @return object
 *  An array of matching records on success, or FALSE on failure.
 */
function salesforce_api_query($query, $options = array(), $sf = NULL) {

  // If not passed a Salesforce connection, then connect to Salesforce.
  if (!is_object($sf)) {
    $sf = salesforce_api_connect();

    // Return FALSE if could not connect to Salesforce.
    if ($sf == FALSE) {
      return FALSE;
    }
  }

  // Merge in defaults.
  $options += array(
    'queryAll' => FALSE,
    'queryMore' => FALSE,
    'limit' => NULL,
  );

  // Set a limit for the query if requested.
  // @todo: Determine why this limit is not being applied.
  if (is_numeric($options['limit']) && $options['limit'] > 0) {
    if ($options['limit'] > 2000) {
      $options['limit'] = 2000;
    }
    if ($options['limit'] < 200) {
      $options['limit'] = 200;
    }
    $queryOptions = new QueryOptions($options['limit']);
    $sf->client
      ->setQueryOptions($queryOptions);
  }

  // Execute the query. Include deleted records if set to queryAll.
  try {
    if (!$options['queryAll']) {
      $result = $sf->client
        ->query($query);
    }
    else {
      $query = preg_replace("@SELECT\\s+@si", "SELECT IsDeleted, ", $query);
      $result = $sf->client
        ->queryAll($query);
    }
  } catch (Exception $e) {
    salesforce_api_log(SALESFORCE_LOG_ALL, 'Salesforce query failed with exception: ' . $e
      ->getMessage(), array(), WATCHDOG_ERROR);
    return FALSE;
  }
  $records = $result->records;

  // If set to queryMore and this query hasn't retrieved all the results, then query for the rest.
  // @todo: Find a resultset large enough to test this.
  if (!$result->done && $options['queryMore']) {
    $moreRecords = _salesforce_api_querymore($result->queryLocator, $sf);
    if (is_array($moreRecords)) {
      $records = array_merge($records, $moreRecords);
    }
  }

  // Return records if any have been retrieved, or else return FALSE.
  return is_array($records) ? $records : FALSE;
}

/**
 * Wraps SforceBaseClient::queryMore. Needs a query locator for an active query
 * and a Salesforce connection, so this must only be called from salesforce_api_query().
 * Calls itself recursively until records are retrieved.
 *
 * @param object $queryLocator
 *  The position within the current query from which to begin.
 * @param object $sf
 *  The active Salesforce connection.
 * @return array
 *  An array of matching records on success, or FALSE on failure.
 */
function _salesforce_api_querymore($queryLocator, $sf) {
  if (!is_object($sf)) {
    return FALSE;
  }

  // Execute the queryMore
  try {
    $result = $sf->client
      ->queryMore($queryLocator);
  } catch (Exception $e) {
    salesforce_api_log(SALESFORCE_LOG_ALL, 'Salesforce queryMore failed with exception: ' . $e
      ->getMessage(), array(), WATCHDOG_ERROR);
    return FALSE;
  }

  // Recursively merge results
  $records = $result->records;
  if (!$result->done) {
    $moreRecords = _salesforce_api_querymore($result->queryLocator, $sf);
    if (is_array($moreRecords)) {
      $records = array_merge($records, $moreRecords);
    }
  }
  return $records;
}

/**
 * Wraps SforceBaseClient::retrieve. Retrieve an object from Salesforce with
 * standard fields and any data in fields defined in the name object.
 *
 * @param $ids
 *   An array of Salesforce IDs for the objects to retrieve.
 * @param $name
 *   The name of the fieldmap that contains the fields to retrieve.
 * @return
 *   The single matching Salesforce objects or an array of all the objects
 *     if more than one are returned.
 */
function salesforce_api_retrieve($ids, $name) {
  $sf = salesforce_api_connect();
  if (!$sf) {

    // Let modules react to a failure to export this node.
    module_invoke_all('salesforce_api_export_connect_fail', NULL, $name, $ids);
    if (user_access('administer salesforce')) {
      drupal_set_message(t('Unable to connect to Salesforce using <a href="!url">current credentials</a>.', array(
        '!url' => url(SALESFORCE_PATH_ADMIN),
      )));
    }
    return FALSE;
  }

  // Load the fieldmap so we can get the object name.
  $map = salesforce_api_fieldmap_load($name);
  $object = salesforce_api_fieldmap_objects_load('salesforce', $map->salesforce);
  $fields = array_keys($object['fields']);
  return $sf->client
    ->retrieve(implode(', ', $fields), $map->salesforce, $ids);
}

/**
 * Wrapper for SOAP SforceBaseClient::getUpdated. Searches for records
 * updated/created between start and end date.
 * @param $type
 *  A string of the name of the Salesforce object to retrieve data for, or
 *  a Salesforce fieldmap object.
 * @param int $start
 *  The timestamp for the beginning of the query
 * @param int $end
 *  The timestamp for the end of the query
 * @return FALSE if failed, or an object containing an array of Ids and the latest date covered.
 *  $response->ids = array of SFIDS
 *  $response->latestDateCovered = timestamp of latest updated Salesforce object
 */
function salesforce_api_get_updated($type, $start, $end) {

  // If $type is an object, we only need the Salesforce type.
  if (is_object($type) && $type->salesforce) {
    $type = $type->salesforce;
  }
  if (!$type || !$start || !$end) {
    return FALSE;
  }
  $sf = salesforce_api_connect();
  if ($sf) {
    try {
      $response = $sf->client
        ->getUpdated($type, (int) $start, (int) $end);
    } catch (Exception $e) {

      // Log the error message.
      salesforce_api_log(SALESFORCE_LOG_SOME, 'Could not get updated records from Salesforce: %message.', array(
        '%message' => $e->faultstring,
        '!debug' => check_plain($sf->client
          ->getLastRequest()),
      ), WATCHDOG_ERROR);

      // Indicate the failed action.
      return FALSE;
    }
    if (!empty($response->ids)) {
      return $response;
    }
    else {
      return FALSE;
    }
  }
}

/**
 * Wrapper for SOAP SforceBaseClient::getDeleted. Searches for records
 * deleted between start and end date.
 * @param string $type
 *  The name of the Salesforce object to retrieve data for
 * @param int $start
 *  The timestamp for the beginning of the query
 * @param int $end
 *  The timestamp for the end of the query
 * @return FALSE if failed, or an object containing an array of Ids and the latest date covered.
 *  $response->deletedRecords = array of objects containing deletedDate and SFID
 *  $response->earliestDateAvailable = timestamp of the earliest available deleted object for the query
 *  $response->latestDateCovered = timestamp of latest deleted Salesforce object
 */
function salesforce_api_get_deleted($type, $start, $end) {
  if (!$type || !$start || !$end) {
    return FALSE;
  }
  $sf = salesforce_api_connect();
  if ($sf) {
    try {
      $response = $sf->client
        ->getDeleted($type, $start, $end);
    } catch (Exception $e) {

      // Log the error message.
      salesforce_api_log(SALESFORCE_LOG_SOME, 'Could not get deleted records from Salesforce: %message.', array(
        '%message' => $e->faultstring,
        '!debug' => check_plain($sf->client
          ->getLastRequest()),
      ), WATCHDOG_ERROR);

      // Indicate the failed delete.
      return FALSE;
    }
    if ($response->deletedRecords) {
      return $response;
    }
    else {
      return FALSE;
    }
  }
}

/**
 * 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);
    if (user_access('administer salesforce')) {
      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' => $object->label,
    'fields' => array(),
  );
  if (!empty($object->fields) && is_array($object->fields)) {
    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));
      foreach ($booleans as $bool) {
        if (empty($sf_definition['sf_type'])) {
          $sf_definition['sf_type'] = (int) $source[$bool] * constant('SALESFORCE_FIELD_' . strtoupper($bool));
        }
        else {
          $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;
}

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

  // Check to see if we can connect to Salesforce
  $sf = salesforce_api_connect();
  if ($sf == TRUE) {

    // if the cache has already been cleared or is expired, then rebuild it.
    if (!$cache || (int) $_SERVER['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)) {
    if (user_access('administer salesforce')) {
      drupal_set_message(t('DescribeSObject expects a string. ' . gettype($type) . ' received.'), 'error');
    }
    return FALSE;
  }
  $objects = salesforce_api_describeSObjects($type);
  if (!empty($objects[$type])) {
    return $objects[$type];
  }
  else {
    if (user_access('administer salesforce')) {
      drupal_set_message(t('DescribeSObject failed to find ' . $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)) {
    if (user_access('administer salesforce')) {
      drupal_set_message(t('DescribeSObjects expects an array. ' . gettype($types) . ' received.'), 'error');
    }
    return FALSE;
  }
  $types = array_filter($types);

  // 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);
      if (user_access('administer salesforce')) {
        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) {
    salesforce_api_log(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;
}

/**
 * Compares mixed 15- and 18-character Salesforce IDs. Up-converts 15-character
 * strings for comparison when applicable. Based Christian G. Warden's code at http://xn.pinkhamster.net/blog/tech/salesforce/convert-15-character-salesforce-ids-to-18-characters-with-php.html
 *
 * @return TRUE if IDs match, or FALSE
 * @see http://salesforce-id.com
 */
function salesforce_api_id_compare($a, $b) {
  if (strlen($a) != strlen($b)) {
    if (strlen($a) == 15) {
      $a = salesforce_api_id_convert($a);
    }
    if (strlen($b) == 15) {
      $b = salesforce_api_id_convert($b);
    }
  }
  return $a == $b;
}

/**
 * Converts a 15-character Salesforce ID to 18-character ID.
 *
 * @param string $sfid15
 * @return case-insensitive 18-character Salesforce ID
 */
function salesforce_api_id_convert($sfid15) {
  if (strlen($sfid15) != 15) {
    return $sfid15;
  }
  $chunks = str_split($sfid15, 5);
  $extra = '';
  foreach ($chunks as $chunk) {
    $chars = str_split($chunk, 1);
    $bits = '';
    foreach ($chars as $char) {
      $bits .= !is_numeric($char) && $char == strtoupper($char) ? '1' : '0';
    }
    $map = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ012345';
    $extra .= substr($map, base_convert(strrev($bits), 2, 10), 1);
  }
  return $sfid15 . $extra;
}

/**
 * Implements hook_views_api
 */
function salesforce_api_views_api() {
  return array(
    'api' => 2,
  );
}

/**
 * @addtogroup exportables
 * @{
 */
function salesforce_api_salesforce_field_map_load_all() {
  ctools_include('export');
  return ctools_export_load_object('salesforce_field_map');
}
function salesforce_api_salesforce_field_map_load($name) {
  ctools_include('export');
  $result = ctools_export_load_object('salesforce_field_map', 'names', array(
    $name,
  ));
  if (isset($result[$name])) {
    return $result[$name];
  }

  // For backwards compatibility, search on fieldmap (numeric id)
  $result = ctools_export_load_object('salesforce_field_map', 'conditions', array(
    'fieldmap' => $name,
  ));
  if (!empty($result)) {

    // "fieldmap" is always unique - if nonempty, this will always contain a
    // direct hit.
    return current($result);
  }
}
function salesforce_api_salesforce_field_map_load_by($conditions) {
  ctools_include('export');
  $result = ctools_export_load_object('salesforce_field_map', 'conditions', $conditions);
  return $result;
}
function salesforce_api_salesforce_field_map_save(&$map) {
  salesforce_api_fieldmap_save($map);
}
function salesforce_api_export_salesforce_field_map($fieldmap) {
  $map = salesforce_api_salesforce_field_map_load($fieldmap);
  drupal_set_title(t('Export Fieldmap'));
  $code = salesforce_api_salesforce_field_map_export($map);
  return drupal_get_form('ctools_export_form', $code, 'Export Fieldmap');
}
function salesforce_api_salesforce_field_map_export($map, $indent = '') {
  ctools_include('export');
  $output = ctools_export_object('salesforce_field_map', $map, $indent);
  return $output;
}
function salesforce_api_import_salesforce_field_map(&$form_state) {
  $form['name'] = array(
    '#type' => 'textfield',
    '#title' => t('Fieldmap'),
    '#description' => t('Enter the name of the new fieldmap. This is optional and is not necessary if you do not wish to rename the object. Lowercase letters, numbers, and underscores only please.'),
  );
  $form['object'] = array(
    '#type' => 'textarea',
    '#title' => t('Paste exported code here'),
    '#rows' => 15,
  );
  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Import'),
  );
  return $form;
}

/**
 * Make sure that an import actually provides a handler.
 */
function salesforce_api_import_salesforce_field_map_validate($form, &$form_state) {

  // First, run the PHP and turn the input code into an object.
  $name = $form_state['values']['name'];
  if (preg_match('/[^a-z0-9_]/', $name)) {
    form_error($form['name'], t('Invalid name. Please use letters, numbers, or underscores only.'));
  }
  ob_start();
  eval($form_state['values']['object']);
  ob_end_clean();

  // The object should appear as $salesforce_fieldmap.
  // This was the "identifier" set in the export section of the schema.
  if (empty($salesforce_field_map)) {
    $errors = ob_get_contents();
    if (empty($errors)) {
      $errors = t('No salesforce_field_map found.');
    }
    form_error($form['object'], t('Unable to get a fieldmap from the import. Errors reported: @errors', array(
      '@errors' => $errors,
    )));
  }
  if (empty($salesforce_field_map->drupal) || empty($salesforce_field_map->salesforce)) {
    form_error($form['object'], t('This fieldmap cannot be imported; the object definition is invalid.'));
    return;
  }
  if (!salesforce_api_fieldmap_source_enabled($salesforce_field_map)) {
    form_error($form['object'], t('This fieldmap cannot be imported, because the module which supports the Drupal entity "%entity" cannot be found. Please make sure you have required any modules with which this fieldmap was built.', array(
      '%entity' => $salesforce_field_map->drupal,
    )));
  }

  // Try to enable the SF object if it was not found.
  if (!salesforce_api_fieldmap_target_enabled($salesforce_field_map)) {
    form_error($form['object'], t('This fieldmap cannot be imported because salesforce_api cannot find a definition for the Salesforce object "%sfobj". Please verify your Salesforce connection and settings.', array(
      '%sfobj' => $salesforce_field_map->salesforce,
    )));
  }
  $form_state['obj'] = $salesforce_field_map;
}

/**
 * Helper function to determine whether the Drupal object (source) for a given
 * fieldmap is available.
 *
 * @param object $map
 * @return boolean
 */
function salesforce_api_fieldmap_source_enabled($salesforce_field_map) {
  $source = salesforce_api_fieldmap_objects_load('drupal', $salesforce_field_map->drupal);
  return !empty($source);
}

/**
 * Helper function to determine whether the Salesforce object (target) for a
 *given fieldmap is available.
 *
 * @param object $map
 * @param boolean $enable (optional) -
 *   if the object is not initially available, whether or not to try and enable
 *   it before returning.
 * @return boolean
 */
function salesforce_api_fieldmap_target_enabled($map, $enable = TRUE) {
  $sf_objects = variable_get('salesforce_api_enabled_objects', array(
    'Campaign',
    'Contact',
    'Lead',
  ));
  if ($enable && !in_array($map->salesforce, $sf_objects)) {
    $sf_objects[] = $map->salesforce;
    variable_set('salesforce_api_enabled_objects', array_filter($sf_objects));
    salesforce_api_cache_build();
  }
  $target = salesforce_api_fieldmap_objects_load('salesforce', $map->salesforce, $reset = TRUE);
  return !empty($target);
}

/**
 * Save the imported object.
 */
function salesforce_api_import_salesforce_field_map_submit($form, &$form_state) {
  $salesforce_field_map = $form_state['obj'];
  if (!empty($form_state['values']['name'])) {
    $salesforce_field_map->name = $form_state['values']['name'];
  }
  salesforce_api_salesforce_field_map_save($salesforce_field_map);
  $form_state['redirect'] = SALESFORCE_PATH_FIELDMAPS . '/' . $salesforce_field_map->name . '/edit';
}

/**
 * @} End of "addtogroup exportables".
 */

/**
 * @addtogroup encryption
 * @{
 */

/**
 * Wrappers for encryption lib. Right now only AES encryption is supported.
 * If/when other methods are supported, this abstraction layer will make the
 * transition easier.
 */
function salesforce_api_decrypt($value) {
  return function_exists('aes_decrypt') ? aes_decrypt($value) : $value;
}
function salesforce_api_encrypt($value) {
  return function_exists('aes_encrypt') ? aes_encrypt($value) : $value;
}
function salesforce_api_encryption_available($options = array()) {
  $defaults = array(
    'check_config' => TRUE,
    'display_errors' => FALSE,
    'display_warnings' => FALSE,
    'display_all' => FALSE,
    'fail_threshold' => 'warnings',
  );
  $options = array_merge($defaults, $options);
  extract($options);
  $errors = array();
  $warnings = array();
  if (!module_exists('aes')) {
    $errors[] = 'AES Encryption module is not installed.';
  }
  elseif ($check_config) {
    if (!variable_get('aes_key_path', FALSE) || variable_get('aes_key_storage_method', FALSE) != 'File') {
      $warnings[] = 'AES Encryption is installed but not configured securely.
        Please go <a href="/admin/settings/aes">configure AES Encryption to use
        file storage</a> to enable encryption for SalesForce credentials.';
    }
  }
  if ($display_errors || $display_all) {
    foreach ($errors as $msg) {
      drupal_set_message(t($msg), 'error');
    }
  }
  if ($display_warnings || $display_all) {
    foreach ($warnings as $msg) {
      drupal_set_message(t($msg), 'warning');
    }
  }
  switch ($fail_threshold) {
    case 'errors':
      if (empty($errors)) {
        return TRUE;
      }
    case 'warnings':
      if (empty($errors) && empty($warnings)) {
        return TRUE;
      }
  }
  return FALSE;
}

/**
 * @} End of "addtogroup encryption".
 */

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 Implementation of hook_cron().
salesforce_api_decrypt Wrappers for encryption lib. Right now only AES encryption is supported. If/when other methods are supported, this abstraction layer will make the transition easier.
salesforce_api_delete_object_map Given a drupal type and oid, delete an object mapping
salesforce_api_delete_salesforce_objects Wrapper for SFBaseClient::delete
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_encrypt
salesforce_api_encryption_available
salesforce_api_export_salesforce_field_map
salesforce_api_features_api Implementation of hook_features_api().
salesforce_api_fieldmap_clone Clones a fieldmap.
salesforce_api_fieldmap_delete Deletes a fieldmap from the database.
salesforce_api_fieldmap_description
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 drupal_type 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 Implementation of 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_source_enabled Helper function to determine whether the Drupal object (source) for a given fieldmap is available.
salesforce_api_fieldmap_system_fields Returns an array of system fields that are retrievable from Salesforce.
salesforce_api_fieldmap_target_enabled Helper function to determine whether the Salesforce object (target) for a given fieldmap is available.
salesforce_api_get_deleted Wrapper for SOAP SforceBaseClient::getDeleted. Searches for records deleted between start and end date.
salesforce_api_get_id_with_sfid Get an object id using the salesforce id
salesforce_api_get_updated Wrapper for SOAP SforceBaseClient::getUpdated. Searches for records updated/created between start and end date.
salesforce_api_help Implements hook_help().
salesforce_api_id_compare Compares mixed 15- and 18-character Salesforce IDs. Up-converts 15-character strings for comparison when applicable. Based Christian G. Warden's code at…
salesforce_api_id_convert Converts a 15-character Salesforce ID to 18-character ID.
salesforce_api_id_load Loads the Salesforce ID and fieldmap index of a Drupal object.
salesforce_api_id_save Saves the Salesforce ID and fieldmap index of a Drupal object. This function will store the timestamp of creation of the object mapping It will also store data about when the object was last exported to Salesforce or imported to Drupal. *
salesforce_api_id_unlink Removes a link between a Salesforce record and a Drupal object. Arguments correspond to the columns in the salesforce_object_map table
salesforce_api_import_salesforce_field_map
salesforce_api_import_salesforce_field_map_submit Save the imported object.
salesforce_api_import_salesforce_field_map_validate Make sure that an import actually provides a handler.
salesforce_api_init
salesforce_api_log Wraps watchdog(). Logs a message to the watchdog based on the Salesforce log settings.
salesforce_api_login Helper function for salesforce_api_connect(). You should probably not call this function directly
salesforce_api_menu Implementation of hook_menu().
salesforce_api_object_to_fieldmap_fields Convert Salesforce object fields to fieldmap array for saving
salesforce_api_perm Implementation of hook_perm().
salesforce_api_query Wraps SforceBaseClient::query. Queries Salesforce for a record or set of records. For information about SOQL syntax,
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_retrieve Wraps SforceBaseClient::retrieve. Retrieve an object from Salesforce with standard fields and any data in fields defined in the name object.
salesforce_api_salesforce_field_map_export
salesforce_api_salesforce_field_map_load
salesforce_api_salesforce_field_map_load_all
salesforce_api_salesforce_field_map_load_by
salesforce_api_salesforce_field_map_save
salesforce_api_search_for_duplicates
salesforce_api_theme Implementation of hook_theme().
salesforce_api_toolkit_installed Simple check for Salesforce Toolkit.
salesforce_api_valid_email_address Salesforce does not accept email addresses with relative domains, like root@localhost. This function is based on Drupal's valid_email_address. Greater men than I have tried and failed to capture valid email addresses with simple regular…
salesforce_api_views_api Implements hook_views_api
_salesforce_api_querymore Wraps SforceBaseClient::queryMore. Needs a query locator for an active query and a Salesforce connection, so this must only be called from salesforce_api_query(). Calls itself recursively until records are retrieved.
_salesforce_fieldmap_access Access callback

Constants