salesforce_api.module in Salesforce Suite 7.2
Same filename and directory in other branches
Defines an API that enables modules to interact with the Salesforce server.
File
salesforce_api/salesforce_api.moduleView 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', salesforce_api_locate_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/services/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;
}
}
/**
* Locates the Salesforce PHP Toolkit, if installed.
*
* @return string $toolkit_path
* The path to the Salesforce Toolkit, or an empty string if not found.
*/
function salesforce_api_locate_toolkit() {
$toolkit_path = variable_get('salesforce_api_toolkit_path', '');
// If the toolkit path is not set or is no longer valid, try to find it.
if ($toolkit_path == '' || !file_exists($toolkit_path . '/soapclient/SforceEnterpriseClient.php')) {
$profile = drupal_get_profile();
$config = conf_path();
// Use Libraries if it is available
if (function_exists('libraries_get_path')) {
$toolkit_path = libraries_get_path('salesforce') . '/toolkit';
}
else {
if (file_exists("profiles/{$profile}/libraries/salesforce/toolkit")) {
$toolkit_path = "profiles/{$profile}/libraries/salesforce/toolkit";
}
else {
if (file_exists("sites/all/libraries/salesforce/toolkit")) {
$toolkit_path = "sites/all/libraries/salesforce/toolkit";
}
else {
if (file_exists("{$config}/libraries/salesforce/toolkit")) {
$toolkit_path = "{$config}/libraries/salesforce/toolkit";
}
else {
$toolkit_path = "";
}
}
}
}
variable_set('salesforce_api_toolkit_path', $toolkit_path);
}
return $toolkit_path;
}
/**
* Implements hook_init().
* Checks to see if the Salesforce PHP Toolkit is installed, and warns if it is not.
*/
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
// @todo Determine the D7 equivalent of filter_filter with the process op and 1 parameter
return file_get_contents(drupal_get_path('module', 'salesforce_api') . "/../README.txt");
}
}
/**
* Implements hook_features_api().
*/
function salesforce_api_features_api() {
return array(
'salesforce_fieldmap' => array(
'name' => t('Salesforce Fieldmaps'),
'feature_source' => TRUE,
'default_hook' => 'default_salesforce_fieldmaps',
'default_file' => FEATURES_DEFAULTS_INCLUDED,
'file' => drupal_get_path('module', 'salesforce_api') . '/salesforce_api.features.inc',
),
);
}
/**
* Implements 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 . '/%salesforce_api_fieldmap'] = 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,
),
'file' => 'salesforce_api.admin.inc',
'weight' => 10,
);
$items[SALESFORCE_PATH_FIELDMAPS . '/%salesforce_api_fieldmap/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 . '/%salesforce_api_fieldmap/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 . '/%salesforce_api_fieldmap/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 . '/%salesforce_api_fieldmap/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_delete_revert_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 . '/%salesforce_api_fieldmap/revert'] = $items[SALESFORCE_PATH_FIELDMAPS . '/%salesforce_api_fieldmap/delete'];
$items[SALESFORCE_PATH_FIELDMAPS . '/%salesforce_api_fieldmap/revert']['title'] = 'Revert';
$items[SALESFORCE_PATH_FIELDMAPS . '/%salesforce_api_fieldmap/revert']['access callback'] = '_salesforce_fieldmap_delete_revert_access';
$items[SALESFORCE_PATH_FIELDMAPS . '/%salesforce_api_fieldmap/revert']['access arguments'] = array(
'administer salesforce',
'revert',
$map_id_arg,
);
$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 . '/%salesforce_api_fieldmap/export'] = array(
'title' => 'Export',
'description' => 'Export a fieldmap.',
'page callback' => 'salesforce_api_export_salesforce_fieldmap',
'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_fieldmap',
),
'access arguments' => array(
'administer salesforce',
),
'type' => MENU_LOCAL_TASK,
'file' => 'salesforce_api.admin.inc',
'weight' => 30,
);
}
return $items;
}
/**
* %wildcard_load implementation for %salesforce_api_fieldmap menu wildcard.
* @see salesforce_api_salesforce_fieldmap_load
*/
function salesforce_api_fieldmap_load($name) {
if (empty($name)) {
return;
}
return salesforce_api_salesforce_fieldmap_load($name);
}
/**
* Implements hook_permission().
*/
function salesforce_api_permission() {
return array(
// @todo: Make this permission more granular, so that users can import and export without having it.
'administer salesforce' => array(
'title' => t('administer salesforce'),
'description' => t('Access Salesforce administration screens, manually export, import, unlink entities'),
'restrict access' => TRUE,
),
// @todo: Possibly move this into a separate module due to security implications.
'use php for salesforce fixed values' => array(
'title' => t('use php for salesforce fixed values'),
'description' => t('Write PHP code in a fieldmap for exporting values. Has the potential to create fatal errors if done incorrectly.'),
'restrict access' => TRUE,
),
);
}
/**
* Access callback for fieldmap editing screens.
*
*/
function _salesforce_fieldmap_access($perm, $op = 'edit', $map = NULL) {
if (empty($map) || !is_object($map)) {
return FALSE;
}
return user_access($perm);
}
/**
* Access callback for delete / revert operations. Only code-based, overridden
* maps can be reverted and only database-only maps can be deleted.
*/
function _salesforce_fieldmap_delete_revert_access($perm, $op = 'delete', $map = NULL) {
if (empty($map) || !is_object($map)) {
return FALSE;
}
switch ($map->type) {
case 'Normal':
return $op == 'delete' && user_access($perm);
case 'Default':
return FALSE;
case 'Overridden':
return $op == 'revert' && user_access($perm);
}
}
/**
* 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) {
// Mimic 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 DRUPAL_ROOT . '/' . 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 a proxy is configured.
$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.
// @todo: Determine whether it is a security risk to show !debug, since it can contain a username, password, and security token.
salesforce_api_log(SALESFORCE_LOG_SOME, 'Could not login to Salesforce: %message.', 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.
// @todo: If it may throw exceptions, should it be wrapped in a try/catch block?
$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');
}
}
/**
* Implements hook_fieldmap_objects().
*
* This will pull a cached version (if possible) of the available Salesforce fields for
* the object(s) in question. This helps prevent the Salesforce API query limit from being reached.
*/
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 nothing is in the cache, then fetch the objects from Salesforce.
if (empty($cache->data)) {
$objects = salesforce_api_cache_build();
}
else {
$objects = $cache->data;
}
}
// To mimic the structure for the $objects array for Drupal objects,
// (i.e., $objects[$entity_name][$bundle_name][$field_name]),
// wrap the return value in 'salesforce'.
return array(
'salesforce' => $objects,
);
}
/**
* Recreate the Salesforce object cache.
*
* @return array
* An array of Salesforce objects enabled for use in fieldmaps.
*/
function salesforce_api_cache_build() {
// @todo: Change Campaign to Case to provide a better demo of a node export.
$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]);
}
// Look up the cache expiration 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);
// Notify administrators when the Salesforce object cache has been refreshed.
// This typically happens either when a manual refresh is requested, a new Salesforce object
// is enabled for mapping, or Drupal does a flush of all caches.
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. (Will not be present on creation.)
* - fieldmap_name: the machine-readable name of the fieldmap. (Only set on creation.)
* - drupal_entity: the kind of Drupal entity being mapped.
* - drupal_bundle: the name of the bundle for this entity. (Always 'user' for the user entity.)
* - 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_keys = array();
if (!empty($map->fieldmap)) {
$primary_keys = array(
'name',
);
}
if (empty($map->name)) {
// If this fieldmap is being created, set its machine name to be human-readable if possible.
if (!empty($map->fieldmap_name)) {
$map->name = $map->fieldmap_name;
// Unset the fieldmap_name, since it is not part of the database schema.
unset($map->fieldmap_name);
}
else {
$map->name = md5(microtime());
}
}
drupal_write_record('salesforce_fieldmap', $map, $primary_keys);
}
/**
* Remove a field from all fieldmaps. This is particularly useful for implementations
* of hook_field_delete_instance. May be use to delete an occurrence
* in a single fieldmap (by supplying drupal_entity, drupal_bundle and/or salesforce_type), or every
* occurence in all fieldmaps (by supplying only the fieldname).
*
* @param string $fieldname
* The name of the field to be deleted. Either a Field API field, or a Salesforce field
* @param array $conditions (optional)
* If given, limit deleting of the field to this Drupal entity, Drupal bundle, and/or Salesforce type.
* These are the fields in the {salesforce_fieldmap} table: drupal_entity, drupal_bundle, salesforce
* @see sf_entity/sf_entity.module:sf_entity_field_delete_instance
* @todo Ensure that this behaves correctly, especially for if it is a Salesforce field being deleted.
*/
function salesforce_api_fieldmap_field_delete($fieldname, $conditions = array()) {
// If entity, bundle, and/or salesforce type are specified, load just the fieldmaps for that combination of conditions.
if (isset($conditions['drupal_entity']) && isset($conditions['drupal_bundle']) && isset($conditions['salesforce']) || isset($conditions['drupal_entity']) || isset($conditions['salesforce'])) {
$maps = salesforce_api_salesforce_fieldmap_load_by($conditions);
}
else {
$maps = salesforce_api_salesforce_fieldmap_load_all();
}
foreach ($maps as $map) {
// In the extremely unlikely event that a Salesforce field and a Drupal
// field share the same name, this function handles both.
if (isset($conditions['drupal_entity']) && !empty($conditions['drupal_entity'])) {
$drupal_fieldname = array_search($fieldname, $map->fields);
if ($drupal_fieldname) {
unset($map->fields[$drupal_fieldname]);
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 (isset($conditions['salesforce'])) {
$sf_fieldname = array_search($fieldname, $map->fields);
if (!empty($map->fields[$sf_fieldname])) {
unset($map->fields[$sf_fieldname]);
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_salesforce_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.
* @return array
* The number of fieldmaps and objects that were deleted, or FALSE if none were.
*/
function salesforce_api_fieldmap_delete($fieldmap) {
// Ensure that the fieldmap parameter is a string.
if (is_string($fieldmap)) {
$name = $fieldmap;
}
else {
$name = db_query('SELECT name FROM {salesforce_fieldmap} WHERE fieldmap = :fieldmap', array(
':fieldmap' => $fieldmap,
))
->fetchField();
}
if (empty($name)) {
return FALSE;
}
$fieldmaps_deleted = db_delete('salesforce_fieldmap')
->condition('name', $name)
->execute();
$objects_deleted = db_delete('salesforce_object_map')
->condition('name', $name)
->execute();
if (function_exists('sf_prematch_match_by_delete')) {
sf_prematch_match_by_delete($name);
}
return array(
'fieldmaps_deleted' => $fieldmaps_deleted,
'objects_deleted' => $objects_deleted,
);
}
/**
* Given a Drupal entity type and Drupal object id, delete an object mapping
*
* @param string $oid
* @param string $entity_name
* @param string $bundle_name (optional)
* @return int
* The number of object mappings deleted.
*/
function salesforce_api_delete_object_map($oid, $entity_name, $bundle_name = NULL) {
$num_deleted = db_delete('salesforce_object_map');
if (isset($bundle_name) && !empty($bundle_name)) {
$num_deleted
->condition('drupal_bundle', $bundle_name);
}
$num_deleted
->condition('drupal_entity', $entity_name)
->condition('oid', $oid)
->execute();
return $num_deleted;
}
/**
* Returns an array of fieldmaps for use as options in the Forms API.
*
* @param $drupal_entity
* Filters the fieldmaps by Drupal object.
* @param $drupal_bundle
* Filters the fieldmaps by Drupal 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($drupal_entity = NULL, $drupal_bundle = NULL, $salesforce = NULL, $automatic = NULL) {
$options = array();
// This does not need to not be optimized for performance since it's only an admin interface.
$maps = salesforce_api_salesforce_fieldmap_load_all();
foreach ($maps as $map) {
if ($drupal_entity && $map->drupal_entity != $drupal_entity) {
continue;
}
if ($drupal_bundle && $map->drupal_bundle != $drupal_bundle) {
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_entity, $map->drupal_bundle),
'@salesforce' => salesforce_api_fieldmap_object_label('salesforce', '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_fieldmap_objects
* and hook_fieldmap_objects_alter().
*
* @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.
* @param bool $reset
* Whether to reset the cache of object definitions.
* @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, $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($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;
}
/**
* 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.
* Note that both the $type and $entity parameters will be 'salesforce'
* in the case of Salesforce objects.
*
* @param $type
* @param $entity
* @param $bundle
* @return string
* The label for the object.
*/
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 (!isset($object['label']) && isset($object['fields']['name'])) {
return check_plain($object['fields']['name']['label']);
}
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_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.
// (Don't add a blank value - we don't want that to be selectable in the fieldmapping UI.)
$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 $name
* The name of the fieldmap used to process the Drupal object for 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_salesforce_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_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) {
$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 'tokens':
if (isset($map->fields[$sf_fieldname]['value'])) {
$value = token_replace($map->fields[$sf_fieldname]['value'], array(
$map->drupal_entity => $drupal_data,
), array(
'clear' => TRUE,
'sanitize' => FALSE,
));
}
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.
*/
// @todo: What to do if this returns multiple results?
function salesforce_api_id_load($oid, $entity_name, $bundle_name = NULL) {
// Query the main ID table for the associated data.
if (empty($bundle_name)) {
$result = db_query("SELECT sfid, name FROM {salesforce_object_map} WHERE drupal_entity = :drupal_entity AND oid = :oid", array(
':drupal_entity' => $entity_name,
':oid' => $oid,
));
}
else {
$result = db_query("SELECT sfid, name FROM {salesforce_object_map} WHERE drupal_entity = :drupal_entity AND drupal_bundle = :drupal_bundle AND oid = :oid", array(
':drupal_entity' => $entity_name,
'drupal_bundle' => $bundle_name,
':oid' => $oid,
));
}
$data = $result
->fetchObject();
// Return an empty array if no data was found.
if (!$data) {
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 and fieldmap.
*
* @param $sfid
* A saleforce id
* @param $name
* The name of the fieldmap for which data is being requested.
* @return
* The associated unique ID used to identify the object in Drupal or FALSE.
*/
function salesforce_api_get_id_with_sfid($sfid, $name = NULL) {
if (isset($name)) {
$result = db_query("SELECT oid FROM {salesforce_object_map} WHERE sfid = :sfid AND name = :name", array(
':sfid' => $sfid,
':name' => $name,
));
}
else {
$result = db_query("SELECT oid FROM {salesforce_object_map} WHERE sfid = :sfid", array(
':sfid' => $sfid,
));
}
return $result
->fetchField();
}
/**
* Saves the Salesforce ID and fieldmap index of a Drupal object.
* Also stores the timestamp of creation for the object mapping, and when the
* object was last exported to Salesforce or imported to Drupal.
*
* @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 $entity_name
* The type of Drupal entity being saved.
* @param string $bundle_name
* The Drupal bundle type being saved.
* @param string $op_type
* The type of operation being performed. Possible values are 'import', 'export', and 'link'.
* @return
* TRUE if was successful in saving the link, FALSE otherwise.
* @todo salesforce_api_id_save_multi()
*/
function salesforce_api_id_save($oid = NULL, $sfid = NULL, $name = NULL, $entity_name = NULL, $bundle_name = NULL, $op_type = NULL) {
// Allows 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($oid, $sfid, $name, $entity_name, $bundle_name, $op_type);
if ($continue === FALSE) {
return FALSE;
}
}
if ($oid) {
$oid = (int) $oid;
}
if ($oid && $sfid && $name && $entity_name && $bundle_name && $op_type) {
$op_type == 'export' ? $fieldname = 'last_export' : ($fieldname = 'last_import');
$result = db_merge('salesforce_object_map')
->key(array(
'oid' => $oid,
'name' => $name,
))
->insertFields(array(
'oid' => $oid,
'sfid' => $sfid,
'name' => $name,
'drupal_entity' => $entity_name,
'drupal_bundle' => $bundle_name,
'created' => REQUEST_TIME,
$fieldname => REQUEST_TIME,
))
->updateFields(array(
$fieldname => REQUEST_TIME,
))
->execute();
// Log if an insert has been performed.
if ($result == MergeQuery::STATUS_INSERT) {
salesforce_api_log(SALESFORCE_LOG_ALL, 'On !op, successfully linked Drupal !entity : !bundle !oid to Salesforce ID !sfid with fieldmap !name', array(
'!op' => $op_type,
'!entity' => $entity_name,
'!bundle' => $bundle_name,
'!oid' => $oid,
'!sfid' => $sfid,
'!name' => $name,
));
return TRUE;
}
elseif ($result == MergeQuery::STATUS_UPDATE) {
salesforce_api_log(SALESFORCE_LOG_ALL, 'On !op, successfully re-saved linkage between Drupal !entity : !bundle !oid to Salesforce ID !sfid with fieldmap !name', array(
'!op' => $op_type,
'!entity' => $entity_name,
'!bundle' => $bundle_name,
'!oid' => $oid,
'!sfid' => $sfid,
'!name' => $name,
));
return TRUE;
}
else {
salesforce_api_log(SALESFORCE_LOG_ALL, 'On !op, failed to link Drupal !entity : !bundle !oid to Salesforce ID !sfid with fieldmap !name', array(
'!op' => $op_type,
'!entity' => $entity_name,
'!bundle' => $bundle_name,
'!oid' => $oid,
'!sfid' => $sfid,
'!name' => $name,
), WATCHDOG_ERROR);
}
}
else {
salesforce_api_log(SALESFORCE_LOG_ALL, 'Attempted to save Drupal->Salesforce linkage with insufficient information. Drupal entity: !entity, Drupal bundle: !bundle, Drupal entity id: !oid, Salesforce ID: !sfid, Fieldmap: !name, Operation: !op', array(
'!entity' => $entity_name,
'!bundle' => $bundle_name,
'!oid' => $oid,
'!sfid' => $sfid,
'!name' => $name,
'!op' => $op_type,
), WATCHDOG_ERROR);
return FALSE;
}
}
/**
* 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.
*
* - 'drupal_entity'
* The type of Drupal entity being exported.
*
* - '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 "name" or "drupal_type"
* OR
* "sfid" must be set
*
* In other words, minimal valid key combinations are:
* - 'sfid'
* - 'name', 'oid'
* - 'drupal_entity', 'oid'
*/
// @todo: Need to add drupal_bundle in here as an option?
// Would need to be combined with something, probably, in order to make it meaningful.
// Also consider adding date-based unlinking (i.e., created, last_import, last_export.
function salesforce_api_id_unlink($args) {
$valid_args = !empty($args['sfid']) || !empty($args['oid']) && (!empty($args['drupal_entity']) || !empty($args['name']));
if (!$valid_args) {
return FALSE;
}
$num_deleted = db_delete('salesforce_object_map');
if (!empty($args['oid'])) {
$num_deleted
->condition('oid', $args['oid']);
}
if (!empty($args['sfid'])) {
$num_deleted
->condition('sfid', $args['sfid']);
}
if (!empty($args['drupal_entity'])) {
$num_deleted
->condition('drupal_entity', $args['drupal_entity']);
}
if (!empty($args['name'])) {
$num_deleted
->condition('name', $args['name']);
}
$num_deleted
->execute();
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();
if (!$sf) {
throw new Exception('Unable to connect to Salesforce');
}
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;
}
}
/**
* Wrapper function for the sf_find_match hook, implemented by sf_match in the core Salesforce Suite.
*/
function salesforce_api_search_for_duplicates($direction, $entity_name, $bundle_name, $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, $entity_name, $bundle_name, $object, $fieldmap_name);
}
/**
* 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(
'file' => 'salesforce_api.admin.inc',
'render element' => 'form',
),
'salesforce_api_object_options' => array(
'render element' => 'element',
),
'salesforce_api_drupal_sfapi_automatic' => array(
'render element' => 'element',
),
);
}
/**
* 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::upsert. Upserts a record in Salesforce.
* If there is an existing record in Salesforce with the same ID, that record is
* updated. Otherwise, a new record is created.
* @param array $records
* Either an array of arrays of Salesforce fields, to be converted to sObjects, of the specified
* type, or else an array of sObjects.
* @param string $type
* The type of sObject to update. Must either be a core sObject, or a custom type defined in your
* WSDL. Custom types all end in "__c". Default type is "Contact".
* @param string $key
* The Salesforce field, or external ID, on which to upsert. Default is "Id".
* @param object $sf
* A currently-active Salesforce connection. If none is passed in, one will be created.
* @return array
* An array containing an array of Salesforce IDs successfully upserted, and the number of failures,
* or FALSE if no Salesforce connection could be made or an exception was thrown.
*/
function salesforce_api_upsert(array $records, $type = 'Contact', $key = 'Id', $sf = NULL) {
// Connects to Salesforce if no existing connection supplied.
if (!is_object($sf)) {
$sf = salesforce_api_connect();
// Return FALSE if could not connect to Salesforce.
if ($sf == FALSE) {
return FALSE;
}
}
// Iterates through the records passed in and convert them to objects if necessary.
$i = 0;
foreach ($records as $record) {
if (!is_object($record)) {
$records[$i] = (object) $record;
}
$i++;
}
try {
$results = $sf->client
->upsert($key, $records, $type);
} catch (Exception $e) {
salesforce_api_log(SALESFORCE_LOG_SOME, 'The following exception occurred while attempting to upsert records: <pre>%e</pre>', array(
'%msg' => $e
->getMessage(),
'%e' => print_r($e, TRUE),
), WATCHDOG_ERROR);
return FALSE;
}
// Sets up the variables for the array of information about results of upsert operation.
$success_ids = array();
$failures = 0;
$created_ids = array();
$updated_ids = array();
// $is_deleted_ids = array();
// Iterate over the resultset.
foreach ($results as $result) {
// Handle any errors.
// @todo: Log is_deleted errors separately, so they can be handled by unlink & upsert on
// a successive call to this function.
if (isset($result->errors) && is_array($result->errors)) {
$err_msgs = array();
$status_codes = array();
// Log all errors to watchdog.
// @todo: Present them more nicely than an array with print_r().
foreach ($result->errors as $error) {
$err_msgs[] = $error->message;
$status_codes[] = $error->statusCode;
if ($error->statusCode == 'ENTITY_IS_DELETED') {
// @todo: Figure out a way to determine which one was deleted.
}
}
salesforce_api_log(SALESFORCE_LOG_SOME, 'Errors occurred while attempting to upsert record: <pre>%msgs</pre>', array(
'%msgs' => print_r($err_msgs, TRUE),
'%codes' => print_r($status_codes, TRUE),
), WATCHDOG_ERROR);
// Increment the number of failures.
$failures++;
}
elseif (isset($result->success) && $result->success == TRUE) {
$success_ids[] = $result->id;
// Separates successes into creates and updates.
if (isset($result->created) && $result->created == TRUE) {
$created_ids[] = $result->id;
}
else {
$updated_ids[] = $result->id;
}
}
}
// Return the ids of results, grouped appropriately.
$result_info = array(
'successes' => $success_ids,
'failures' => $failures,
'created' => $created_ids,
'updated' => $updated_ids,
);
return $result_info;
}
/**
* 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_salesforce_fieldmap_load($name);
$object = salesforce_api_fieldmap_objects_load('salesforce', '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 string $type
* The name of the Salesforce object for which to retrieve data, 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 || !$start || !$end) {
return FALSE;
}
// If $type is an object, we only need the Salesforce type.
if (is_object($type) && $type->salesforce) {
$type = $type->salesforce;
}
$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 (isset($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) {
@($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');
// 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 || 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 on 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;
}
/**
* Wrapper for Devel's dpm() function. Called in other Salesforce API modules.
*/
function sf_dpm($var, $show_msg = TRUE, $msg_level = 'error') {
if (!variable_get('salesforce_api_debug', TRUE)) {
return;
}
if (function_exists('dpm')) {
dpm($var);
}
elseif ($show_msg) {
drupal_set_message(check_plain(print_r($var, TRUE)), $msg_level);
}
}
/**
* Implements hook_views_api().
*/
function salesforce_api_views_api() {
return array(
'api' => 2,
);
}
/**
* @addtogroup exportables
* @{
*/
/**
* Loads all fieldmaps currently defined in the database or in code.
* @return array
* An array of all defined fieldmaps.
*/
function salesforce_api_salesforce_fieldmap_load_all() {
ctools_include('export');
return ctools_export_load_object('salesforce_fieldmap');
}
/**
* Loads a specific fieldmap by name.
* @param string name
* The machine name of the fieldmap, or the numeric ID (in legacy code only).
* @return object
* The specified fieldmap.
*/
function salesforce_api_salesforce_fieldmap_load($name) {
ctools_include('export');
$result = ctools_export_load_object('salesforce_fieldmap', 'names', array(
$name,
));
if (isset($result[$name])) {
return $result[$name];
}
// For backwards compatibility, search on fieldmap column (numeric id).
$result = ctools_export_load_object('salesforce_fieldmap', 'conditions', array(
'fieldmap' => $name,
));
if (!empty($result)) {
// "fieldmap" column is serial, and thus is always unique - if not empty, this will always
// contain a direct hit.
return current($result);
}
}
/**
* Loads fieldmaps that match a particular set of conditions.
* @param array $conditions
* An array of conditions on which to match fieldmaps, keyed by the fields of the
* {salesforce_fieldmap} table.
* @return array
* An array of fieldmaps matching the provided conditions.
*/
function salesforce_api_salesforce_fieldmap_load_by($conditions) {
ctools_include('export');
$result = ctools_export_load_object('salesforce_fieldmap', 'conditions', $conditions);
return $result;
}
/**
* Generates a form with the export code for a given fieldmap.
* @param string $fieldmap
* The name of the fieldmap to export.
* @return array
* A CTools exportable form.
*/
function salesforce_api_export_salesforce_fieldmap($map) {
drupal_set_title(t('Export Fieldmap'));
$code = salesforce_api_salesforce_fieldmap_export($map);
return drupal_get_form('ctools_export_form', $code, 'Export Fieldmap');
}
/**
* Generates the export code for a given fieldmap.
* @param object $map
* The fieldmap object to export.
* @return string
* The code for the export.
*/
function salesforce_api_salesforce_fieldmap_export($map, $indent = '') {
ctools_include('export');
$output = ctools_export_object('salesforce_fieldmap', $map, $indent);
return $output;
}
/**
* Form builder function for a fieldmap import.
* Makes it possible to save a fieldmap from generated code.
*/
function salesforce_api_import_salesforce_fieldmap($form, &$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;
}
/**
* Validation function for salesforce_api_import_fieldmap().
* Makes sure that an import actually provides a handler.
*/
function salesforce_api_import_salesforce_fieldmap_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 {salesforce_fieldmap} schema.
if (empty($salesforce_fieldmap)) {
$errors = ob_get_contents();
if (empty($errors)) {
$errors = t('Could not load a fieldmap from this input. Check your code for errors.');
}
form_error($form['object'], t('Unable to get a fieldmap from the import. Errors reported: @errors', array(
'@errors' => $errors,
)));
}
if (empty($salesforce_fieldmap->drupal_entity) || empty($salesforce_fieldmap->drupal_bundle) || empty($salesforce_fieldmap->salesforce)) {
form_error($form['object'], t('This fieldmap cannot be imported; the object definition is invalid.'));
return;
}
if (!salesforce_api_fieldmap_source_entity_enabled($salesforce_fieldmap)) {
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_fieldmap->drupal_entity,
)));
}
if (!salesforce_api_fieldmap_source_bundle_enabled($salesforce_fieldmap)) {
form_error($form['object'], t('This fieldmap cannot be imported, because the module which supports the Drupal entity type "%bundle" cannot be found. Please make sure you have required any modules with which this fieldmap was built.', array(
'%entity' => $salesforce_fieldmap->drupal_bundle,
)));
}
// Try to enable the Salesforce object if it was not found.
if (!salesforce_api_fieldmap_target_enabled($salesforce_fieldmap)) {
form_error($form['object'], t('This fieldmap cannot be imported because the Salesforce API module cannot find a definition for the Salesforce object "%sfobj". Please verify your Salesforce connection and settings.', array(
'%sfobj' => $salesforce_fieldmap->salesforce,
)));
}
$form_state['obj'] = $salesforce_fieldmap;
}
/**
* Helper function to determine whether the Drupal entity for a given
* fieldmap is available.
*
* @param object $map
* @return boolean
*/
function salesforce_api_fieldmap_source_entity_enabled($map) {
$source = salesforce_api_fieldmap_objects_load('drupal', $map->drupal_entity);
return !empty($source);
}
/**
* Helper function to determine whether the bundle for the Drupal entity for a given
* fieldmap is available.
*
* @param object $map
* @return boolean
*/
function salesforce_api_fieldmap_source_bundle_enabled($map) {
$source = salesforce_api_fieldmap_objects_load('drupal', $map->drupal_entity, $map->drupal_bundle);
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();
}
// Load the Salesforce fieldmap objects fresh (not from cache), to see if the WSDL defines the object.
$target = salesforce_api_fieldmap_objects_load('salesforce', 'salesforce', $map->salesforce, $reset = TRUE);
return !empty($target);
}
/**
* Submit handler for salesforce_api_import_salesforce_fieldmap().
* Saves the imported object.
*/
function salesforce_api_import_salesforce_fieldmap_submit($form, &$form_state) {
$salesforce_fieldmap = $form_state['obj'];
// Allow a name to be specified for the fieldmap.
if (!empty($form_state['values']['name'])) {
$salesforce_fieldmap->name = $form_state['values']['name'];
}
salesforce_api_fieldmap_save($salesforce_fieldmap);
$form_state['redirect'] = SALESFORCE_PATH_FIELDMAPS . '/' . $salesforce_fieldmap->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.
*/
/**
* Decrypt a specified value.
*/
function salesforce_api_decrypt($value) {
return function_exists('aes_decrypt') ? aes_decrypt($value) : $value;
}
/**
* Encrypt a specified value.
*/
function salesforce_api_encrypt($value) {
return function_exists('aes_encrypt') ? aes_encrypt($value) : $value;
}
/**
* Check whether encryption is available.
*/
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')) {
$warnings[] = 'AES Encryption module is not installed.';
}
elseif ($check_config) {
if (!variable_get('aes_key_path', FALSE) || variable_get('aes_key_storage_method', FALSE) != 'File') {
$errors[] = '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');
}
}
switch ($fail_threshold) {
case 'errors':
if (empty($errors)) {
return TRUE;
}
case 'warnings':
if (empty($errors) && empty($warnings)) {
return TRUE;
}
}
}
Functions
Name | 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_decrypt | Decrypt a specified value. |
salesforce_api_delete_object_map | Given a Drupal entity type and Drupal object id, 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 | Encrypt a specified value. |
salesforce_api_encryption_available | Check whether encryption is available. |
salesforce_api_export_salesforce_fieldmap | Generates a form with the export code for a given fieldmap. |
salesforce_api_features_api | Implements hook_features_api(). |
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_field_delete_instance. May be use to delete an occurrence in a single fieldmap (by supplying drupal_entity, drupal_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 | %wildcard_load implementation for %salesforce_api_fieldmap menu wildcard. |
salesforce_api_fieldmap_objects | Implements hook_fieldmap_objects(). |
salesforce_api_fieldmap_objects_load | Returns all or a subset of the objects defined via hook_fieldmap_objects and hook_fieldmap_objects_alter(). |
salesforce_api_fieldmap_object_label | Returns the label for the object of the specified type and name. Note that both the $type and $entity parameters will be 'salesforce' in the case of Salesforce objects. |
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_bundle_enabled | Helper function to determine whether the bundle for the Drupal entity for a given fieldmap is available. |
salesforce_api_fieldmap_source_entity_enabled | Helper function to determine whether the Drupal entity 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 and fieldmap. |
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 on Christian G. Warden's code at… |
salesforce_api_id_convert | Converts a 15-character Salesforce ID to 18-character ID. |
salesforce_api_id_load | |
salesforce_api_id_save | Saves the Salesforce ID and fieldmap index of a Drupal object. Also stores the timestamp of creation for the object mapping, and when the object was last exported to Salesforce or imported to Drupal. |
salesforce_api_id_unlink | |
salesforce_api_import_salesforce_fieldmap | Form builder function for a fieldmap import. Makes it possible to save a fieldmap from generated code. |
salesforce_api_import_salesforce_fieldmap_submit | Submit handler for salesforce_api_import_salesforce_fieldmap(). Saves the imported object. |
salesforce_api_import_salesforce_fieldmap_validate | Validation function for salesforce_api_import_fieldmap(). Makes sure that an import actually provides a handler. |
salesforce_api_init | Implements hook_init(). Checks to see if the Salesforce PHP Toolkit is installed, and warns if it is not. |
salesforce_api_locate_toolkit | Locates the Salesforce PHP Toolkit, if installed. |
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 | 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_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_fieldmap_export | Generates the export code for a given fieldmap. |
salesforce_api_salesforce_fieldmap_load | Loads a specific fieldmap by name. |
salesforce_api_salesforce_fieldmap_load_all | Loads all fieldmaps currently defined in the database or in code. |
salesforce_api_salesforce_fieldmap_load_by | Loads fieldmaps that match a particular set of conditions. |
salesforce_api_search_for_duplicates | Wrapper function for the sf_find_match hook, implemented by sf_match in the core Salesforce Suite. |
salesforce_api_theme | Implements hook_theme(). |
salesforce_api_toolkit_installed | Simple check for Salesforce Toolkit. |
salesforce_api_upsert | Wraps SforceBaseClient::upsert. Upserts a record in Salesforce. If there is an existing record in Salesforce with the same ID, that record is updated. Otherwise, a new record is created. |
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(). |
sf_dpm | Wrapper for Devel's dpm() function. Called in other Salesforce API modules. |
_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 for fieldmap editing screens. |
_salesforce_fieldmap_delete_revert_access | Access callback for delete / revert operations. Only code-based, overridden maps can be reverted and only database-only maps can be deleted. |