Index: sf_node/sf_node.module =================================================================== RCS file: /cvs/salesforce/sf_node/sf_node.module,v retrieving revision 1.2 diff -u -r1.2 sf_node.module --- sf_node/sf_node.module 30 Jun 2009 08:46:13 -0000 1.2 +++ sf_node/sf_node.module 2 Jul 2009 01:11:44 -0000 @@ -484,6 +484,18 @@ // Load the fieldmap so we can get the object name. $map = salesforce_api_fieldmap_load($fieldmap); + + $prematch_found = false; + if (empty($sfid)) { + // call hook_sf_find_match to give opportunity to try to match existing sf object instead + // of creating a new one. + $matches = module_invoke_all('sf_find_match', 'export', 'node', $node, $map); + // TODO: handle case where multiple implementers of hook return multiple results. + if (count($matches)) { + $sfid = $matches[0]; + $prematch_found = true; + } + } if (empty($sfid)) { // Export the object to Salesforce. @@ -497,7 +509,8 @@ // If the export was successful... if ($response->success) { - if (empty($sfid)) { + // If this is the first time this drupal object has been exported... + if (empty($sfid) || $prematch_found) { // Store the Salesforce ID for the node and return TRUE. salesforce_api_id_save('node', $node->nid, $response->id, $fieldmap); } Index: sf_user/sf_user.module =================================================================== RCS file: /cvs/salesforce/sf_user/sf_user.module,v retrieving revision 1.2 diff -u -r1.2 sf_user.module --- sf_user/sf_user.module 30 Jun 2009 08:46:13 -0000 1.2 +++ sf_user/sf_user.module 2 Jul 2009 01:14:53 -0000 @@ -334,6 +334,18 @@ // Load the fieldmap so we can get the object name. $map = salesforce_api_fieldmap_load($fieldmap); + + $prematch_found = false; + if (empty($sfid)) { + // call hook_sf_find_match to give opportunity to try to match existing sf object instead + // of creating a new one. + $matches = module_invoke_all('sf_find_match', 'export', 'user', $account, $map); + // TODO: handle case where multiple implementers of hook return multiple results. + if (count($matches)) { + $sfid = $matches[0]; + $prematch_found = true; + } + } if (empty($sfid)) { // Export the object to Salesforce. @@ -347,7 +359,8 @@ // If the export was successful... if ($response->success) { - if (empty($sfid)) { + // If this is the first time this drupal object has been exported... + if (empty($sfid) || $prematch_found) { // Store the Salesforce ID for the node and return TRUE. salesforce_api_id_save('user', $uid, $response->id, $fieldmap); } --- sf_prematch.admin.inc +++ sf_prematch.admin.inc @@ -0,0 +1,240 @@ + t('Operations'), 'colspan' => 3), + ); + + $result = db_query("SELECT salesforce_field_map.*, salesforce_prematch.rule " . + "FROM salesforce_field_map LEFT JOIN salesforce_prematch " . + "ON salesforce_field_map.fieldmap = salesforce_prematch.fieldmap" + ); + $rows = array(); + + // Loop through all the indexed field maps. + while ($map = db_fetch_array($result)) { + if ($map['rule']) { + $op_0 = l(t('edit prematch'), SALESFORCE_PATH_FIELDMAPS . '/prematching/' . $map['fieldmap']); + $op1 = l(t('delete prematch'), SALESFORCE_PATH_FIELDMAPS . '/prematching/' . $map['fieldmap'] . '/delete'); + } + else { + $op_0 = l(t('add prematch'), SALESFORCE_PATH_FIELDMAPS . '/prematching/' . $map['fieldmap']); + $op1 = ''; + } + // Add the row to the table with the basic operations. + $rows[] = array( + $map['fieldmap'], + salesforce_api_fieldmap_object_label('drupal', $map['drupal']), + salesforce_api_fieldmap_object_label('salesforce', $map['salesforce']), + $op_0, + $op1, + ); + } + + // Add a message if no objects have been mapped. + if (count($rows) == 0) { + $rows[] = array( + array('data' => t('You have not yet defined any fieldmaps.'), 'colspan' => 7), + ); + } + + $output = theme('table', $header, $rows); + return $output; +} + +// Displays the form to add prematching to a fieldmap. +function sf_prematch_edit_form(&$form_state, $fieldmap) { + // Load the fieldmap from the database. + $map = salesforce_api_fieldmap_load($fieldmap); + + // Return to the admin page if the fieldmap did not exist. + if (empty($map)) { + drupal_set_message(t('That fieldmap does not exist.'), 'error'); + drupal_goto(SALESFORCE_PATH_FIELDMAPS); + } + + // Return to the admin page if the fieldmap has no mapped fields. + if (empty($map['fields'])) { + drupal_set_message(t('That fieldmap exists, but does not have any fields.'), 'error'); + drupal_goto(SALESFORCE_PATH_FIELDMAPS); + } + + // Load the prematch from the database. + $prematch = sf_prematch_match_by_load($fieldmap); + + $form = array(); + + // Add the index to the form array. + $form['fieldmap_index'] = array( + '#type' => 'value', + '#value' => $fieldmap, + ); + + // Set flag for use in deciding where to redirect to on form submit. + $is_new = + $form['new_prematch'] = array( + '#type' => 'value', + '#value' => ($prematch['primary_field'] == '' || + (isset($form_state['values']['new_prematch']) && $form_state['values']['new_prematch'])), + ); + + // Add a description of the prematch to the form array. + $form['prematch_desc'] = array( + '#value' => '
' . t('Before creating a new object, attempt to match an existing one using the fields and rules below. (Click cancel to skip this step.)') . '
', + ); + + // Add the select lists for the mapped Drupal field(s) to use in prematching. + $form['primary'] = array( + '#type' => 'select', + '#title' => t('Primary Field'), + '#options' => sf_prematch_get_options($map, true), + '#default_value' => $prematch['primary_field'], + '#required' => true, + ); + + $options = sf_prematch_get_options($map); + + $form['secondary'] = array( + '#type' => 'select', + '#title' => t('Secondary Field'), + '#options' => $options, + '#default_value' => $prematch['secondary_field'], + '#required' => false, + ); + $form['tertiary'] = array( + '#type' => 'select', + '#title' => t('Tertiary Field'), + '#options' => $options, + '#default_value' => $prematch['tertiary_field'], + '#required' => false, + ); + + // Create options to use in rule select. + $options = array( + SF_PREMATCH_PRIMARY_SECONDARY_AND_TERTIARY => 'primary, secondary and tertiary fields', + SF_PREMATCH_PRIMARY_AND_SECONDARY => 'primary and secondary fields', + SF_PREMATCH_PRIMARY => 'primary field', + ); + + // Add the select list for prematching rule. + $form['rule'] = array( + '#type' => 'select', + '#title' => t('Only consider a match if the found object matches'), + '#options' => $options, + '#default_value' => $prematch['rule'], + '#required' => true, + ); + + $form['submit'] = array( + '#type' => 'submit', + '#value' => t('Save changes'), + '#suffix' => l(t('Cancel'), SALESFORCE_PATH_FIELDMAPS), + ); + + return $form; +} + +function sf_prematch_edit_form_validate($form, &$form_state) { + +} + +function sf_prematch_edit_form_submit($form, &$form_state) { + $values = $form_state['values']; + + // Store data in the database. + if ($values['new_prematch']) { + db_query("INSERT INTO {salesforce_prematch} (fieldmap, primary_field, secondary_field, tertiary_field, rule) VALUES(%d, '%s', '%s', '%s', %d)", $values['fieldmap_index'], $values['primary'], $values['secondary'], $values['tertiary'], $values['rule']); + } + else { + db_query("UPDATE {salesforce_prematch} SET primary_field = '%s', secondary_field = '%s', tertiary_field = '%s', rule = %d WHERE fieldmap = %d", $values['primary'], $values['secondary'], $values['tertiary'], $values['rule'], $values['fieldmap_index']); + } + + // Display a message. + drupal_set_message(t('The changes have been saved.')); + + // Redirect to fieldmap list or prematching page. + if ($values['new_prematch']) { + $form_state['redirect'] = SALESFORCE_PATH_FIELDMAPS; + } + else { + $form_state['redirect'] = SALESFORCE_PATH_FIELDMAPS . '/prematching'; + } +} + +function sf_prematch_delete_form(&$form_state, $fieldmap) { + // Load the fieldmap and prematch from the database. + $map = salesforce_api_fieldmap_load($fieldmap); + $match_by = sf_prematch_match_by_load($fieldmap); + + // Return to the admin page if the fieldmap did not exist. + if (empty($match_by)) { + drupal_set_message(t('That prematch does not exist.'), 'error'); + drupal_goto(SALESFORCE_PATH_FIELDMAPS . '/prematching'); + } + + $form = array(); + + // Add the fieldmap to the form array. + $form['fieldmap_index'] = array( + '#type' => 'value', + '#value' => $fieldmap, + ); + + // Build the description text for this prematch. + $desc = t('You are about to delete the prematch for fieldmap '); + if ($map['action'] == 'import') { + $desc .= t('@index. That fieldmap maps Salesforce %salesforce objects to Drupal %drupal objects for import.', array('@index' => $map['fieldmap'], '%drupal' => $map['drupal'], '%salesforce' => $map['salesforce'])); + } + else { + $desc .= t('@index. That fieldmap maps Drupal %drupal objects to Salesforce %salesforce objects for export.', array('@index' => $map['fieldmap'], '%drupal' => $map['drupal'], '%salesforce' => $map['salesforce'])); + } + + return confirm_form($form, t('Are you sure you want to delete this prematch?'), SALESFORCE_PATH_FIELDMAPS . '/prematching', $desc, t('Delete')); +} + +function sf_prematch_delete_form_submit($form, &$form_state) { + // Delete the specified prematch. + sf_prematch_match_by_delete($form_state['values']['fieldmap_index']); + + // Display a message and return to the admin prematch screen. + drupal_set_message(t('The prematch has been deleted.')); + + $form_state['redirect'] = SALESFORCE_PATH_FIELDMAPS . '/prematching'; +} + +function sf_prematch_match_by_delete($fieldmap) { + db_query("DELETE FROM {salesforce_prematch} WHERE fieldmap = %d", $fieldmap); +} + +function sf_prematch_get_options($map, $required = false) { + // Extract terms from $map. + if ($map['action'] == 'import') { + $terms = array_keys($map['fields']); + } + else { + $terms = array_values($map['fields']); + } + sort($terms); + + // Build terms into ordered options to use in select. + $options = array(); + // Start with empty option if select is not required. + if (!$required) {$options[] = '';} + + // Add terms to options, making key = value so form value is key not integer. + foreach ($terms as $term) { + $options[$term] = $term; + } + + return $options; +} --- sf_prematch.info +++ sf_prematch.info @@ -0,0 +1,6 @@ +; $Id$ +name = Salesforce Match +description = Extends Salesforce API module so it checks for an existing sf object to match a drupal object before creating a new one in sf. +dependencies[] = salesforce_api, sf_node +package = Salesforce +core = 6.x --- sf_prematch.install +++ sf_prematch.install @@ -0,0 +1,71 @@ + t('Drupal to Salesforce object mapping table for pre-creation '), + 'fields' => array( + 'fieldmap' => array( + 'description' => 'Fieldmap id FK', + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => '0', + ), + 'primary_field' => array( + 'description' => 'Primary field or object name for use in prematching.', + 'type' => 'varchar', + 'length' => 128, + 'not null' => TRUE, + 'default' => '', + ), + 'secondary_field' => array( + 'description' => 'Secondary field or object name for use in prematching.', + 'type' => 'varchar', + 'length' => 128, + 'not null' => false, + 'default' => '', + ), + 'tertiary_field' => array( + 'description' => 'Tertiary field or object name for use in prematching.', + 'type' => 'varchar', + 'length' => 128, + 'not null' => false, + 'default' => '', + ), + 'rule' => array( + 'description' => 'Int identifying rule for prematching (based on constants)', + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0 + ), + ), + 'primary key' => array('fieldmap'), + ); + + return $schema; +} --- sf_prematch.main.inc +++ sf_prematch.main.inc @@ -0,0 +1,138 @@ + $drupal_field_name) { + if ($match_by_field == 'fieldmap' || $match_by_field == 'rule') {continue;} + $values[$match_by_field] = array(); + // If a handler is specified for retrieving a value for the source field... + if (isset($drupal_fields_info[$drupal_field_name]['export'])) { + // Get the value for the field from the handler function. + $drupal_value = $drupal_fields_info[$drupal_field_name]['export']($drupal_object, $drupal_field_name); + } + // Otherwise set the field on the export object to the value of the source + // field if it's present on the source object. + elseif (isset($drupal_object->$drupal_field_name)) { + $drupal_value = $drupal_object->$drupal_field_name; + } + else { + $drupal_value = null; + } + $values[$match_by_field]['drupal_value'] = $drupal_value; + $values[$match_by_field]['salesforce_field_name'] = $drupal_to_salesforce_fieldmap[$drupal_field_name]; + + // If there's a salesforce field to match by, include it in the select clause. + if ($values[$match_by_field]['salesforce_field_name']) { + $select_clause .= ', ' . $values[$match_by_field]['salesforce_field_name']; + } + } + + $sf_class = $map['salesforce']; + $from_where_clause = " FROM $sf_class WHERE "; + + // Use match by rule to build out where clause. + switch ($match_by['rule']) { + case SF_PREMATCH_PRIMARY_SECONDARY_AND_TERTIARY: + $from_where_clause .= $values['tertiary_field']['salesforce_field_name'] . " = '" . + $values['tertiary_field']['drupal_value'] . "' AND "; + // no break; + case SF_PREMATCH_PRIMARY_AND_SECONDARY: + $from_where_clause .= $values['secondary_field']['salesforce_field_name'] . " = '" . + $values['secondary_field']['drupal_value'] . "' AND "; + // no break; + case SF_PREMATCH_PRIMARY: + $from_where_clause .= $values['primary_field']['salesforce_field_name'] . " = '" . + $values['primary_field']['drupal_value'] . "'"; + break; + default: + return; + break; + } + + $query = $select_clause . $from_where_clause; + + // Run the SOQL query against the Salesforce API + if($sf = salesforce_api_connect()) { + try { + $result = $sf->client->query($query); + } + catch (Exception $e) { + // TODO: watchdog($e->faultstring); + return; + } + + switch ($result->size) { + case 0: + return; + break; + case 1: + return $result->records[0]->Id; + break; + default: + // TODO: handle multiple records + break; + } + } +} +/* +$result = Object of: stdClass + done = (boolean) true + queryLocator = null + records = Array [1] + 0 = Object of: stdClass + Email = (string:19) sid@craftyspace.com + FirstName = (string:3) Sid + Id = (string:18) 003S0000004X3aDIAS + LastName = (string:6) Maskit + size = (int) 1 +*/ + +/* +$map = Array [6] + action = (string:0) + automatic = (string:1) 0 + drupal = (string:12) node_profile + fieldmap = (string:1) 4 + fields = Array [4] + Email = (string:4) mail + FirstName = (string:16) field_first_name + LastName = (string:15) field_last_name + Volunteer_Interests__c = (string:25) field_volunteer_interests + salesforce = (string:7) Contact +$node = Object of: stdClass +$match_by = Array [5] + fieldmap = (string:1) 4 + primary_field = (string:4) mail + rule = (string:1) 1 + secondary_field = (string:15) field_last_name + tertiary_field = (string:16) field_first_name +*/ + + +function sf_prematch_import($object_type, $object, $map, $match_by) { + return; +} --- sf_prematch.module +++ sf_prematch.module @@ -0,0 +1,156 @@ + 'Prematching', + 'description' => 'Create or edit prematching for a fieldmap.', + 'page callback' => 'sf_prematch_list', + 'access arguments' => array('administer prematching'), + 'type' => MENU_LOCAL_TASK, + 'weight' => 15, + 'file' => 'sf_prematch.admin.inc', + ); + $items[SALESFORCE_PATH_FIELDMAPS . '/prematching/%'] = array( + 'title' => 'Edit fieldmap prematching', + 'description' => 'Edit an existing fieldmap prematch.', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('sf_prematch_edit_form', 5), + 'access arguments' => array('administer prematching'), + 'type' => MENU_CALLBACK, + 'file' => 'sf_prematch.admin.inc', + ); + $items[SALESFORCE_PATH_FIELDMAPS . '/prematching/%/delete'] = array( + 'title' => 'Delete prematching from a fieldmap', + 'description' => 'Delete an existing fieldmap prematch.', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('sf_prematch_delete_form', 5), + 'access arguments' => array('administer prematching'), + 'type' => MENU_CALLBACK, + 'file' => 'sf_prematch.admin.inc', + ); + + return $items; +} + +/** + * Implementation of hook_perm(). + */ +function sf_prematch_perm() { + return array('administer prematching'); +} + +/** + * Implementation of hook_form_alter(). + */ +function sf_prematch_form_alter(&$form, $form_state, $form_id) { + if ($form_id == 'salesforce_api_settings_form') { + $form['sf_prematch'] = array( + '#type' => 'fieldset', + '#title' => t('Pre-creation prematching'), + '#description' => t('Placeholder for any pre-creation matching settings.'), + '#collapsible' => TRUE, + '#collapsed' => TRUE, + '#weight' => -1, + ); + } + if ($form_id == 'salesforce_api_fieldmap_edit_form') { + if (!array_search('sf_prematch_fieldmap_edit_form_submit', $form['#submit'])) { + $form['#submit'][] = 'sf_prematch_fieldmap_edit_form_submit'; + } + } +} + +/** + * Piggyback on fieldmap edit form submit so can redirect to prematch creation for the fieldmap. + * + * @param array $form + * @param array $form_state + */ +function sf_prematch_fieldmap_edit_form_submit($form, &$form_state) { + $fieldmap = $form_state['values']['fieldmap_index']; + $form_state['redirect'] = SALESFORCE_PATH_FIELDMAPS .'/prematching/'. $fieldmap; +} + +/** + * Loads a prematch from the database. + * + * @param $fieldmap + * The index of the fieldmap's prematch to load. + * @return + * An array containing the prematch rule. + */ +function sf_prematch_match_by_load($fieldmap) { + static $match_bys; + if (!isset($match_bys[$fieldmap]) && $fieldmap != '') { + $result = db_query("SELECT * FROM {salesforce_prematch} WHERE fieldmap = %d", $fieldmap); + $match_by = db_fetch_array($result); + if (count($match_by)) { + $match_bys[$fieldmap] = $match_by; + } + else { + $match_bys[0] = array( + 'fieldmap' => $fieldmap, + 'primary_field' => '', + 'secondary_field' => '', + 'tertiary_field' => '', + 'rule' => 1 + ); + $fieldmap = 0; + } + } + + return $match_bys[$fieldmap]; +} + +/** + * Implement hook_sf_find_match + * + * @param string $action + * @param stdClass $object + * @param array $map + */ +function sf_prematch_sf_find_match($action, $object_type, $object, $map) { + if ($action != 'export') { + return; + } + $fieldmap = $map['fieldmap']; + $match_by = sf_prematch_match_by_load($fieldmap); + if ($match_by['primary_field'] == '') {return;} + require_once('sf_prematch.main.inc'); + switch ($action) { + case 'export': + $result = sf_prematch_export($object_type, $object, $map, $match_by); + break; + case 'import': + $result = sf_prematch_import($object_type, $object, $map, $match_by); + break; + default: + $result = null; + break; + } + return $result; +} + +/** + * Implement hook_sf_fieldmap_deleted + * + * @param int $fieldmap + */ +function sf_prematch_sf_fieldmap_deleted($fieldmap) { + require_once('sf_prematch.main.inc'); + sf_prematch_match_by_delete($fieldmap); +}