Missing required Salesforce push/pull hooks.

It is a hand-made patch provides following hooks:

   Provides following hooks:

     Various:

       - hook_salesforce_pull_select_query_alter($record_type, SalesforceSelectQuery $query)
         Called before Salesforce select query is executed. User may want to fetch custom fields then.

     Skipping records:

       - hook_salesforce_pull_record_allowed($result)
         Should return FALSE if specified record shouldn't be imported into Drupal.

       - hook_salesforce_pull_entity_allowed($drupal_entity_type, $entity, $sf_object, $sf_mapping)
         Should return FALSE if specified entity shouldn't be imported into Drupal.

     Pulling:

       - hook_salesforce_pull_before_update_alter($sf_object, $drupal_entity_type, $entity)
         Called before the Drupal entity is updated with new data.

       - hook_salesforce_pull_after_update_alter($sf_object, $drupal_entity_type, $entity)
         Called after the Drupal entity is updated with new data.

       - hook_salesforce_pull_before_create_alter($sf_object, $drupal_entity_type, $entity)
         Called before the Drupal entity is created with data from Salesforce.

       - hook_salesforce_pull_after_create_alter($sf_object, $drupal_entity_type, $entity)
         Called after the Drupal entity is created with data from Salesforce.

     Pushing:

       - hook_salesforce_push_before_update_alter($entity_type, $entity, $params, $mapping)
         Called before the Drupal entity is updated inside Salesforce.

       - hook_salesforce_push_after_update_alter($entity_type, $entity, $params, $mapping)
         Called after the Drupal entity is updated inside Salesforce.

       - hook_salesforce_push_before_create_alter($entity_type, $entity, $params, $mapping)
         Called before a new Drupal entity is created and sent into Salesforce.

       - hook_salesforce_push_after_create_alter($entity_type, $entity, $params, $mapping)
         Called after a new Drupal entity is created and sent into Salesforce.

       - hook_salesforce_push_before_delete_alter($drupal_entity_type, $entity, $mapping)
         Called before the Drupal entity is queued to be deleted by Salesforce.

       - hook_salesforce_push_after_delete_alter($drupal_entity_type, $entity, $mapping)
         Called after the Drupal entity is queued to be deleted by Salesforce.

Example usage:

Comments

kenorb’s picture

Status: Active » Needs review
kenorb’s picture

Issue summary: View changes
kenorb’s picture

Issue summary: View changes

Examples:


/**
 * Implements hook_salesforce_pull_select_query_alter().
 */
function foo_salesforce_salesforce_pull_select_query_alter($record_type, SalesforceSelectQuery $query) {
  if ($record_type === 'Product2') {
    $query->fields[] = 'Online__c';
  }
}

/**
 * Implements hook_salesforce_pull_record_allowed().
 */
function foo_salesforce_salesforce_pull_record_allowed($record) {
  // Checking "Available Online" field.
  return (bool)$record['Online__c'];
}

/**
 * Implements hook_salesforce_pull_after_update_alter().
 */
function foo_salesforce_salesforce_pull_after_update_alter($sf_object, $entity_type, $entity) {
  // Retrieving and updating prices in the entity.

  $prices = foo_salesforce_product_prices_pull($entity->salesforce_id);

  foreach ($prices as $currency => $amount) {
    $entity->{'commerce_price_' . strtolower($currency)}[LANGUAGE_NONE] = array(
      array(
        'currency_code' => $currency,
        'amount' => $amount,
        'data' => array('components' => array()),
      ),
    );
  }

  // $entity->commerce_price = $entity->commerce_price_usd;

  entity_save($entity_type, $entity);

  watchdog('foo',
    'Imported prices into entity %label from Salesforce Object ID: %sfid',
    array(
      '%label' => $entity->title,
      '%sfid' => $entity->salesforce_id,
    )
  );
}

/**
 * Implements hook_salesforce_pull_after_create_alter().
 */
function foo_salesforce_salesforce_pull_after_create_alter($sf_object, $entity_type, $entity) {
  // Retrieving and storing prices in the entity.
  return foo_salesforce_salesforce_pull_after_update_alter($sf_object, $entity_type, $entity);
}

/**
 * Implements hook_salesforce_push_after_create_alter().
 */
function foo_salesforce_salesforce_push_after_create_alter($entity_type, $entity, $mapping) {
  // Creating prices from the entity into the Salesforce via its API.
  foo_salesforce_product_prices_push($entity->salesforce_id, foo_salesforce_product_entity_extract_prices($entity));
}

/**
 * Implements hook_salesforce_push_after_update_alter().
 */
function foo_salesforce_salesforce_push_after_update_alter($entity_type, $entity, $mapping) {
  // Updating prices from the entity into the Salesforce via its API.
  foo_salesforce_product_prices_push($entity->salesforce_id, foo_salesforce_product_entity_extract_prices($entity));
}

// Helpful functions

function foo_salesforce_product_prices_pull($salesforce_product_id) {
  $prices = array();

  $sfapi = salesforce_get_api();
  $query = new SalesforceSelectQuery('PriceBookEntry');
  $query->fields = array('CurrencyIsoCode', 'UnitPrice');
  $query->addCondition('PriceBookEntry.Product2.Id', "'" . $salesforce_product_id . "'");
  $query->addCondition('IsActive', 'true');
  $query->addCondition('Pricebook2Id', "'" . FOO_SALESFORCE_PRICEBOOK_ID_DEFAULT . "'");

  $result = $sfapi->query($query);

  foreach ($result['records'] as $record) {
    $prices[$record['CurrencyIsoCode']] = $record['UnitPrice'] * 100;
  }

  return $prices;
}

function foo_salesforce_product_prices_push($salesforce_product_id, $prices) {
  $sfapi = salesforce_get_api();

  $query = new SalesforceSelectQuery('PriceBookEntry');
  $query->fields = array('Id', 'CurrencyIsoCode', 'UnitPrice', 'IsActive');
  $query->addCondition('Pricebook2Id', "'" . FOO_SALESFORCE_PRICEBOOK_ID_DEFAULT . "'");
  $query->addCondition('Product2Id', "'" . $salesforce_product_id . "'");

  $result = $sfapi->query($query);

  if ($result['records']) {
    // Pricebook entries already exist, updating.
    foreach ($result['records'] as $record) {
      $sfapi->objectUpdate('PriceBookEntry', $record['Id'], array(
        'UnitPrice' => $prices[$record['CurrencyIsoCode']],
      ));
    }
  }
  else {
    // No pricebook entries exists, creating new entries.
    foreach ($prices as $currency => $amount) {
      $sfapi->objectCreate('PriceBookEntry', array(
        'Pricebook2Id' => FOO_SALESFORCE_PRICEBOOK_ID_DEFAULT,
        'Product2Id' => $salesforce_product_id,
        'CurrencyIsoCode' => $currency,
        'UnitPrice' => $amount,
        'IsActive' => 'true',
      ));
    }
  }
}

kenorb’s picture

Issue summary: View changes
kenorb’s picture

StatusFileSize
new7.77 KB

Small improvements.

ohthehugemanatee’s picture

This is a lot of extra hooks. I tried an alternative approach, just adding one hook to modify/override results, in #2223669: Add "pull allowed" hooks. Otherwise I can get by with the normal entity hooks, especially since salesforce_pull flags the entity when it's saving something.

Maybe that flag isn't quite enough, but you could really simplify this down if you think about a similar approach. Flag the entity so that people can use core entity hooks.

kenorb’s picture

Issue summary: View changes
kenorb’s picture

Version: 7.x-3.x-dev » 7.x-3.0
StatusFileSize
new7.38 KB

Updated patch against 7.x-3.0

StevenWill’s picture

This patch is great. I applied the patch cleanly and have not tested all the hooks, but was able to test hook_salesforce_pull_entity_allowed with success.

yogaf’s picture

Thanks @kenorb. This patch is essential for many use cases.
How we can move it foreword and commit it?

kenorb’s picture

Once tested, please set the status to Reviewed.

yogaf’s picture

Attached patch against latest dev. Still got hooks I need to test.

rojan raj’s picture

Can you please light me more how do I implement this? I created a custome module folder, under the folder I had .info and .module files, I placed the above code in .module files.
And then I ran cron, but not a single function is called up, can you please share me some ideas?
I would be very much thankfull.
Thanks

yogaf’s picture

You need to apply a patch on the dev version of the Salesforce module and after that you can use the new hooks from your custom module.
If you still have trouble making this work, let me now...

aaronbauman’s picture

Version: 7.x-3.0 » 7.x-3.x-dev
StatusFileSize
new7.48 KB

rerolled against latest

aaronbauman’s picture

Issue tags: +Needs documentation

There isn't any change to existing functionality here, so we should be able to get this in quickly and easily.

Adding "Needs Documentation" tag because we need this patch, or a separate patch, to document these new hooks in salesforce.api.php

kenorb’s picture

We can use examples from #3 for salesforce.api.php, pretty much self-explanatory.

aaronbauman’s picture

Issue tags: -Needs documentation
StatusFileSize
new11.26 KB

reroll with docs

kenorb’s picture

Looks great for me.

Some side note, that foo_salesforce_product_prices_pull() can be also replaced with the block from function foo_salesforce_product_prices_pull() above, so it has some examples how to use salesforce_get_api()/SalesforceSelectQuery().

tauno’s picture

Status: Needs review » Postponed (maintainer needs more info)

I reviewed this patch in depth and I don't think all the additional hooks make sense. The examples provided all appear possible using existing or recently added hooks.

  • pull_query_alter duplicates hook_salesforce_query_alter
  • pull_record_allowed and pull_entity_allowed do the same thing but at different points. I think a version of the record_allowed hook makes sense, but possibly more like an improved version of #2223669: Add "pull allowed" hooks
  • all the before hooks should be workable using the existing push_params_alter and pull_entity_value_alter hooks as well as hook_entity_presave
  • the after hooks look unnecessary due to the recently added push_success and push_fail hooks (once #1118736: Add push success/failure hooks for REST is added for REST pushes) as well as the standard entity hooks (hook_entity_update, hook_entity_insert, hook_entity_delete). You can also implement these hooks on the mapping object entity.

Currently it looks like adding a variant of pull_record_allowed (or the approach in #2223669: Add "pull allowed" hooks) is the only necessary change. Marking need more info to determine if there are use cases for the other hooks.

aaronbauman’s picture

One use case that applies to some of my projects:
pull_entity_allowed provides Drupal entity context, while pull_record_allowed does not.
If 2 Drupal entities are mapped to the same SF object, this additional context is critical.

And one theoretical use case:
The biggest difference between pull_select_query_alter and salesforce_query_alter is, again, contextualization. pull_select_query_alter applies only to queries issued via pull enqueue operations, while salesforce_query_alter would apply to any query leveraging the SF wrapper. Maybe uncommon, but possibly significant for complex implementations.

aaronbauman’s picture

Status: Postponed (maintainer needs more info) » Closed (won't fix)
Related issues: +#2223669: Add "pull allowed" hooks, +#1118736: Add push success/failure hooks for REST

OK, I think this probably makes sense to close this as "won't fix".
Please, ohthehugemanatee, kenorb, or anyone else on this thread, create a new issue for individual hooks (or groups of hooks), along with some details about what use case the proposed hook addresses.

I'll post a new patch in #2223669: Add "pull allowed" hooks to cover the concerns I outlined above for pull actions.

chrisolof’s picture

New issue with an individual hook:

#2650502: Add hook_salesforce_pull_entity_save()

Similar to hook_salesforce_pull_after_create_alter() and hook_salesforce_pull_after_update_alter(), but not an alter-type hook. Basically a chance to react to a salesforce_pull Drupal entity save with both the saved Drupal entity and the Salesforce object available.

TravisJohnston’s picture

Thanks for all your work everyone! I've been looking around forever to try and find information on the available hooks and examples so I could create the ability to check to make sure records don't exist before creating - which before it was only creating duplicates.

My issue right now, which I don't see in your documentation, is how to save the newly mapped entity with the entity in Drupal.

At the moment, I just use a custom Salesforce ID text field that I fill in with a returned ID value. This works great in most circumstances but I am having issues saving the returned ID within customer billing profiles in Drupal Commerce.

Is there a class available to create the mapping of the objects after it's completed?

Also, I found that with these hooks, I had to turn off the Triggers in my mappings in order for them to work. If I left the Triggers in place, they fired first before the hooks took place thus not making any change. But if I delete the mappings completely, the hooks break. So I need them, but just need them turned off in order for them to work.

yogaf’s picture

@TravisJohnston I'm not sure I understand completely what you're trying to do, but if you're trying to maintain relation between drupal entities and SF objects it comes for free using this module. no need for hooks for that.

TravisJohnston’s picture

The built in functionality doesn't seem to first check if an entity exists prior to pushing from Drupal to Salesforce based on a series of criteria. Using the hooks, I'm able to do that pretty easily.

Or rather it does, but only 1 key. I need to validate if it's the same user for a registration for instance based on email, first, and last name and not just off of 1 field.

TravisJohnston’s picture

The built in functionality doesn't seem to first check if an entity exists prior to pushing from Drupal to Salesforce based on a series of criteria. Using the hooks, I'm able to do that pretty easily.

aaronbauman’s picture

Travis:
Why aren't push_allowed and pull_allowed sufficient to accomplish this?

Unless someone can demonstrate how the hooks in this patch are useful -- beyond those in linked issues and existing hooks -- I'm gonna leave this on "won't fix".

TravisJohnston’s picture

I haven't tried those hooks specifically aaronbauman - since the documentation has been limited. Using the provided, non-code, approach you can only set a single key which is not granular enough make a distinction if the record is the same. Not sure if those hooks would of helped but I was able to do this pretty easily by utilizing the API connection and query methods provided by the module and doing the checks myself and storing the returned ID's into a field on each of my entities.

You can mark it as "won't fix" if you'd like.

chrisolof’s picture

Just broke out a group of three closely-related salesforce_pull hooks into a new issue:

#2688033: Add entity_presave/insert/update sf pull hooks

I think it's a better approach than what I had going in #23. Curious as to what you guys think. Patch is in there ready for testing.

kenorb’s picture