Salesforce Suite is a group of modules for Drupal that allows for pulling data from Salesforce into Drupal, as well as pushing data from Drupal to Salesforce. The module api provides some very useful hooks, including the _salesforce_pull_entity_presave hook implemented by the Salesforce Pull module. In this blog post, we’ll look at using that hook to pull three Salesforce custom fields (select lists) into Drupal as taxonomy terms in three vocabularies.
Create a custom module to house the hook called <sitename>_salesforce and create a <sitename>_salesforce.module file. In that file, drop in the presave function, as copied from salesforce.api.php in the Salesforce Suite module:
/**
* Act on an entity just before it is saved by a salesforce pull operation.
* Implementations should throw a SalesforcePullException to prevent the pull.
*
* @param $entity
* The Drupal entity object.
* @param array $sf_object
* The Salesforce query result array.
* @param SalesforceMapping $sf_mapping
* The Salesforce Mapping being used to pull this record
*
* @throws SalesforcePullException
*/
function hook_salesforce_pull_entity_presave($entity, $sf_object, $sf_mapping) {
if (!some_entity_validation_mechanism($entity)) {
throw new SalesforcePullException('Refused to pull invalid entity.');
}
// Set a fictional property using a fictional Salesforce result object.
$entity->example_property = $sf_object['Lookup__r']['Data__c'];
}
Take a look at the example code in the function body but remove it.
The hook gets called during the salesforce_pull_process_records function with this line:
// Allow modules to react just prior to entity save.
module_invoke_all('salesforce_pull_entity_presave', $wrapper->value(), $sf_object, $sf_mapping);
So, that’s where we will intervene with our custom code. With this hook, we have access to the data queried from Salesforce, and the entity that is about to be saved into Drupal, so it's a perfect time to do any translations between the two data sets.
The first problem we have to address is that, by default, the Salesforce Pull module will create a new node as it processes each Salesforce record instead of modifying the existing nodes on your Drupal site. If you don’t want this behavior, add this code:
// first of all, don't create new nodes
if (isset($entity->is_new) && $entity->is_new == TRUE) {
throw new SalesforcePullException('Tried to create a new node.');
}
You may also want to look at the _salesforce_pull_mapping_object_alter hook to aid in prematching nodes.
Then, we need to define our taxonomy vocabularies:
// lookup table
$names_vids = array(
'exampleVocabularyA' => array('vid' => 1, 'field' => 'field_example_vocabulary_a'),
'exampleVocabularyB' => array('vid' => 2, 'field' => 'field_example_vocabulary_b'),
'exampleVocabularyC' => array('vid' => 3, 'field' => 'field_example_vocabulary_c'),
);
Gather the terms from $sf_object like this:
// gather terms
$incoming = array(
'exampleVocabularyA' => explode(';', $sf_object['Terms_A__c'] ? $sf_object['Terms_A__c'] : ''),
'exampleVocabularyB' => explode(';', $sf_object['Terms_B__c'] ? $sf_object['Terms_B__c'] : ''),
'exampleVocabularyC' => explode(';', $sf_object['Terms_C__c'] ? $sf_object['Terms_C__c'] : ''),
);
You’ll want to clean up the incoming data:
array_walk_recursive($incoming, 'trim');
$incoming = array_map('array_filter', $incoming);
Then, we need to iterate over the incoming terms and create a new term if it doesn’t already exist in Drupal. Finally, we set the tids on the desired nodes:
foreach($incoming as $vname => $term_names) {
$tids = array();
foreach($term_names as $term_name) {
$tid = taxonomy_get_term_by_name($term_name, $vname);
if (empty($tid)) {
// add the term if we don't already have it
$newterm = new stdClass();
$newterm->name = $term_name;
$newterm->vid = $names_vids[$vname]['vid'];
taxonomy_term_save($newterm);
$tid = $newterm->tid;
}
array_push($tids, $tid);
}
// set tids on target nodes
// first unset all existing tids
$entity->{$names_vids[$vname]['field']} = array();
// using $length here because we modify $tids in loop
$length = count($tids);
for ($i = 0; $i < $length; $i++) {
$tid = array_shift($tids);
$tid = array_keys($tid)[0];
$entity->{$names_vids[$vname]['field']}[LANGUAGE_NONE][$i]['tid'] = $tid;
}
}
This will keep your Drupal nodes in sync (on each cron run) with any terms added or deleted on the Salesforce objects.
(If you are having trouble getting Salesforce to complete its whole queue on a single cron run, I recommend this blog post for troubleshooting tips: Drupal Salesforce Not Updating Records. In particular, we recommend the Queue UI module.)