diff -urp salesforce_orig/includes/salesforce.php salesforce/includes/salesforce.php --- salesforce_orig/includes/salesforce.php 2007-05-04 04:09:05.000000000 -0500 +++ salesforce/includes/salesforce.php 2008-01-18 16:39:09.000000000 -0600 @@ -129,7 +129,8 @@ class salesforce { * @access public */ function setURL($url){ - $this->client->forceEndpoint = str_replace("https", "http", $url); + //$this->client->forceEndpoint = str_replace("https", "http", $url); + $this->client->forceEndpoint = $url; } /** diff -urp salesforce_orig/includes/salesforce_api.inc salesforce/includes/salesforce_api.inc --- salesforce_orig/includes/salesforce_api.inc 2007-11-09 17:47:16.000000000 -0600 +++ salesforce/includes/salesforce_api.inc 2008-01-21 12:06:45.000000000 -0600 @@ -12,7 +12,11 @@ * salesforce include + connecting via SOAP */ function salesforce($login_return = false) { - static $included; + static $included, $salesforce; + + if ($salesforce && !$login_return) { + return $salesforce; + } $admin = user_access('administer salesforce'); @@ -70,15 +74,22 @@ function salesforce_contact($op, $accoun switch ($op) { case 'insert': case 'update': - $contact = new sObject('Contact', null, - array( - 'Company' => $account->company, + + $fields = array( 'FirstName' => $account->first_name, 'LastName' => $account->last_name, 'Phone' => $account->phone, 'Fax' => $account->fax, 'Email' => $account->mail, - )); + ); + + $fields = _sf_profile_fields_put($fields, $account); + + if (!variable_get('salesforce_contact_sync', 0) && !variable_get('salesforce_lead_sync', 0)) { + $fields['Company'] = $account->company; + } + + $contact = new sObject('Contact', null, $fields); // add any additional params to the contact object foreach ($params as $param => $param_value) { @@ -126,7 +137,7 @@ function salesforce_contact($op, $accoun if ($result['success'] == 'true') { if ($admin) { - drupal_set_message(t('updated contact in salesforce for the user @user', array('@user' => "$account->first_name $account->last_name"))); + drupal_set_message(t('updated contact information in salesforce for the user @user', array('@user' => "$account->first_name $account->last_name"))); return array('status' => 'updated'); } } else { @@ -147,6 +158,54 @@ function salesforce_contact($op, $accoun return array('error' => $error); } break; + + case 'insert from remote': + // New user is being created from new contact on salesforce + db_query('DELETE FROM {salesforce_users} WHERE uid=%d', $account->uid); + db_query('INSERT INTO {salesforce_users} (uid,contact_id,account_id,created,last_sync) VALUES (%d,"%s","%s", %d, %d)', $account->uid, $account->contact_id, $account->account_id, time(), time()); + break; + + case 'update from remote': + // Information was updated in salesforce, we need to mirror it locally, called by cron.php + $profile_fields = _sf_get_profile_field_info(); + + $contact = salesforce_contact_select($account, array_merge(array('FirstName','LastName','Phone','Fax','Email','LastModifiedDate'), $profile_fields)); + + // Compare the salesforce LastModifiedDate to the local last_sync date and only perform the update if the + // Salesforce date is more recent. + if (strtotime($contact['LastModifiedDate']) > gmdate('U',$account->salesforce['last_sync'])) { + if ($account->mail != $contact['Email'] ) { + user_save($account, array('mail' => $contact['Email'], 'status' => $status, 'update_from_salesforce' => TRUE)); + } + + $fields = array( + 'first_name' => $contact['FirstName'], + 'last_name' => $contact['LastName'], + 'phone' => $contact['Phone'], + 'fax' => $contact['Fax'], + 'update_from_salesforce' => TRUE + ); + + $fields = _sf_profile_fields_get($fields, $profile_fields, $contact); + + if (!variable_get('salesforce_contact_sync', 0) && !variable_get('salesforce_lead_sync', 0)) { + $fields['company'] = $account->company; + } + + user_save($account, $fields, 'Personal Information'); + + // Set last synced date to now + db_query('UPDATE {salesforce_users} SET last_sync=%d WHERE uid=%d', time(), $account->uid); + + watchdog('salesforce', t('Updated @user from Salesforce', array('@user' => $account->name))); + return TRUE; // Update performed + } + + return FALSE; // No updated perfomred + + break; + + } } @@ -154,7 +213,7 @@ function salesforce_contact($op, $accoun * handle leads in salesforce */ function salesforce_lead($op, $account, $params = array()) { - // grab a salesforce api connection + // create a salesforce api client $salesforce = salesforce(); // avoid fatal error on salesforce query with null element in query @@ -168,15 +227,21 @@ function salesforce_lead($op, $account, switch ($op) { case 'insert': case 'update': default: // create the main salesforce object - $lead = new sObject('Lead', null, - array( - 'Company' => $account->company, + $fields = array( 'FirstName' => $account->first_name, 'LastName' => $account->last_name, 'Phone' => $account->phone, 'Fax' => $account->fax, 'Email' => $account->mail, - )); + ); + + $fields = _sf_profile_fields_put($fields, $account); + + if (!variable_get('salesforce_contact_sync', 0) && !variable_get('salesforce_lead_sync', 0)) { + $fields['Company'] = $account->company; + } + + $lead = new sObject('Lead', null, $fields); // add any additional params to the lead object foreach ($params as $param => $param_value) { @@ -255,6 +320,60 @@ function salesforce_lead($op, $account, return array('error' => $error); } break; + + // New user is being created from new lead on salesforce + case 'insert from remote': + db_query('DELETE FROM {salesforce_users} WHERE uid=%d', $account->uid); + db_query('INSERT INTO {salesforce_users} (uid,lead_id,account_id,created,last_sync) VALUES (%d,"%s","%s",%d, %d)', $account->uid, $account->lead_id, $account->account_id, time(), time()); + break; + + // Information was updated in salesforce, we need to mirror it locally, called by cron.php + case 'update from remote': + $profile_fields = _sf_get_profile_field_info(); + + $lead = salesforce_lead_select($account,array_merge(array('FirstName','LastName','Phone','Fax','Email','IsConverted','ConvertedAccountId','ConvertedContactId','LastModifiedDate'),$profile_fields)); + + // Compare the salesforce LastModifiedDate to the local last_sync date and only perform the update if the + // Salesforce date is more recent. + if (strtotime($contact['LastModifiedDate']) > gmdate('U',$account->salesforce['last_sync'])) { + // Otherwise we can update their profile, status, etc. as needed. + $status = variable_get('salesforce_lead_account_status', 0); + // Has this Lead been converted to a Contact? + if ($lead['IsConverted'] == 'true') { + $status = 1; + db_query('UPDATE {salesforce_users} SET lead_id="", contact_id="%s", account_id="%s" WHERE uid=%d', $lead['ConvertedContactId'], $lead['ConvertedAcountId'], $account->uid); + } + + if ($account->mail != $lead['Email'] ) { + user_save($account, array('mail' => $lead['Email'], 'status' => $status, 'update_from_salesforce' => TRUE)); + } + + $fields = array( + 'first_name' => $lead['FirstName'], + 'last_name' => $lead['LastName'], + 'phone' => $lead['Phone'], + 'fax' => $lead['Fax'], + 'update_from_salesforce' => TRUE + ); + + $fields = _sf_profile_fields_get($fields, $profile_fields, $contact); + + if (!variable_get('salesforce_contact_sync', 0) && !variable_get('salesforce_lead_sync', 0)) { + $fields['company'] = $account->company; + } + + user_save($account, $fields, 'Personal Information'); + + // Set last synced date to now + db_query('UPDATE {salesforce_users} SET last_sync=%d WHERE uid=%d', time(), $account->uid); + watchdog('salesforce', t('Updated @user from Salesforce', array('@user' => $account->name))); + return true; // Update performed + } + + return false; // Update not preformed + + break; + } } @@ -284,7 +403,7 @@ function salesforce_event($op, $subject, $event = new sObject('Event', NULL, array( 'WhoId' => $id, $activityDate, - 'Subject' => t('%subject - %date', array('%subject' => $subject, '%date' => format_date(time(), 'large'))), + 'Subject' => t('@subject - @date', array('@subject' => $subject, '@date' => format_date(time(), 'large'))), 'Description' => $message, 'DurationInMinutes' => 1, )); @@ -329,27 +448,60 @@ function salesforce_event($op, $subject, /** * select from the salesforce contact table */ -function salesforce_account_select($account = NULL, $params = array(), $cache = true) { +function salesforce_contact_select($account = NULL, $params = array(), $cache = true) { $salesforce = salesforce(); $account = _salesforce_select_account($account); // make sure we're dealing with a contact if (!$account->salesforce['contact_id']) return array('error' => 'NO_CONTACT_ID'); - $defaults = array('accountId'); + $defaults = array('Id','AccountId'); $params = array_merge($defaults, $params); - // query salesforce for the contact's account_id - $contact = $salesforce->query("SELECT ". implode(',', $params) ." FROM contact WHERE id = '". $account->salesforce['contact_id'] ."'"); - $id = $contact['records']->values['AccountId']; + + // is there any SOQL limiter for selecting contacts? + if ( $limiter = variable_get('salesforce_contact_soql_limiter', '') ) { + $limiter = ' AND ( '. trim($limiter) .')'; + } + + // query salesforce + $contact = $salesforce->query("SELECT ". implode(',', $params) ." FROM contact WHERE Id = '". $account->salesforce['contact_id'] ."'". $limiter); + + $id = $contact['records']->id; if ($id) { if ($cache) { - _salesforce_insert('account_id', $id, $account); + _salesforce_insert('account_id', $contact['records']->values['AccountId'], $account); } + // add the Id to the returned value + $contact['records']->values['Id'] = $id; return $contact['records']->values; } } /** + * Select from the salesforce lead table + */ +function salesforce_lead_select($account = NULL, $params = array(), $cache = true) { + $salesforce = salesforce(); + $account = _salesforce_select_account($account); + + // make sure we're dealing with a lead + if (!$account->salesforce['lead_id']) return array('error' => 'NO_LEAD_ID'); + + $defaults = array('Id'); + $params = array_merge($defaults,$params); + + $lead = $salesforce->query("SELECT ". implode(',', $params) ." FROM lead WHERE id = '". $account->salesforce['lead_id'] ."'"); + //$lead = $salesforce->query("SELECT id, FirstName, LastName, Email, Phone, Fax, IsConverted, ConvertedAccountId, ConvertedContactId FROM lead WHERE id = '". $account->salesforce['lead_id'] ."'"); + + if (count($lead['records'])) { + // add the Id to the returned value + $lead['records']->values['Id'] = $lead['records']->id; + return $lead['records']->values; + } +} + + +/** * retreive contract information from an account */ function salesforce_contract_select($account = NULL, $params = array(), $defaults = true) { @@ -359,7 +511,7 @@ function salesforce_contract_select($acc // first we need to make sure we have an account_id to work with if (!$account->salesforce['account_id']) { - $result = salesforce_account_select($account); + $result = salesforce_contact_select($account); $account->salesforce['account_id'] = $result['AccountId']; if (!$account->salesforce['account_id']) return array('error' => 'NO_ACCOUNT_ID_FOUND'); } @@ -382,3 +534,83 @@ function salesforce_contract_select($acc return $contracts; } + +/** + * get a list of all udpated objects of type within specified date range + */ +function salesforce_get_updated($type, $startDate, $endDate='') { + $salesforce = salesforce(); + $endDate = ( !$endDate ) ? time() : $endDate; + $res = $salesforce->getUpdated($type, gmdate('c',$startDate), gmdate('c',$endDate)); + + $updated = array(); + if (count($res['ids']) > 1) { + foreach($res['ids'] as $id) { + $updated[$id] = salesforce_user_load($id); + } + } else if (count($res)) { + $updated[$res['ids']] = salesforce_user_load($res['ids']); + } + return (count($updated)) ? $updated : false; +} + +/** + * Load a user object if their is a user that matches salesforce lead_id, or contact_id + * + * @param $id + * Salesforce lead or contact id to look up. + * @return + * $user object if present, else FALSE + */ +function salesforce_user_load($id) { + $uid = db_result(db_query('SELECT uid FROM {salesforce_users} WHERE lead_id="%s" OR contact_id="%s"', $id, $id)); + if ($uid) { + return user_load(array('uid' => $uid)); + } else { + return FALSE; + } +} + +function _sf_get_profile_field_info() { + $res = db_query('SELECT name FROM {profile_fields} WHERE name LIKE "sf_%"'); + $fields = array(); + while($row = db_fetch_object($res)) { + $fields[$row->name] = substr($row->name,3); + } + return $fields; +} + +/** + * Look up fields with speccial salesforce "sf_" prefix + */ +function _sf_profile_fields_put($fields, $account) { + $fields = ( count($fields) ) ? $fields : array(); + // Look for fields with the special sf_ prefix + if ($account) { + foreach($account as $key => $value) { + if (substr($key,0,3) == 'sf_') { + // Check for profile date fields + if (is_array($value)) { + if ($value['year'] && $value['month'] && $value['day']) { + $value = new soapval(substr($key,3), 'date', gmdate('c',mktime(0,0,0,$value['month'],$value['day'],$value['year']))); + } + } + // Check for checkbox fields, hardcoded ... + if (in_array($key, array('sf_CORN__c'))) { + $value = ( $value == 1 ) ? true : false; + } + + $fields[substr($key,3)] = $value; + } + } + } + return $fields; +} + +function _sf_profile_fields_get($fields, $profile_fields, $contact) { + $fields = ( count($fields) ) ? $fields : array(); + foreach ($profile_fields as $key => $value) { + $fields[$key] = $contact[$value]; + } + return $fields; +} \ No newline at end of file diff -urp salesforce_orig/salesforce.install salesforce/salesforce.install --- salesforce_orig/salesforce.install 2007-11-03 09:07:40.000000000 -0500 +++ salesforce/salesforce.install 2008-01-21 14:26:35.000000000 -0600 @@ -15,7 +15,8 @@ function salesforce_install() { contact_id tinytext NOT NULL, opp_id tinytext NOT NULL, account_id tinytext NOT NULL, - created timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP, + created INT(10), + last_sync INT(10) NOT NULL default 0, PRIMARY KEY (uid) ) ENGINE=MyISAM DEFAULT CHARSET=utf8"); @@ -44,6 +45,21 @@ function salesforce_install() { drupal_set_message(t('salesforce.module - logs table did not install'), 'error'); } + db_query('CREATE TABLE IF NOT EXISTS {salesforce_sync_log} ( + id INT NOT NULL AUTO_INCREMENT, + type VARCHAR(50), + sfid VARCHAR(50), + nid INT, + created_at INT(10), + PRIMARY KEY(id) + ) ENGINE=MyISAM DEFAULT CHARSET=utf8'); + + if (!db_error()) { + drupal_set_message(t('salesforce.module - sync_log table installed successfully')); + } else { + drupal_set_message(t('salesforce.module - sync_log table did not install'), 'error'); + } + break; } diff -urp salesforce_orig/salesforce.module salesforce/salesforce.module --- salesforce_orig/salesforce.module 2007-11-12 12:37:19.000000000 -0600 +++ salesforce/salesforce.module 2008-01-21 14:16:43.000000000 -0600 @@ -37,6 +37,22 @@ function salesforce_menu($may_cache) { 'type' => MENU_NORMAL_ITEM, // optional ); + $items[] = array( + 'path' => 'admin/settings/salesforce/queue_contacts', + 'title' => t('Import/Update Contacts'), + 'callback' => 'salesforce_queue_contact_import_overview', + 'access' => user_access('administer salesforce'), + 'type' => MENU_NORMAL_ITEM + ); + + $items[] = array( + 'path' => 'admin/settings/salesforce/queue_contacts/do', + 'title' => t('Import/Update Contacts'), + 'callback' => 'salesforce_queue_contact_import', + 'access' => user_access('administer salesforce'), + 'type' => MENU_CALLBACK + ); + $items[] = array('path' => 'admin/content/salesforce', 'title' => t('Salesforce'), 'access' => user_access('administer salesforce'), @@ -102,54 +118,109 @@ function salesforce_admin_settings() { _salesforce_settings(); $form['salesforce_account'] = array( - '#type' => 'fieldset', - '#title' => t('SalesForce.com Account'), - '#collapsible' => true, - '#description' => t('this information is required for this module to work') - ); + '#type' => 'fieldset', + '#title' => t('SalesForce.com Account'), + '#collapsible' => true, + '#description' => t('this information is required for this module to work'), + ); $form['salesforce_account']['salesforce_user'] = array( - '#type' => 'textfield', - '#title' => t('Username'), - '#default_value' => variable_get('salesforce_user', NULL), - '#required' => true - ); + '#type' => 'textfield', + '#title' => t('Username'), + '#default_value' => variable_get('salesforce_user', NULL), + '#required' => true, + ); // TODO: need to encrypt / decrypt this if (!variable_get('salesforce_password', NULL)) { $form['salesforce_account']['salesforce_password'] = array( - '#type' => 'password', - '#title' => t('Password'), - '#default_value' => variable_get('salesforce_password', NULL), - '#required' => true - ); - } else { - $form['salesforce_account']['salesforce_password_delete'] = array( - '#prefix' => t('***** (TODO: setup like this until a better password saving feature can be implemented)'), - '#type' => 'checkbox', - '#title' => t('Delete Password'), - '#default_value' => variable_get('salesforce_password_delete', false) - ); + '#type' => 'password', + '#title' => t('Password'), + '#default_value' => variable_get('salesforce_password', NULL), + '#required' => true, + ); } + else { + $form['salesforce_account']['salesforce_password_delete'] = array( + '#prefix' => t('***** (TODO: setup like this until a better password saving feature can be implemented)'), + '#type' => 'checkbox', + '#title' => t('Delete Password'), + '#default_value' => variable_get('salesforce_password_delete', false), + ); + } + + $form['salesforce_contact_options'] = array( + '#type' => 'fieldset', + '#title' => t('Contact Options'), + '#collapsible' => true, + ); + $form['salesforce_contact_options']['salesforce_contact_sync'] = array( + '#type' => 'checkbox', + '#title' => t('Synchronize contacts in Salesforce'), + '#default_value' => variable_get('salesforce_contact_sync', 0), + '#description' => t('Allow Drupal to obtain information about Contacts from Salesforce and create Drupal user accounts for those Contacts. Once Drupal knows about a contact if it is updated in Salesforce it will be updated in Drupal.'), + ); + $form['salesforce_contact_options']['salesforce_contact_account_status'] = array( + '#type' => 'radios', + '#title' => t('Contact default account status'), + '#default_value' => variable_get('salesforce_contact_account_status', 0), + '#options' => array( 0 => t('Blocked'), 1 => t('Active')), + '#description' => t('when creating a new user account from a Salesforce contact the account will be given this status.'), + ); + // Stolen from user.module user_edit_form() + $roles = user_roles(1); + unset($roles[DRUPAL_AUTHENTICATED_RID]); + if ($roles) { + $form['salesforce_contact_options']['salesforce_contact_account_roles'] = array( + '#type' => 'checkboxes', + '#title' => t('Contact default account roles'), + '#default_value' => variable_get('salesforce_contact_account_roles',0), + '#options' => $roles, + '#description' => t('The new user receives the combined permissions of the %au role, and all roles selected here.', array('%au' => t('authenticated user'))) + ); + } + $form['salesforce_contact_options']['salesforce_contact_soql_limiter'] = array( + '#type' => 'textfield', + '#title' => t('Limit which contacts can be synced'), + '#description' => t('The WHERE portion of the SOQL statement used to select a contact from salesforce. Allows limiting to contacts that meet certian criteria. Enclose strings with single quotes.'), + '#default_value' => variable_get('salesforce_contact_soql_limiter', ''), + ); + + $form['salesforce_lead_options'] = array( + '#type' => 'fieldset', + '#title' => t('Lead Options'), + '#collapsible' => true, + ); + $form['salesforce_lead_options']['salesforce_lead_sync'] = array( + '#type' => 'checkbox', + '#title' => t('Synchronize leads in Salesforce'), + '#default_value' => variable_get('salesforce_lead_sync', 0), + '#description' => t('Allow Drupal to obtain information about Leads from Salesforce and create Drupal user accounts for those Leads. Once Drupal knows about a lead if it is updated in Salesforce it will be updated in Drupal.'), + ); + $form['salesforce_lead_options']['salesforce_lead_account_status'] = array( + '#type' => 'radios', + '#title' => t('Lead account status'), + '#default_value' => variable_get('salesforce_lead_account_status', 0), + '#options' => array( 0 => t('Blocked'), 1 => t('Active')), + '#description' => t('when creating a new user account from a Salesforce lead the account will be given this status.'), + ); $form['salesforce_options'] = array( - '#type' => 'fieldset', - '#title' => t('Additional Options'), - '#collapsible' => true - ); - + '#type' => 'fieldset', + '#title' => t('Additional Options'), + '#collapsible' => true, + ); $form['salesforce_options']['salesforce_leads_create_forms'] = array( - '#type' => 'textarea', - '#title' => t('Create Leads on form processes'), - '#description' => t('a new line for each form_id (drupal 5.0 will make this feature much easier as I will be able to generate a select box with the names of the forms)'), - '#default_value' => SALESFORCE_LEADS_CREATE_FORMS - ); - + '#type' => 'textarea', + '#title' => t('Create Leads on form processes'), + '#description' => t('a new line for each form_id (drupal 5.0 will make this feature much easier as I will be able to generate a select box with the names of the forms)'), + '#default_value' => SALESFORCE_LEADS_CREATE_FORMS, + ); $form['salesforce_options']['salesforce_events_create_forms'] = array( - '#type' => 'textarea', - '#title' => t('Create Events on form processes'), - '#description' => t('a new line for each form_id | message (drupal 5.0 will make this feature much easier as I will be able to generate a select box with the names of the forms)'), - '#default_value' => SALESFORCE_EVENTS_CREATE_FORMS - ); + '#type' => 'textarea', + '#title' => t('Create Events on form processes'), + '#description' => t('a new line for each form_id | message (drupal 5.0 will make this feature much easier as I will be able to generate a select box with the names of the forms)'), + '#default_value' => SALESFORCE_EVENTS_CREATE_FORMS, + ); // 4.7 to 5.x conversion: // return $form; @@ -160,12 +231,117 @@ function salesforce_admin_settings() { * Implementation of hook_cron(). */ function salesforce_cron() { + $result = db_query("SELECT sid, type, type_id, message, timestamp, status FROM {salesforce_log} WHERE status = 1 ORDER BY timestamp DESC"); if (db_num_rows($result) > 0) { while ($row = db_fetch_object($result)) { - _salesforce_cron_run($row); + _salesforce_cron_run($row); } } + + /** + * TODO: + * All of this should be put into the salesforce_sync_api.inc file once that becomes part of + * the core module + */ + + $time = time(); + $salesforce = salesforce(); + + // Check for updated or deleted leads and log them in the sync_log for later processing + if (variable_get('salesforce_lead_sync', 0)) { + // updated + $res = $salesforce->getUpdated('Lead', gmdate('c', variable_get('salesforce_last', time())-600), gmdate('c', time())); + if ($res) { + foreach((array)$res['ids'] as $id) { + db_query('INSERT INTO {salesforce_sync_log} (type,sfid,created_at) VALUES ("%s","%s",%d)', + 'lead_update', $id, time() + ); + } + } + unset($res); + + // deleted + $res = $salesforce->getDeleted('Lead', gmdate('c', variable_get('salesforce_last', time())-600), gmdate('c', time())); + if ($res) { + // returns are in a different format if there is just one + if ($res['deletedRecords']['id']) { + $tmp = $res['deletedRecords']; + $res = array('deletedRecords' => array('id' => $res['deletedRecords']['id'])); + } + if (count($res['deletedRecords'])) { + foreach((array)$res['deletedRecords'] as $record) { + db_query('INSERT INTO {salesforce_sync_log} (type,sfid,created_at) VALUES ("%s","%s",%d)', + 'lead_delete', $record['id'], time() + ); + } + } + } + unset($res); + } + + // Check for updated or deleted contacts and log them in the sync_log for later processing + if (variable_get('salesforce_contact_sync', 0)) { + // updated + $res = $salesforce->getUpdated('Contact', gmdate('c', variable_get('salesforce_last', time())-600), gmdate('c', time())); + if ($res) { + foreach((array)$res['ids'] as $id) { + db_query('INSERT INTO {salesforce_sync_log} (type,sfid,created_at) VALUES ("%s","%s",%d)', + 'contact_update', $id, time() + ); + } + } + unset($res); + + // deleted + $res = $salesforce->getDeleted('Contact', gmdate('c', variable_get('salesforce_last', time())-600), gmdate('c', time())); + if ($res) { + // returns are in a different format if there is just one + if ($res['deletedRecords']['id']) { + $tmp = $res['deletedRecords']; + $res = array('deletedRecords' => array('id' => $res['deletedRecords']['id'])); + } + if (count($res['deletedRecords'])) { + foreach((array)$res['deletedRecords'] as $record) { + db_query('INSERT INTO {salesforce_sync_log} (type,sfid,created_at) VALUES ("%s","%s",%d)', + 'contact_delete', $record['id'], time() + ); + } + } + } + unset($res); + } + + // Then get 10 records from the log, and processes them during this cron run. This is to prevent timing out. + $res = db_query('SELECT * FROM {salesforce_sync_log} WHERE type="lead_update" OR type="lead_delete" OR type="contact_update" OR type="contact_delete" LIMIT 10'); + while ($row = db_fetch_object($res)) { + switch($row->type) { + case 'lead_update': + _salesforce_cron_user_update(array($row->sfid => salesforce_user_load($row->sfid)), 'lead_id'); + break; + + case 'lead_delete': + $user = salesforce_user_load($row->sfid); + user_delete(array(),$user->uid); + watchdog('salesforce', t('Deleted user %user because Lead was deleted in Salesforce', $user->name), WATCHDOG_NOTICE); + break; + + case 'contact_update': + _salesforce_cron_user_update(array($row->sfid => salesforce_user_load($row->sfid)), 'contact_id'); + break; + + case 'contact_delete': + $user = salesforce_user_load($row->sfid); + db_query('UPDATE {users} SET status=0 WHERE uid=%d', $user->uid); + watchdog('salesforce', t('Blocked user because Contact was deleted in Salesforce'), WATCHDOG_NOTICE, url('user/'. $user->uid)); + break; + } + db_query('DELETE FROM {salesforce_sync_log} WHERE type="%s" AND sfid="%s"', $row->type, $row->sfid); + } + + // Log current time for next run + variable_set('salesforce_last', $time); + } /** @@ -188,26 +364,54 @@ function salesforce_user($op, &$edit, &$ if ($edit['create_lead']) { // must reload the object with user_load() to grab the updated profile fields salesforce_lead('insert', user_load(array('uid' => $account->uid)), array('Description' => t('new site user: @date', array('@date' => format_date(time(), 'large'))))); + + } else if ($edit['contact_id'] || $edit['lead_id']) { + // user is being created from salesforce contact/lead + $account->contact_id = $edit['contact_id']; + $account->lead_id = $edit['lead_id']; + $account->account_id = $edit['account_id']; + + if ($account->contact_id) { + salesforce_contact('insert from remote', $account); + } else if ($account->lead_id) { + salesforce_lead('insert from remote', $account); + } } break; case 'update': - if (_salesforce_is_form('leads', 'user_edit')) { + if (_salesforce_is_form('leads', 'user_edit') && !$edit['update_from_salesforce']) { // must reload the object with user_load() to grab the updated profile fields $updated_account = user_load(array('uid' => $account->uid)); - if ($account->salesforce['contact_id']) { - salesforce_contact('update', $updated_account); - } else if ($account->salesforce['lead_id']) { - salesforce_lead('update', $updated_account, array('Description' => t('last updated account information: @date', array('@date' => format_date(time(), 'large'))))); - } else { - // we check on a form value so we can display a checkbox instead of a hidden field if it's an admin viewing the page - if ($edit['create_lead']) { - // must reload the object with user_load() to grab the updated profile fields - salesforce_lead('insert', user_load(array('uid' => $account->uid)), array('Description' => t('admin adding lead: @date', array('@date' => format_date(time(), 'large'))))); - } + // we check on a form value so we can display a checkbox instead of a hidden field if it's an admin viewing the page + if ($edit['create_lead']) { + // must reload the object with user_load() to grab the updated profile fields + salesforce_lead('insert', user_load(array('uid' => $account->uid)), array('Description' => t('admin adding lead: @date', array('@date' => format_date(time(), 'large'))))); } } + + if ($account->salesforce['contact_id'] && variable_get('salesforce_contact_sync', 0)) { + salesforce_contact('update', user_load(array('uid' => $account->uid))); + } else if ($account->salesforce['lead_id'] && variable_get('salesforce_lead_sync', 0)) { + salesforce_lead('update', user_load(array('uid' => $account->uid)), array('Description' => t('last updated account information: @date', array('@date' => format_date(time(), 'large'))))); + } + break; + + case 'delete': + // Remove their information from salesforce_users table + db_query('DELETE FROM {salesforce_users} WHERE uid=%d', $account->uid); + break; + + case 'register': + // Add these fields to the registration form so we can make use of them later. + $form = array(); + $form['account_id'] = array('#type' => 'hidden', '#value' => ''); + $form['lead_id'] = array('#type' => 'hidden', '#value' => ''); + $form['contact_id'] = array('#type' => 'hidden', '#value' => ''); + return $form; + break; + case 'view': break; } @@ -293,20 +497,21 @@ function salesforce_contact_form($form_i * all forms set to use lead / event actions receive these submit functions * separated right now just because.. TODO: should probably be reduced 1 one function but we're waiting.. */ -function salesforce_form_lead_submit() { - $message =_salesforce_is_form('leads', $_POST['edit']['form_id'], true); +function salesforce_form_lead_submit($form_id, $form_values) { + $message =_salesforce_is_form('leads', $form_id, true); // TODO: ? (currently not needed but.. hmm...) } -function salesforce_form_event_submit() { +function salesforce_form_event_submit($form_id, $form_values) { global $user; // only track logged in users since we need an account to associated them with if ($user->uid == 0) return; // grab our message to display as the description of this event in salesforce - $message =_salesforce_is_form('events', $_POST['edit']['form_id'], true); + $message =_salesforce_is_form('events', $form_id, true); + // insert our event and yet again i find myself having to reload the user object to get my stuff? wtf? - $result = salesforce_event('insert', $_POST['edit']['form_id'], $message, user_load(array('mail' => $_POST['edit']['mail']))); + $result = salesforce_event('insert', $form_id, $message, user_load(array('mail' => $form_values['mail']))); } /** @@ -431,6 +636,69 @@ function salesforce_log_run_page() { } /** + * salesforce_queuue_contact_import + */ +function salesforce_queue_contact_import_overview() { + drupal_set_title(t('Salesforce - Contact update queue')); + $output = '

'. t('Queue your entire Salesforce Contact database for import/update in Drupal. If the Salesforce Contact is already associated with a Drupal user this will force a profile udpate. If the Salesforce Contact is not already linked to a Drupal user and matches a Drupal user based on e-mail address it will attempt to link the two, otherwise a new Drupal user will be created.') .'

'; + $output .= '

'. l('Queue now', 'admin/settings/salesforce/queue_contacts/do') .'

'; + + // Show all current log records + $res = db_query('SELECT * FROM {salesforce_sync_log} WHERE type="contact_update" ORDER BY created_at'); + if (db_num_rows($res)) { + $rows = array(); + while ($log_entry = db_fetch_object($res)) { + $rows[] = array( + $log_entry->sfid, + format_date($log_entry->created_at, 'short') + ); + } + $output .= '

'. t('Current queue') .'

'; + $output .= theme('table', array('Contact ID', 'Log Record Created'), $rows); + $output .= '

'. t('Total: @total', array('@total' => db_num_rows($res))). '

'; + } + + return $output; +} + +/** + * salesforce_queue_contact_import + * + * Queries Salesforce for the Id of all Contacts that match the optional SOQL limiter + * and then creates a record in the salesforce_sync_log for each one so that it will + * be updated next time cron.php is run + */ +function salesforce_queue_contact_import() { + $sf = salesforce(); + $query = 'SELECT Id FROM Contact'; + // is there any SOQL limiter for selecting accounts? + if ( $limiter = variable_get('salesforce_contact_soql_limiter', '') ) { + $query .= ' WHERE ('. trim($limiter) .')'; + } + + $res = $sf->query($query); + + if ($res['size'] > 0) { + $contacts = (array)$res['records']; + while ($res['done'] != 'true') { + $res = $sf->queryMore($res['queryLocator']); + $contacts = array_merge($contacts, (array)$res['records']); + } + } + + $total = 0; + foreach ($contacts as $c) { + if ($c->id != '') { + db_query('INSERT INTO {salesforce_sync_log} (type,sfid,created_at) VALUES ("%s", "%s", %d)', 'contact_update', $c->id, time()); + $total++; + } + } + + drupal_set_message(t('@total contacts queued for import/update from Salesforce', array('@total' => $total))); + drupal_goto('admin/settings/salesforce/queue_contacts'); +} + +/** * helper functions */ @@ -438,7 +706,7 @@ function _salesforce_admin_list($op) { $sf = 'https://na1.salesforce.com/'; if ($op == 'lead' || $op == 'contact') { - $result = db_query("SELECT s.uid FROM {salesforce_users} s WHERE %s != ''", 's.'. $op .'_id'); + $result = db_query("SELECT * FROM {salesforce_users} s WHERE %s != ''", 's.'. $op .'_id'); if (db_num_rows($result) > 0) { $cols = array(array('data' => t('User'), 'class' => 'user'), array('data' => t('Name')), array('data' => t('%i', array('%i' => ucfirst($op) .' ID')), 'class' => 'id')); if ($op == 'contact') { @@ -446,9 +714,9 @@ function _salesforce_admin_list($op) { } while ($row = db_fetch_object($result)) { - if ($op == 'contact') { + if ($op == 'contact' && !$row->account_id) { // we do this incase we don't have a cached account id - salesforce_account_select(user_load(array('uid' => $row->uid))); + salesforce_contact_select(user_load(array('uid' => $row->uid))); } $account = user_load(array('uid' => $row->uid)); // remove the extra 3 chars that are not used in this case @@ -522,6 +790,112 @@ function _salesforce_cron_run($row) { } /** + * Apply updated salesforce information to a local user account + */ +function _salesforce_cron_user_update($updated, $type='contact_id') { + foreach($updated as $id => $user) { + // if they already have an account locally we just update it. + if ($user && $id) { + if ($type == 'contact_id') { + salesforce_contact('update from remote', $user); + } else if ($type == 'lead_id') { + salesforce_lead('update from remote', $user); + } + + } else if ($id) { + // This is a record that exists on salesforce but not locally + $account->salesforce[$type] = $id; + + $profile_fields = _sf_get_profile_field_info(); + + // TODO: Rather than select each one individually can we select all of them at once? This would be faster + // and require less calls to salesforce + $fields = array('FirstName', 'LastName', 'Phone', 'Fax', 'Email'); + if ($type == 'contact_id') { + $contact = salesforce_contact_select($account, array_merge($fields, $profile_fields)); + } else if ($type == 'lead_id') { + $contact = salesforce_lead_select($account, array_merge($fields, $profile_fields)); + } + + + if ($contact) { + + // Lets see if we can find a local account with the same e-mail address first + if ($user = user_load(array('mail' => $contact['Email']))) { + // If we found one, we can actually just udpate that account. + $user->$type = $contact['Id']; + $user->salesforce[$type] = $contact['Id']; + $user->account_id = $contact['AccountId']; + + if ($type == 'contact_id') { + // saves the 'contact_id' field, creating a link between the drupal user and the salesforce contact + salesforce_contact('insert from remote', $user); + } else if ($type == 'lead_id') { + // saves the 'lead_id' field, creating a link between the drupal user and the salesforce lead + salesforce_lead('insert from remote', $user); + } + // We can then just re-call this function and save the changes + _salesforce_cron_user_update(array($contact['Id'] => $user), $type); + + _salesforce_cron_user_update(array($row->sfid => salesforce_user_load($row->sfid)), $type); + + } else { + + // If we can't find a local account with the same e-mail address we create a new one. + $values = array(); + $values['name'] = utf8_encode($contact['FirstName'] . $contact['LastName']); + $values['mail'] = $contact['Email']; + $values['pass'] = user_password(); + $values['notify'] = 1; + $values['create_lead'] = 0; + $values['first_name'] = $contact['FirstName']; + $values['last_name'] = $contact['LastName']; + $values['phone'] = $contact['Phone']; + $values['fax'] = $contact['Fax']; + $values[$type] = $id; + $values['account_id'] = $contact['AccountId']; + + $values = _sf_profile_fields_get($values, $profile_fields, $contact); + + // Make sure this is a unique username + $i = 1; + $name = $values['name']; + while (db_num_rows(db_query('SELECT uid FROM {users} WHERE name="%s"', $values['name'])) > 0) { + $values['name'] = $name .'_'. $i; + $i++; + } + + // Register the user account. Doing it this way allows Drupal to call all the necessary hooks + user_register_submit('user_register', $values); + + // get the new users uid + $uid = db_result(db_query('SELECT uid FROM {users} WHERE mail="%s" AND name="%s"', $values['mail'], $values['name'])); + + // Set the new users status and roles manually since we can't do it via cron unless you run cron.php when + // logged in as an administrator + db_query('UPDATE {users} SET status=%d WHERE uid=%d', variable_get('salesforce_contact_account_status', 0), $uid); + $roles = variable_get('salesforce_contact_account_roles',0); + if ($roles) { + foreach ($roles as $key => $rid) { + if ($key == $rid) { + db_query('DELETE FROM {users_roles} WHERE uid=%d AND rid=%d', $uid, $rid); + db_query('INSERT INTO {users_roles} (uid,rid) VALUES (%d, %d)', $uid, $rid); + } + } + } + + watchdog('salesforce', t('Inserted user @user from Salesforce', array('@user' => $values['name']))); + + } + + } + + } + } + return true; +} + +/** * used for inserting a single column into the salesforce_users table */ function _salesforce_insert($col, $value, $account = NULL) { @@ -617,7 +991,7 @@ function _salesforce_settings() { function _salesforce_user_ids($account = NULL) { $account = _salesforce_select_account($account); - $result = db_query("SELECT lead_id, contact_id, opp_id, account_id FROM {salesforce_users} WHERE uid = %d", $account->uid); + $result = db_query("SELECT lead_id, contact_id, opp_id, account_id, last_sync FROM {salesforce_users} WHERE uid = %d", $account->uid); if (db_num_rows($result) > 0) { return db_fetch_array($result);