diff -urpN salesforce/salesforce_api/salesforce_api.module salesforce/salesforce_api/salesforce_api.module --- salesforce/salesforce_api/salesforce_api.module 2010-03-01 09:31:08.000000000 -0500 +++ salesforce/salesforce_api/salesforce_api.module 2010-03-17 14:24:31.000000000 -0400 @@ -45,6 +45,17 @@ define('SALESFORCE_LOG_NONE', 0); define('SALESFORCE_LOG_SOME', 5); define('SALESFORCE_LOG_ALL', 10); +if (!function_exists('is_sfid')) { + // Without a roundtrip to salesforce.com, checking the string length is the + // best we can do to verify a SalesForce ID. + function is_sfid($sfid) { + if (strlen($sfid) == 15 || strlen($sfid) == 18) { + return TRUE; + } + return FALSE; + } +} + /** * Implementation of hook_menu(). diff -urpN salesforce/sf_node/sf_node.module salesforce/sf_node/sf_node.module --- salesforce/sf_node/sf_node.module 2010-03-01 09:38:28.000000000 -0500 +++ salesforce/sf_node/sf_node.module 2010-03-17 14:24:31.000000000 -0400 @@ -597,8 +597,8 @@ function sf_node_export($node, $fieldmap /** * Imports data from Salesforce into a node. * - * @param $sfid - * The Salesforce ID of the object from which you want to import. + * @param $sf_data + * The Salesforce Object OR The Salesforce ID of the object to be imported. * @param $fieldmap * The index of the fieldmap to use to create the export object. * @param $nid @@ -606,7 +606,7 @@ function sf_node_export($node, $fieldmap * @return * The nid of the imported node or FALSE on failure. */ -function sf_node_import($sfid, $fieldmap, $nid = NULL) { +function sf_node_import($sf_data, $fieldmap, $nid = NULL) { // Retrieve the object from Salesforce. $sf = salesforce_api_connect(); if (!$sf) { @@ -614,8 +614,17 @@ function sf_node_import($sfid, $fieldmap return FALSE; } - $sf_data = $sf->retrieve(array($sfid), $fieldmap); + if (is_sfid($sf_data)) { + $sf_data = $sf->retrieve(array($sf_data), $fieldmap); + } + elseif (is_array($sf_data)) { + $sf_data = (object) $sf_data; + } + if (empty($sf_data)) { + return FALSE; + } + $sfid = $sf_data->Id; // Load the fieldmap data. $map = salesforce_api_fieldmap_load($fieldmap); @@ -637,6 +646,7 @@ function sf_node_import($sfid, $fieldmap 'uid' => 1, ); } + // Loop through the fields on the fieldmap. foreach ($map['fields'] as $sf_fieldname => $drupal_fieldname) { // If a handler is specified for importing a value from Salesforce.... @@ -655,6 +665,10 @@ function sf_node_import($sfid, $fieldmap } $node->sf_node_skip_export = TRUE; node_save($node); + if ($map['automatic'] && !empty($node->nid)) { + // Store the Salesforce ID for the node and return TRUE. + salesforce_api_id_save('node', $node->nid, $sfid, $fieldmap); + } unset($node->sf_node_skip_export); return $node->nid; diff -urpN salesforce/sf_notifications/sf_notifications.admin.inc salesforce/sf_notifications/sf_notifications.admin.inc --- salesforce/sf_notifications/sf_notifications.admin.inc 1969-12-31 19:00:00.000000000 -0500 +++ salesforce/sf_notifications/sf_notifications.admin.inc 2010-03-17 14:24:31.000000000 -0400 @@ -0,0 +1,17 @@ + array( + '#type' => 'markup', '#value' => 'Placeholder for more SalesForce Notifications settings' + )); +} + diff -urpN salesforce/sf_notifications/sf_notifications.info salesforce/sf_notifications/sf_notifications.info --- salesforce/sf_notifications/sf_notifications.info 1969-12-31 19:00:00.000000000 -0500 +++ salesforce/sf_notifications/sf_notifications.info 2010-03-17 14:24:31.000000000 -0400 @@ -0,0 +1,6 @@ +; $Id$ +name = Salesforce Notifications +description = Responds to SOAP Outbound Messages from SalesForce. +dependencies[] = salesforce_api +package = Salesforce +core = 6.x diff -urpN salesforce/sf_notifications/sf_notifications.install salesforce/sf_notifications/sf_notifications.install --- salesforce/sf_notifications/sf_notifications.install 1969-12-31 19:00:00.000000000 -0500 +++ salesforce/sf_notifications/sf_notifications.install 2010-03-17 14:24:31.000000000 -0400 @@ -0,0 +1,24 @@ +NOT($User.Username = \'' . $sfuser . '\')'; + $args = array( + '!messages' => 'https://na1.salesforce.com/04k', + '!workflow' => 'https://na1.salesforce.com/01Q', + '!base_url' => $base_url, + '!formula' => $formula); + drupal_set_message(t('You have successfully enabled SalesForce Notifications. To make use + of this module, you will probably want to head over to salesforce.com and + set up some outbound messages and + associate them with workflow rules. Point the outbound message(s) to + !base_url and set up the workflow rules to fire when the user is NOT your + SFDC API user. Use the following formula if you are unsure: !formula.', $args)); +} + +function sf_notifications_disable() { + drupal_set_message(t('You have successfully disabled SalesForce Notifications. You may + want to deactivate any workflow rules associated + with this site.', array('!workflow' => 'https://na1.salesforce.com/01Q'))); +} \ No newline at end of file diff -urpN salesforce/sf_notifications/sf_notifications.module salesforce/sf_notifications/sf_notifications.module --- salesforce/sf_notifications/sf_notifications.module 1969-12-31 19:00:00.000000000 -0500 +++ salesforce/sf_notifications/sf_notifications.module 2010-03-17 15:04:27.000000000 -0400 @@ -0,0 +1,262 @@ + array( + 'title' => 'Notifications', + 'description' => 'Placeholder for more SalesForce Notifications settings', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('sf_notifications_settings_form'), + 'access arguments' => array('administer salesforce'), + 'type' => MENU_LOCAL_TASK, + 'file' => 'sf_notifications.admin.inc', + ), + SALESFORCE_PATH_NOTIFICATIONS_ENDPOINT => array( + 'title' => FALSE, + 'page callback' => 'sf_notifications_endpoint', + 'access arguments' => array('access content'), + 'type' => MENU_CALLBACK + ), + ); +} + +/** + * Menu callback for SalesForce notifications endpoint + * @todo Add authentication. see "Downloading the Salesforce.com Client Certificate" at + * http://www.salesforce.com/us/developer/docs/ajax/Content/sforce_api_ajax_queryresultiterator.htm + */ +function sf_notifications_endpoint() { + // SF Toolkit only gets included on salesforce_api_connect, which we've no reason to call. + require_once(drupal_get_path('module', 'salesforce_api') .'/salesforce.class.inc'); + + // Needed for the reference to SObject in parse_message, otherwise it just seems to die + // when it tries to call new SObject() + require_once(drupal_get_path('module', 'salesforce_api') .'/toolkit/soapclient/SforcePartnerClient.php'); + + $content = file_get_contents('php://input'); + if (empty($content)) { + DrupalSalesforce::watchdog(SALESFORCE_LOG_SOME, 'SalesForce Notifications: Empty request.'); + exit; + } + $dom = new DOMDocument(); + $dom->loadXML($content); + if (empty($dom) || !$dom->hasChildNodes()) { + DrupalSalesforce::watchdog(SALESFORCE_LOG_NONE, + 'SalesForce Notifications: Failed to parse into DOM Document. +
' . print_r($content) . '
'); + _sf_notifications_soap_respond('false'); + exit; + } + $resultArray = _sf_notifications_parse_message($dom); + $ret = _sf_notifications_handle_message($resultArray); + + // Sends SOAP response to SFDC + if ($ret) { + _sf_notifications_soap_respond('true'); + } + else { + _sf_notifications_soap_respond('false'); + } + exit; +} + +/** + * Loop through an array of SObjects from SalesForce and save them according to + * any existing sf fieldmaps and data. + * + * @param array $objects + * A numerically indexed array of SObjects (as returned by _sf_notifications_parse_message) + * @return (boolean) FALSE if there were errors. TRUE otherwise. + */ +function _sf_notifications_handle_message($objects) { + $success = TRUE; + // Assume all the records are new, and delete them while we loop otherwise. + $new_records = $objects['salesforce']; + foreach ($objects['drupal'] as $object_record) { + $sfid = $object_record['sfid']; + $obj = $objects['salesforce'][$sfid]; + $fieldmap = salesforce_api_fieldmap_load($object_record['fieldmap']); + unset($new_records[$sfid]); + // Booleans from SalesForce are strings "true" and "false". + if ($obj->fields->IsDeleted == 'true') { + // Try to delete the local record. Since the record is no more, in this + // case we're agnostic to the drupal_type ("node" or "user"). + if ($object_record['drupal_type'] == 'user') { + user_delete(array(), $object_record['oid']); + DrupalSalesforce::watchdog(SALESFORCE_LOG_ALL, + 'SalesForce Notificaitions deleted user ' + . $object_record['oid'] . ' sfid ' . $sfid); + } + elseif ($object_record['drupal_type'] == 'node') { + node_delete($object_record['oid']); + DrupalSalesforce::watchdog(SALESFORCE_LOG_ALL, + 'SalesForce Notificaitions deleted node ' + . $object_record['oid'] . ' sfid ' . $sfid); + } + elseif (function_exists($object_record['drupal_type'] . '_delete')) { + $function = $object_record['drupal_type'] . '_delete'; + $function($object_record['oid']); + DrupalSalesforce::watchdog(SALESFORCE_LOG_ALL, + 'SalesForce Notificaitions deleted ' + . $object_record['drupal_type'] . ' ' . $object_record['oid'] + . ' sfid ' . $sfid); + } + else { + DrupalSalesforce::watchdog(SALESFORCE_LOG_SOME, + ' SalesForce Notifications: Could not find delete handler for deleted + SalesForce record
' . $obj . '
'); + $success = FALSE; + } + continue; + } + + $function = 'sf_' . $object_record['drupal_type'] . '_import'; + if (function_exists($function)) { + $drupal_id = $function($obj->fields, $object_record['fieldmap'], $object_record['oid']); + if ($drupal_id) { + DrupalSalesforce::watchdog(SALESFORCE_LOG_ALL, + 'SalesForce Notificaitions updated ' + . $object_record['drupal_type'] . ' ' . $drupal_id); + } + else { + DrupalSalesforce::watchdog(SALESFORCE_LOG_NONE, + 'SalesForce Notificaitions failed to update ' + . $object_record['drupal_type'] . ' from record. +
' . print_r($obj, 1) . '
'); + $success = FALSE; + } + } + else { + DrupalSalesforce::watchdog(SALESFORCE_LOG_SOME, + 'SalesForce Notifications: Import handler ' . $function . ' undefined. + Drupal ' . $fieldmap['drupal'] . ' with id ' + . $object_record['oid'] . ' was not updated.'); + $success = FALSE; + } + } + + foreach ($new_records as $sfid => $obj) { + $result = db_query('SELECT fieldmap FROM {salesforce_field_map} WHERE salesforce = "%s"', $obj->type); + // If there are multiple fieldmaps for a single SalesForce type, there is + // no way to know which one to use for an import. Execute import for all. + // @see todos + $fieldmap_id = db_result($result); + if (!$fieldmap_id) { + DrupalSalesforce::watchdog(SALESFORCE_LOG_SOME, + 'SalesForce Notifications: No fieldmap found. +
' . print_r($obj, 1) . '
'); + $success = FALSE; + continue; + } + do { + $fieldmap = salesforce_api_fieldmap_load($fieldmap_id); + $fieldmap_type = $fieldmap['drupal']; + if (strpos($fieldmap['drupal'], 'node_') === 0) { + $fieldmap_type = 'node'; + } + $function = 'sf_' . $fieldmap_type . '_import'; + if (function_exists($function)) { + $drupal_id = $function($obj->fields, $fieldmap_id, NULL); + if ($drupal_id) { + DrupalSalesforce::watchdog(SALESFORCE_LOG_ALL, + 'SalesForce Notificaitions created ' + . $fieldmap['drupal'] . ' ' . $drupal_id); + } + else { + DrupalSalesforce::watchdog(SALESFORCE_LOG_NONE, + 'SalesForce Notificaitions failed to create ' + . $fieldmap['drupal'] . ' from SF record. +
' . print_r($obj, 1) . '
'); + $success = FALSE; + } + } + else { + DrupalSalesforce::watchdog(SALESFORCE_LOG_SOME, + 'SalesForce Notifications: No import handler defined for ' + . $fieldmap['drupal'] . '. No record created. +
' . print_r($obj, 1) . '
'); + $success = FALSE; + } + } while ($fieldmap_id = db_result($result)); + } + + return $success; +} + + +/** + * Parse SOAP message into its component args. + * + * @param (object) $domDoc + * A DOMDocument representation of the outbound SOAP message from SalesForce. + * @return (array) $result + * An array with two sub-arrays, keyed as: + * 'drupal': + * A sequential array containing relevant salesforce_ids records. + * We don't index on drupal_id because there could be overlap. + * 'salesforce': + * An indexed array mapping sfids to SObject records from SalesForce. + */ +function _sf_notifications_parse_message($domDoc) { + $result = array('salesforce' => array(), 'drupal' => array()); + $sfids = array(); + // Create sObject array and fill fields provided in notification + $objects = $domDoc->getElementsByTagName('sObject'); + foreach ($objects as $sObjectNode) { + $sObjType = $sObjectNode->getAttribute('xsi:type'); + if (substr_count($sObjType,'sf:')) { + $sObjType = substr($sObjType,3); + } + $obj = new SObject(); + $obj->type = $sObjType; + $elements = $sObjectNode->getElementsByTagNameNS('urn:sobject.enterprise.soap.sforce.com','*'); + $obj->fieldnames = array(); + foreach ($elements as $node) { + if ($node->localName == 'Id') { + // "Id" is a property of the SObject as well as SObject->fields + $sfids[] = $obj->Id = $node->textContent; + } + $fieldname = $node->localName; + $obj->fields->$fieldname = $node->nodeValue; + array_push($obj->fieldnames,$fieldname); + } + $result['salesforce'][$obj->Id] = $obj; + } + + $dbresult = db_query( + 'SELECT oid, sfid, fieldmap, drupal_type FROM salesforce_object_map + WHERE sfid IN (' . db_placeholders($sfids, 'varchar') . ')', $sfids); + while($row = db_fetch_array($dbresult)) { + $result['drupal'][] = $row; + } + + return $result; +} + +/** + * Format and send a SOAP response message. + * + * @param boolean $tf + * @return void +**/ +function _sf_notifications_soap_respond($tf = 'true') { +print ' + + + + '.$tf.' + + + +'; +} + + diff -urpN salesforce/sf_user/sf_user.module salesforce/sf_user/sf_user.module --- salesforce/sf_user/sf_user.module 2010-02-25 14:18:46.000000000 -0500 +++ salesforce/sf_user/sf_user.module 2010-03-17 14:24:31.000000000 -0400 @@ -406,8 +406,8 @@ function sf_user_export($uid, $fieldmap, /** * Imports data from Salesforce into a user. * - * @param $sfid - * The Salesforce ID of the object from which you want to import. + * @param $sf_data + * The Salesforce Object OR The Salesforce ID of the object to be imported. * @param $fieldmap * The index of the fieldmap to use to create the export object. * @param $uid @@ -415,7 +415,7 @@ function sf_user_export($uid, $fieldmap, * @return * The uid of the imported user or FALSE on failure. */ -function sf_user_import($sfid, $fieldmap, $uid = NULL) { +function sf_user_import($sf_data, $fieldmap, $uid = NULL) { // Retrieve the object from Salesforce. $sf = salesforce_api_connect(); if (!$sf) { @@ -423,13 +423,17 @@ function sf_user_import($sfid, $fieldmap return FALSE; } - $sf_data = $sf->retrieve(array($sfid), $fieldmap); + if (is_sfid($sf_data)) { + $sf_data = $sf->retrieve(array($sf_data), $fieldmap); + } + elseif (is_array($sf_data)) { + $sf_data = (object) $sf_data; + } - // Return FALSE if the object data was not found at Salesforce. if (empty($sf_data)) { return FALSE; } - + $sfid = $sf_data->Id; // Load the fieldmap data. $map = salesforce_api_fieldmap_load($fieldmap); @@ -476,6 +480,10 @@ function sf_user_import($sfid, $fieldmap $account->sf_user_skip_export = TRUE; $changes['sf_user_skip_export'] = TRUE; $account = user_save($account, $changes); + if ($map['automatic'] && !empty($account->uid)) { + // Store the Salesforce ID for the node and return TRUE. + salesforce_api_id_save('user', $account->uid, $sfid, $fieldmap); + } unset($account->sf_user_skip_export); }