Index: sf_notifications/sf_notifications.admin.inc =================================================================== RCS file: sf_notifications/sf_notifications.admin.inc diff -N sf_notifications/sf_notifications.admin.inc --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ sf_notifications/sf_notifications.admin.inc 29 Dec 2010 14:56:47 -0000 @@ -0,0 +1,17 @@ + array( + '#type' => 'markup', '#value' => 'Placeholder for more SalesForce Notifications settings' + )); +} + Index: sf_notifications/sf_notifications.info =================================================================== RCS file: sf_notifications/sf_notifications.info diff -N sf_notifications/sf_notifications.info --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ sf_notifications/sf_notifications.info 29 Dec 2010 14:56:47 -0000 @@ -0,0 +1,6 @@ +; $Id$ +name = Salesforce Notifications +description = Responds to SOAP Outbound Messages from SalesForce. +dependencies[] = salesforce_api +package = Salesforce +core = 6.x Index: sf_notifications/sf_notifications.install =================================================================== RCS file: sf_notifications/sf_notifications.install diff -N sf_notifications/sf_notifications.install --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ sf_notifications/sf_notifications.install 29 Dec 2010 14:56:48 -0000 @@ -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 Index: sf_notifications/sf_notifications.module =================================================================== RCS file: sf_notifications/sf_notifications.module diff -N sf_notifications/sf_notifications.module --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ sf_notifications/sf_notifications.module 29 Dec 2010 14:56:48 -0000 @@ -0,0 +1,283 @@ + 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]; + // We don't have fieldmaps anymore! So we added name + //$fieldmap = salesforce_api_fieldmap_load($object_record['fieldmap']); + $fieldmap = salesforce_api_fieldmap_load($object_record['name']); + 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'; +//log_debug('********* function ***************', $function); +//log_debug('obj rec', $object_record); +//log_debug('obj', $obj->fields); + if (function_exists($function)) { + // ain't no fieldmap anymore! import functions take name, not fieldmap id + // $drupal_id = $function($obj->fields, $object_record['fieldmap'], $object_record['oid']); + $drupal_id = $function($obj->fields, $object_record['name'], $object_record['oid']); +//log_debug('drupal_id', $drupal_id); + if ($drupal_id) { + DrupalSalesforce::watchdog(SALESFORCE_LOG_ALL, + 'SalesForce Notificaitions updated ' + . $object_record['drupal_type'] . ' ' . $drupal_id); + } + else { + //DrupalSalesforce::watchdog(SALESFORCE_LOG_NONE, + DrupalSalesforce::watchdog(SALESFORCE_LOG_ALL, + 'SalesForce Notificaitions failed to update ' + . $object_record['drupal_type'] . ' from record. +
' . print_r($obj, 1) . ''); + $success = FALSE; + } + } + else { + //DrupalSalesforce::watchdog(SALESFORCE_LOG_SOME, + DrupalSalesforce::watchdog(SALESFORCE_LOG_ALL, + '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)) { +//log_debug('function', $function); +//log_debug('fieldmap_id', $fieldmap_id); +//log_debug('fields', $fields); + $drupal_id = $function($obj->fields, $fieldmap_id, NULL); +//log_debug('result', $drupal_id); + if ($drupal_id) { + DrupalSalesforce::watchdog(SALESFORCE_LOG_ALL, + 'SalesForce Notificaitions created ' + . $fieldmap['drupal'] . ' ' . $drupal_id); + } + else { + //DrupalSalesforce::watchdog(SALESFORCE_LOG_NONE, + DrupalSalesforce::watchdog(SALESFORCE_LOG_ALL, + '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) { +//$theXML = $domDoc->saveXML(); +//log_debug('domDoc', $theXML); + $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; + } + +//log_debug('sfids', $sfids); + + $dbresult = db_query( + // there is no fieldmap column any longer in the table + // so we remove it; other changes are necessary to be able to load the field map of course + //'SELECT oid, sfid, fieldmap, drupal_type FROM salesforce_object_map + 'SELECT name, oid, sfid, drupal_type FROM salesforce_object_map + WHERE sfid IN (' . db_placeholders($sfids, 'varchar') . ')', $sfids); + while($row = db_fetch_array($dbresult)) { + $result['drupal'][] = $row; + } + +//log_debug('parsed dom', $result); + return $result; +} + +/** + * Format and send a SOAP response message. + * + * @param boolean $tf + * @return void +**/ +function _sf_notifications_soap_respond($tf = 'true') { +print ' +