View source
<?php
define('SALESFORCE_PATH_NOTIFICATIONS_ADMIN', SALESFORCE_PATH_ADMIN . '/notifications');
define('SALESFORCE_PATH_NOTIFICATIONS_ENDPOINT', 'sf_notifications/endpoint');
function sf_notifications_menu() {
return array(
SALESFORCE_PATH_NOTIFICATIONS_ADMIN => array(
'title' => 'Notifications',
'description' => 'Salesforce Notifications configuration settings - allowed IPs, active fieldmaps',
'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 callback' => TRUE,
'type' => MENU_CALLBACK,
),
SALESFORCE_PATH_NOTIFICATIONS_ADMIN . '/queue' => array(
'title' => 'Manage Notifications queue',
'description' => 'Salesforce Notifications queue view and actions.',
'page callback' => 'drupal_get_form',
'page arguments' => array(
'sf_notifications_queue_form',
),
'access arguments' => array(
'administer salesforce',
),
'type' => MENU_CALLBACK,
'file' => 'sf_notifications.admin.inc',
),
SALESFORCE_PATH_NOTIFICATIONS_ADMIN . '/queue/process' => array(
'title' => 'Process Notifications queue',
'description' => 'Process Salesforce notifications queue',
'page callback' => 'drupal_get_form',
'page arguments' => array(
'sf_notifications_process_confirm_form',
),
'access arguments' => array(
'administer salesforce',
),
'type' => MENU_CALLBACK,
'file' => 'sf_notifications.admin.inc',
),
SALESFORCE_PATH_NOTIFICATIONS_ADMIN . '/queue/empty' => array(
'title' => 'Process Notifications queue',
'description' => 'Empty Salesforce notifications queue',
'page callback' => 'drupal_get_form',
'page arguments' => array(
'sf_notifications_empty_confirm_form',
),
'access arguments' => array(
'administer salesforce',
),
'type' => MENU_CALLBACK,
'file' => 'sf_notifications.admin.inc',
),
);
}
function sf_notifications_allowed_ips() {
$ip = $_SERVER['REMOTE_ADDR'];
$ips = variable_get('sf_notifications_allowed_ips', FALSE);
$allowed_ips = $ips === FALSE ? sf_notifications_default_allowed_ips() : explode("\n", $ips);
$access = FALSE;
if (in_array($ip, $allowed_ips, TRUE)) {
$access = TRUE;
}
else {
foreach ($allowed_ips as $range) {
if (_sf_notifications_cidr_match($ip, $range)) {
$access = TRUE;
}
}
}
if ($access) {
salesforce_api_log(SALESFORCE_LOG_ALL, 'Salesforce Notifications: IP address @ip accessed @endpoint successfully.', array(
'@ip' => $_SERVER['REMOTE_ADDR'],
'@endpoint' => SALESFORCE_PATH_NOTIFICATIONS_ENDPOINT,
));
}
else {
salesforce_api_log(SALESFORCE_LOG_ALL, 'Salesforce Notifications: Access denied to IP address @ip in attempt to access @endpoint.', array(
'@ip' => $_SERVER['REMOTE_ADDR'],
'@endpoint' => SALESFORCE_PATH_NOTIFICATIONS_ENDPOINT,
));
}
return $access;
}
function _sf_notifications_cidr_match($ip, $range) {
list($subnet, $bits) = explode('/', $range);
$ip = ip2long($ip);
$subnet = ip2long($subnet);
if (empty($subnet) || empty($bits) || empty($ip)) {
return FALSE;
}
$mask = -1 << 32 - $bits;
$subnet &= $mask;
return ($ip & $mask) == $subnet;
}
function sf_notifications_default_allowed_ips() {
return array(
'204.14.232.0/23',
'204.14.237.0/24',
'96.43.144.0/22',
'96.43.148.0/22',
'204.14.234.0/23',
'204.14.238.0/23',
'202.129.242.0/25',
);
}
function sf_notifications_fieldmap_settings_access($fieldmap_id, $perm) {
$active = variable_get('sf_notifications_active_maps', array());
if (!empty($active[$fieldmap_id])) {
return user_access($perm);
}
return FALSE;
}
function sf_notifications_endpoint() {
if (sf_notifications_allowed_ips() == FALSE) {
exit;
}
$content = file_get_contents('php://input');
if (empty($content)) {
salesforce_api_log(SALESFORCE_LOG_SOME, 'Salesforce Notifications: Empty request.');
drupal_exit();
}
salesforce_api_log(SALESFORCE_LOG_ALL, 'New outbound message received from Salesforce. Contents: <pre>%content</pre>', array(
'%content' => print_r($content, TRUE),
));
if (variable_get('sf_notifications_use_queue', 0)) {
$queue = DrupalQueue::get('sf_notifications_queue');
if ($queue
->createItem($content)) {
salesforce_api_log(SALESFORCE_LOG_SOME, 'Outbound message from Salesforce pushed to queue.', array());
_sf_notifications_soap_respond('true');
drupal_exit();
}
else {
salesforce_api_log(SALESFORCE_LOG_ALL, 'Unable to store outbound message from Salesforce in queue, attempting to process directly. Contents: <pre>%content</pre>', array(
'%content' => print_r($content, TRUE),
), WATCHDOG_ERROR);
}
}
$ret = _sf_notifications_parse_handle_message($content);
if ($ret) {
_sf_notifications_soap_respond('true');
}
else {
_sf_notifications_soap_respond('false');
}
drupal_exit();
}
function _sf_notifications_handle_message($objects) {
$success = TRUE;
$new_records = $objects['salesforce'];
$active = variable_get('sf_notifications_active_maps', array());
$active = array_filter($active);
foreach ($objects['drupal'] as $object_record) {
$sfid = $object_record['sfid'];
$obj = $objects['salesforce'][$sfid];
unset($new_records[$sfid]);
$map = salesforce_api_salesforce_fieldmap_load($object_record['name']);
if (empty($active[$map->name])) {
continue;
}
$operation = $obj->fields->IsDeleted == 'true' ? 'delete' : 'update';
$object_record['fields'] = $obj->fields;
$object_record['operation'] = $operation;
$results = module_invoke_all('sf_notifications_check_condition', $operation, $object_record, $map);
foreach ($results as $result) {
if (!$result) {
continue 2;
}
}
switch ($operation) {
case 'delete':
$success = $success && sf_notifications_delete_record($object_record);
break;
case 'update':
$success = $success && sf_notifications_update_record($object_record);
break;
}
}
foreach ($new_records as $sfid => $obj) {
$maps = salesforce_api_salesforce_fieldmap_load_by(array(
'salesforce' => $obj->type,
));
if (empty($maps)) {
salesforce_api_log(SALESFORCE_LOG_SOME, 'Salesforce Notifications: No fieldmap found.
<pre>' . print_r($obj, TRUE) . '</pre>');
$success = FALSE;
continue;
}
foreach ($maps as $map) {
if (empty($active[$map->name])) {
continue;
}
$object_record = array(
'oid' => NULL,
'name' => $map->name,
'drupal_entity' => $map->drupal_entity,
'drupal_bundle' => $map->drupal_bundle,
'fields' => $obj->fields,
'operation' => 'insert',
);
$results = module_invoke_all('sf_notifications_check_condition', 'insert', $object_record, $map);
foreach ($results as $result) {
if (!$result) {
continue 2;
}
}
$success = $success && sf_notifications_update_record($object_record);
}
}
return $success;
}
function sf_notifications_delete_record($object_record) {
$success = TRUE;
switch ($object_record['drupal_entity']) {
case 'user':
user_cancel(array(), $object_record['oid'], $method = 'user_cancel_block');
module_invoke_all('sf_notifications_processed', 'delete', $object_record);
salesforce_api_log(SALESFORCE_LOG_ALL, 'Salesforce Notifications deleted user ' . $object_record['oid'] . ' sfid ' . $sfid);
break;
case 'node':
$node = node_load($nid, NULL, TRUE);
db_delete('node')
->condition('nid', $node->nid)
->execute();
db_delete('node_revisions')
->condition('nid', $node->nid)
->execute();
node_invoke($node, 'delete');
module_invoke_all('node_delete', $node);
search_wipe($node->nid, 'node');
module_invoke_all('sf_notifications_processed', 'delete', $object_record);
salesforce_api_log(SALESFORCE_LOG_ALL, 'Salesforce Notifications deleted node ' . $object_record['oid'] . ' sfid ' . $sfid);
break;
default:
if (function_exists($object_record['drupal_entity'] . '_delete')) {
$function = $object_record['drupal_entity'] . '_delete';
$function($object_record['oid']);
module_invoke_all('sf_notifications_processed', 'delete', $object_record);
salesforce_api_log(SALESFORCE_LOG_ALL, 'Salesforce Notifications deleted ' . $object_record['drupal_type'] . ' ' . $object_record['oid'] . ' sfid ' . $sfid);
}
else {
salesforce_api_log(SALESFORCE_LOG_SOME, ' Salesforce Notifications: Could not find delete handler for deleted
Salesforce record <pre>' . print_r($object_record, TRUE) . '</pre>');
$success = FALSE;
}
break;
}
return $success;
}
function sf_notifications_update_record($object_record) {
$success = TRUE;
$drupal_entity = $object_record['drupal_entity'];
if (empty($drupal_entity)) {
$map = salesforce_api_salesforce_fieldmap_load($object_record['name']);
if (empty($map)) {
$success = FALSE;
}
else {
$drupal_entity = $map->drupal_entity;
}
}
$entities = field_info_bundles();
$entity_names = array_keys($entities);
if (in_array($drupal_entity, $entity_names)) {
$function = 'sf_entity_import';
}
else {
$function = 'sf_' . $drupal_entity . '_import';
}
if (function_exists($function)) {
$drupal_id = $function($object_record['fields'], $object_record['name'], $object_record['oid']);
if ($drupal_id) {
salesforce_api_log(SALESFORCE_LOG_ALL, 'Salesforce Notifications successful ' . $object_record['operation'] . ' of ' . $drupal_entity . ' ' . $drupal_id);
module_invoke_all('sf_notifications_processed', $object_record['operation'], $object_record, $drupal_id);
}
else {
salesforce_api_log(SALESFORCE_LOG_ALL, 'Salesforce Notifications failed to update ' . $object_record['drupal_entity'] . ' from record.
<pre>' . print_r($object_record, TRUE) . '</pre>');
$success = FALSE;
}
}
else {
salesforce_api_log(SALESFORCE_LOG_ALL, 'Salesforce Notifications: Import handler ' . $function . ' undefined.
Drupal ' . $object_record['drupal_entity'] . ' with id ' . $object_record['oid'] . ' was not updated.');
$success = FALSE;
}
return $success;
}
function _sf_notifications_parse_message($domDoc) {
require_once DRUPAL_ROOT . '/' . SALESFORCE_DIR_SOAPCLIENT . '/SforcePartnerClient.php';
$matches = array(
'salesforce' => array(),
'drupal' => array(),
);
$sfids = array();
$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') {
$sfids[] = $obj->Id = $node->textContent;
}
$fieldname = $node->localName;
$obj->fields->{$fieldname} = $node->nodeValue;
array_push($obj->fieldnames, $fieldname);
}
$matches['salesforce'][$obj->Id] = $obj;
}
$result = db_query('SELECT name, oid, sfid, drupal_entity, drupal_bundle FROM {salesforce_object_map} WHERE sfid IN (:sfids)', array(
':sfids' => $sfids,
));
while ($row = $result
->fetchAssoc()) {
$matches['drupal'][] = $row;
}
salesforce_api_log(SALESFORCE_LOG_ALL, 'Salesforce Notifications found the following matches for the outbound message: ' . '<pre>' . print_r($matches, TRUE) . '</pre>');
return $matches;
}
function _sf_notifications_soap_respond($tf = 'true') {
print '<?xml version = "1.0" encoding = "utf-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<soapenv:Body>
<notifications xmlns="http://soap.sforce.com/2005/09/outbound">
<Ack>' . $tf . '</Ack>
</notifications>
</soapenv:Body>
</soapenv:Envelope>
';
}
function _sf_notifications_parse_handle_message($content) {
$dom = new DOMDocument();
$dom
->loadXML($content);
if (empty($dom) || !$dom
->hasChildNodes()) {
salesforce_api_log(SALESFORCE_LOG_NONE, 'Salesforce Notifications: Failed to parse into DOM Document.
<pre>' . print_r($content) . '</pre>', array(), WATCHDOG_ERROR);
return FALSE;
}
$resultArray = _sf_notifications_parse_message($dom);
$ret = _sf_notifications_handle_message($resultArray);
return $ret;
}
function sf_notifications_cron() {
sf_notifications_release_expired_claims();
$queue = DrupalQueue::get('sf_notifications_queue');
$end = time() + 60;
while (time() < $end && ($item = $queue
->claimItem(60))) {
$ret = _sf_notifications_parse_handle_message($item->data);
if ($ret) {
salesforce_api_log(SALESFORCE_LOG_SOME, 'Queued notification processed. Contents: <pre>%content</pre>', array(
'%content' => print_r($item->data, TRUE),
));
$queue
->deleteItem($item);
}
else {
salesforce_api_log(SALESFORCE_LOG_ALL, 'Queued notification processing failed. Contents: <pre>%content</pre>', array(
'%content' => print_r($item->data, TRUE),
), WATCHDOG_ERROR);
}
}
}
function sf_notifications_release_expired_claims() {
db_update('queue')
->fields(array(
'expire' => 0,
))
->condition('expire', 0, '<>')
->condition('expire', REQUEST_TIME, '<')
->condition('name', 'sf_notifications_queue')
->execute();
}