using Sitecore.ExperienceForms.Models;
using Sitecore.ExperienceForms.Processing.Actions;
using System;
using System.Collections.Generic;
using System.Linq;
using Sitecore.ExperienceForms.Processing;
using FuseIT.Sitecore.Salesforce;
using Sitecore.ExperienceForms.Mvc.Models.Fields;
using FuseIT.Sitecore.SalesforceConnector.Entities;
using FuseIT.Sitecore.SalesforceConnector.Services;
using System.Globalization;
using Sitecore.Mvc.Extensions;
using SCAnalytics = Sitecore.Analytics;
using FuseIT.S4S.WebToSalesforce;
using FuseIT.Sitecore.SalesforceConnector;
using FuseIT.S4S.WebToSalesforce.Connection;
using FuseIT.S4S.WebToSalesforce.Sitecore_Visit_Model;
using FuseIT.Sitecore.SalesforceConnector.SalesforcePartner;
using FuseIT.Sitecore.SalesforceConnector.Exceptions;
namespace FuseIT.Sitecore.Demo
{
/// <summary>
/// Sitecore Experience Forms custom submit action that will push web form input data and Sitecore analytics data into Salesforce using S4S connector.
/// </summary>
public class SubmitToSalesforceContacts : SubmitActionBase<string>
{
//Salesforce entity names
public const string ContactEntityName = "Contact";
public const string LeadEntityName = "Lead";
//Number of visits to retrieve from Sitecore
public const int NumberOfVisitsToRetrieve = 5;
/// <summary>
/// Get the saved Salesforce Session needed to connect to Salesforce using the Salesforce connection string name
/// </summary>
public SalesforceSession GetSalesforceSession
{
get
{
//Get SF session
return SalesforceSessionCache.GetSalesforceSession("S4SConnString");
}
}
public SubmitToSalesforceContacts(ISubmitActionData submitActionData) : base(submitActionData)
{
}
/// <summary>
/// Tries to convert the specified <paramref name="value" /> to an instance of the specified target type.
/// </summary>
/// <param name="value">The value.</param>
/// <param name="target">The target object.</param>
/// <returns>
/// true if <paramref name="value" /> was converted successfully; otherwise, false.
/// </returns>
protected override bool TryParse(string value, out string target)
{
target = string.Empty;
return true;
}
/// <summary>
/// Submit method to extract form fields and Sitecore analytics and insert/update Salesforce contact
/// </summary>
/// <param name="data"></param>
/// <param name="formSubmitContext"></param>
/// <returns></returns>
protected override bool Execute(string data, FormSubmitContext formSubmitContext)
{
try
{
//Get value from the form field "Email"
var emailFieldValue = string.Empty;
var emailField = formSubmitContext.Fields.Where(x => ((TitleFieldViewModel)x).Title.Equals("Email")).FirstOrDefault();
if (emailField != null)
{
var emailProperty = emailField.GetType().GetProperty("Value");
var postedEmail = emailProperty.GetValue(emailField);
emailFieldValue = postedEmail.ToStringOrEmpty();
}
if (string.IsNullOrEmpty(emailFieldValue))
{
throw new ArgumentException("Email address cannot be null or empty.");
}
WebToEntity webToSfEntity = new WebToEntity();
//Identify the current Sitecore contact using the email address and get the Sitecore identifier value
IdentifyCurrentVisitorByEmail(emailFieldValue);
string identifier = webToSfEntity.GetCurrentVisitorId();
if (string.IsNullOrWhiteSpace(identifier))
{
throw new InvalidOperationException("Unable to get the identifier for current Sitecore contact.");
}
var sfSession = this.GetSalesforceSession;
var xdbContactEntity = GetSalesforceXDBContactObject(identifier, sfSession);
string sfContactId = xdbContactEntity.InternalFields[WebToEntity.ContactField];
string entityId = string.Empty;
ContactService contactService = new ContactService(sfSession);
Contact sfContact = null;
//Try to find existing Salesforce Contact from ContactId or Email
if (!string.IsNullOrEmpty(sfContactId) && GetContact(contactService, sfContactId, "Id", out sfContact))
{
Logging.Debug(this, $"Salesforce Contact found with the Id: {sfContact.Id}.");
}
else if (!string.IsNullOrEmpty(emailFieldValue) && GetContact(contactService, emailFieldValue, "Email", out sfContact))
{
Logging.Debug(this, $"Salesforce Contact found with the Email: {sfContact.Email}.");
}
else
{
Logging.Debug(this, $"No existing Salesforce Contact found using Id: {sfContactId} or Email: {emailFieldValue}. Creating new Salesforce Contact.");
sfContact = new Contact();
}
sfContact.InternalFields["Email"] = emailFieldValue;
//Get "FirstName" field value from the form and set it to Salesforce
var firstnameField = formSubmitContext.Fields.Where(x => ((TitleFieldViewModel)x).Title.Equals("FirstName")).FirstOrDefault();
if (firstnameField != null)
{
var firstNameProperty = firstnameField.GetType().GetProperty("Value");
var postedFirstName = firstNameProperty.GetValue(firstnameField);
var firstNameFieldValue = postedFirstName.ToStringOrEmpty();
sfContact.InternalFields["FirstName"] = firstNameFieldValue;
}
//Get "LastName" field value from the form and set it to Salesforce
var lastNameField = formSubmitContext.Fields.Where(x => ((TitleFieldViewModel)x).Title.Equals("LastName")).FirstOrDefault();
if (lastNameField != null)
{
var lastNameProperty = lastNameField.GetType().GetProperty("Value");
var postedLastName = lastNameProperty.GetValue(lastNameField);
var lastNameFieldValue = postedLastName.ToStringOrEmpty();
sfContact.InternalFields["LastName"] = lastNameFieldValue;
}
//Get "Birthdate" field value from the form and set it to Salesforce
var birthdateField = formSubmitContext.Fields.Where(x => ((TitleFieldViewModel)x).Title.Equals("Birthdate")).FirstOrDefault();
if (birthdateField != null)
{
var birthdateProperty = birthdateField.GetType().GetProperty("Value");
var postedBirthdate = birthdateProperty.GetValue(birthdateField);
var birthdateFieldValue = postedBirthdate.ToStringOrEmpty();
DateTime dtValue;
if (!DateTime.TryParseExact(birthdateFieldValue, "M/d/yyyy hh:mm:ss tt", CultureInfo.CurrentCulture, DateTimeStyles.None, out dtValue))
{
Logging.Debug(this, "Error occured when converting Sitecore Experience form datetime field value to to Salesforce datetime format.");
}
else
{
sfContact.InternalFields.SetField("Birthdate", dtValue, typeof(DateTime));
}
}
var saveResults = contactService.SaveContact(sfContact);
if (saveResults.success)
{
PushSitecoreAnalyticsToSalesforce(sfSession, xdbContactEntity, identifier, saveResults.id);
}
return true;
}
catch (Exception ex)
{
Logging.Error(this, "Error saving web form data to Salesforce contact.", ex);
return false;
}
}
/// <summary>
/// Push Sitecore analytic data into Salesforce
/// </summary>
/// <param name="sfSession">Salesforce session</param>
/// <param name="xdbContactEntity">xdbContactEntity in Salesforce</param>
/// <param name="visitorId">Sitecore contact identifier (AKA visitor id)</param>
/// <param name="sfEntityId"> Saleforce enitity id</param>
private void PushSitecoreAnalyticsToSalesforce(SalesforceSession sfSession, GenericSalesforceEntity xdbContactEntity, string visitorId, string sfEntityId)
{
FieldService xdbContactFieldService = FieldService.Instance(WebToEntity.SitecoreXDBContactObject, sfSession);
int totalVisitsValue = 0;
int totalPageCount = 0;
string goalReached = string.Empty;
WebToEntity webToSfEntity = new WebToEntity();
xdbContactEntity.InternalFields[WebToEntity.ContactField] = sfEntityId;
// Populate the Sitecore analytics fields
if (webToSfEntity.GetVisitsData(visitorId, sfEntityId, out totalVisitsValue, out totalPageCount, out goalReached))
{
if (xdbContactFieldService.HasField(WebToEntity.SitecoreAliasIdField))
{
Guid visitorGuid;
Guid.TryParse(visitorId, out visitorGuid);
if (visitorGuid != Guid.Empty)
{
xdbContactEntity.InternalFields[WebToEntity.SitecoreAliasIdField] = visitorId;
}
}
else
{
Logging.Warn(this, $"{nameof(PushSitecoreAnalyticsToSalesforce)}(): Cannot find Salesforce field [{WebToEntity.SitecoreAliasIdField}].");
}
if (xdbContactFieldService.HasField(WebToEntity.SitecoreXDBGoalValueField))
{
xdbContactEntity.InternalFields[WebToEntity.SitecoreXDBGoalValueField] = totalVisitsValue.ToString();
}
else
{
Logging.Warn(this, $"{nameof(PushSitecoreAnalyticsToSalesforce)}(): Cannot find Salesforce field [{WebToEntity.SitecoreXDBGoalValueField}].");
}
if (xdbContactFieldService.HasField(WebToEntity.SitecoreXDBVisitCountField))
{
xdbContactEntity.InternalFields[WebToEntity.SitecoreXDBVisitCountField] = totalPageCount.ToString();
}
else
{
Logging.Warn(this, $"{nameof(PushSitecoreAnalyticsToSalesforce)}(): Cannot find Salesforce field [{WebToEntity.SitecoreXDBVisitCountField}].");
}
// Save Salesforce entities
var genericSalesforceService = new GenericSalesforceService(sfSession, WebToEntity.SitecoreXDBContactObject);
var saveXDBContactsInSFResult = genericSalesforceService.Save(xdbContactEntity);
string xdbContactEntityId = saveXDBContactsInSFResult.id;
Logging.Debug(this, $"{nameof(PushSitecoreAnalyticsToSalesforce)}(): Saved {WebToEntity.SitecoreXDBContactObject} record in Salesforce. Id: {xdbContactEntityId}.");
List<SitecoreVisit> sitecoreVisits;
Logging.Debug(this, $"{nameof(PushSitecoreAnalyticsToSalesforce)}(): Getting visits for visitor id: {visitorId}.");
if (webToSfEntity.GetContactData(visitorId, NumberOfVisitsToRetrieve, sfEntityId, out sitecoreVisits))
{
try
{
List<EntityBase> entities = UpdateSitecoreAnalyticsInSalesforce(visitorId, sfEntityId, ContactEntityName, sitecoreVisits);
DisparateSalesforceService disparateSalesforceService = new DisparateSalesforceService(sfSession);
//Insert records in batch
var saveSitecoreVisitsInSFResults = disparateSalesforceService.InsertEntities(entities);
}
catch (SalesforceCreateException ex)
{
if (ex.FailedSaveResults.Any(r => r.errors.Any(e => e.statusCode != StatusCode.DUPLICATE_VALUE)))
{
Logging.Error(this, $"{nameof(PushSitecoreAnalyticsToSalesforce)}(): Update Analytics Failed. Salesforce Exception occurred.", ex);
throw;
}
}
catch (SalesforceException ex)
{
Logging.Error(this, $"{nameof(PushSitecoreAnalyticsToSalesforce)}(): Update Analytics Failed. Salesforce Exception occurred.", ex);
throw;
}
}
else
{
Logging.Warn(this, $"{nameof(PushSitecoreAnalyticsToSalesforce)}(): Unable to get Analytics contact data for visitor id: {visitorId}.");
}
}
}
/// <summary>
/// Update Sitecore Analytics In Salesforce
/// </summary>
/// <param name="visitorId">The visitor id</param>
/// <param name="sfEntityId">The Salesforce entity id</param>
/// <param name="entityType">The Salesforce entity type</param>
/// <param name="sitecoreVisits"></param>
private List<EntityBase> UpdateSitecoreAnalyticsInSalesforce(string visitorId, string sfEntityId, string entityType, List<SitecoreVisit> sitecoreVisits)
{
try
{
//Data in the list needs to be arranged to reduce the number of chunks.
//Salesforce Limit: 10 chunks in a single operation
List<EntityBase> entities = new List<EntityBase>();
List<GenericSalesforceEntity> visitEntities = new List<GenericSalesforceEntity>();
List<GenericSalesforceEntity> goalEntities = new List<GenericSalesforceEntity>();
List<GenericSalesforceEntity> profileEntities = new List<GenericSalesforceEntity>();
List<GenericSalesforceEntity> profileKeyEntities = new List<GenericSalesforceEntity>();
UpdateSalesforceEntitiesFormScVisits(visitorId, sfEntityId, entityType, sitecoreVisits, visitEntities, goalEntities, profileEntities, profileKeyEntities);
entities.AddRange(visitEntities);
entities.AddRange(goalEntities);
entities.AddRange(profileEntities);
entities.AddRange(profileKeyEntities);
return entities;
}
catch (Exception ex)
{
Logging.Error(this, ex.Message, ex);
throw;
}
}
/// <summary>
/// Update actual Salesforce custom objects with Sitecore analytics data
/// </summary>
/// <param name="visitorId"></param>
/// <param name="sfEntityId"></param>
/// <param name="entityType"></param>
/// <param name="sitecoreVisits"></param>
/// <param name="visitEntities"></param>
/// <param name="goalEntities"></param>
/// <param name="profileEntities"></param>
/// <param name="profileKeyEntities"></param>
private void UpdateSalesforceEntitiesFormScVisits(string visitorId, string sfEntityId, string entityType, List<SitecoreVisit> sitecoreVisits, List<GenericSalesforceEntity> visitEntities, List<GenericSalesforceEntity> goalEntities, List<GenericSalesforceEntity> profileEntities, List<GenericSalesforceEntity> profileKeyEntities)
{
SitecoreVisit latestVist = sitecoreVisits.OrderByDescending(v => v.StartDateTime).FirstOrDefault();
GenericSalesforceEntity xdbContact = new GenericSalesforceEntity(WebToEntity.SitecoreXDBContactObject);
xdbContact.InternalFields[WebToEntity.SitecoreAliasIdField] = visitorId;
foreach (SitecoreVisit scVisit in sitecoreVisits)
{
GenericSalesforceEntity visit = new GenericSalesforceEntity(WebToEntity.SitecoreVisitObject);
visit.InternalFields[WebToEntity.BrowserField] = scVisit.Browser;
visit.InternalFields.SetField(WebToEntity.StartDateTimeField, scVisit.StartDateTime);
visit.InternalFields.SetField(WebToEntity.EndDateTimeField, scVisit.EndDateTime);
visit.InternalFields[WebToEntity.VisitDurationField] = scVisit.VisitDuration.ToString();
visit.InternalFields[WebToEntity.InteractionField] = scVisit.InteractionId.ToString();
visit.InternalFields.SetField(WebToEntity.PageViewsField, scVisit.PageViews);
visit.InternalFields[WebToEntity.TrafficChannelField] = scVisit.TrafficChannel;
DateTime startDateDatum = new DateTime(scVisit.StartDateTime.Year, 1, 1, 0, 0, 0, scVisit.StartDateTime.Kind);
long elapsedTicks = scVisit.StartDateTime.Ticks - startDateDatum.Ticks;
TimeSpan elapsedTimeSpan = new TimeSpan(elapsedTicks);
visit.InternalFields[WebToEntity.VisitIndexField] = string.Format("{0}.{1:000.00000}", scVisit.StartDateTime.Year, elapsedTimeSpan.TotalDays);
visit.InternalFields.SetField(WebToEntity.ValueField, scVisit.Value);
visit.InternalFields[WebToEntity.CampaignNameField] = scVisit.CampaignName;
if (entityType.Equals(ContactEntityName, StringComparison.InvariantCultureIgnoreCase))
{
visit.InternalFields[WebToEntity.ContactField] = sfEntityId;
}
else if (entityType.Equals(LeadEntityName, StringComparison.InvariantCultureIgnoreCase))
{
visit.InternalFields[WebToEntity.LeadField] = sfEntityId;
}
else
{
Logging.Error(this, $"{nameof(UpdateSalesforceEntitiesFormScVisits)}(): {entityType} Entity type not supported.");
}
visit.SetInternalEntity<GenericSalesforceEntity>(WebToEntity.SitecoreXDBContactField, WebToEntity.SitecoreXDBContactField, xdbContact);
GenericSalesforceEntity tempVist = new GenericSalesforceEntity(WebToEntity.SitecoreVisitObject);
tempVist.InternalFields[WebToEntity.InteractionField] = visit.InternalFields[WebToEntity.InteractionField];
visitEntities.Add(visit);
foreach (SitecoreGoal scGoal in scVisit.GoalsReached)
{
GenericSalesforceEntity goal = new GenericSalesforceEntity(WebToEntity.SitecoreGoalObject);
goal.InternalFields[WebToEntity.SitecoreGoalNameField] = scGoal.Name;
goal.InternalFields[WebToEntity.GoalLinkField] = scGoal.GoalPath;
goal.InternalFields.SetField(WebToEntity.DateTimeField, scGoal.DateTime);
goal.InternalFields.SetField(WebToEntity.ValueField, scGoal.Value);
//Set the last 4 digits of Ticks to 0000 - Create a new dt and get tick
DateTime goalDateTime = new DateTime(scGoal.DateTime.Year, scGoal.DateTime.Month, scGoal.DateTime.Day, scGoal.DateTime.Hour, scGoal.DateTime.Minute, scGoal.DateTime.Second, scGoal.DateTime.Millisecond);
goal.InternalFields[WebToEntity.SitecoreGoalId] = goalDateTime.Ticks.ToString() + "_" + scVisit.InteractionId + "_" + scGoal.GoalId;
if (entityType.Equals(ContactEntityName, StringComparison.InvariantCultureIgnoreCase))
{
goal.InternalFields[WebToEntity.ContactField] = sfEntityId;
}
else if (entityType.Equals(LeadEntityName, StringComparison.InvariantCultureIgnoreCase))
{
goal.InternalFields[WebToEntity.LeadField] = sfEntityId;
}
//Setting the master Sitecore Visit field for refrential integrity
goal.SetInternalEntity<GenericSalesforceEntity>(WebToEntity.SitecoreVisitField, WebToEntity.SitecoreVisitField, tempVist);
goalEntities.Add(goal);
}
foreach (SitecoreProfile scProfile in scVisit.Profiles)
{
GenericSalesforceEntity profile = new GenericSalesforceEntity(WebToEntity.SitecoreProfileObject);
profile.InternalFields[WebToEntity.SitecoreProfileNameField] = scProfile.Name;
profile.InternalFields[WebToEntity.PatternCardField] = scProfile.PatternCard;
profile.InternalFields.SetField(WebToEntity.TotalField, scProfile.Total);
profile.InternalFields[WebToEntity.ProfileExternalIdField] = scVisit.VisitIndex.ToString() + "_" + scProfile.Name + "_" + scVisit.InteractionId.ToString();
//If the Profiles are from the latest visit
if (scVisit.InteractionId == latestVist.InteractionId)
{
if (entityType.Equals(ContactEntityName, StringComparison.InvariantCultureIgnoreCase))
{
profile.InternalFields[WebToEntity.ContactField] = sfEntityId;
}
else if (entityType.Equals(LeadEntityName, StringComparison.InvariantCultureIgnoreCase))
{
profile.InternalFields[WebToEntity.LeadField] = sfEntityId;
}
}
//Setting the master Sitecore Visit field for refrential integrity
profile.SetInternalEntity<GenericSalesforceEntity>(WebToEntity.SitecoreVisitField, WebToEntity.SitecoreVisitField, tempVist);
profileEntities.Add(profile);
GenericSalesforceEntity tempProfile = new GenericSalesforceEntity(WebToEntity.SitecoreProfileObject);
tempProfile.InternalFields[WebToEntity.ProfileExternalIdField] = profile.InternalFields[WebToEntity.ProfileExternalIdField];
foreach (string scProfileKey in scProfile.ProfileData.Keys)
{
GenericSalesforceEntity profileKey = new GenericSalesforceEntity(WebToEntity.SitecoreProfilekeyObject);
profileKey.InternalFields[WebToEntity.SitecoreProfilekeyField] = scProfileKey;
profileKey.InternalFields.SetField(WebToEntity.ValueField, scProfile.ProfileData[scProfileKey]);
profileKey.InternalFields[WebToEntity.SitecoreProfileKeyIdField] = scVisit.VisitIndex.ToString() + "_" + scProfile.Name + "_" + scProfileKey + "_" + scVisit.InteractionId.ToString();
//Setting the master Sitecore Profile field for refrential integrity
profileKey.SetInternalEntity<GenericSalesforceEntity>(WebToEntity.SitecoreProfileField, WebToEntity.SitecoreProfileField, tempProfile);
profileKeyEntities.Add(profileKey);
}
}
}
}
/// <summary>
/// Gets an existing Salesforce Sitecore xDB contact object if it already exists otherwise a new instance
/// </summary>
/// <param name="identifier">The identifier to use to find the object</param>
/// <returns>The Sitecore xDB contact object if found; otherwise an empty object</returns>
private GenericSalesforceEntity GetSalesforceXDBContactObject(string identifier, SalesforceSession sfSession)
{
string aliasIdFieldName = WebToEntity.SitecoreAliasIdField;
GenericSalesforceEntity entity = null;
var genericSalesforceService = new GenericSalesforceService(sfSession, WebToEntity.SitecoreXDBContactObject);
Logging.Info(this, $"{nameof(GetSalesforceXDBContactObject)}(): Find existing sObject {WebToEntity.SitecoreXDBContactObject} using identifier: {identifier}.");
entity = genericSalesforceService.GetSingleByFieldEquals(aliasIdFieldName, identifier, "Id", WebToEntity.ContactField, WebToEntity.LeadField);
if (entity != null)
{
Logging.Debug(this, $"{nameof(GetSalesforceXDBContactObject)}(): Found {WebToEntity.SitecoreXDBContactObject}, {aliasIdFieldName} = {identifier}.");
}
else
{
Logging.Debug(this, $"{nameof(GetSalesforceXDBContactObject)}(): Not found {WebToEntity.SitecoreXDBContactObject}, {aliasIdFieldName} = {identifier}.");
entity = new GenericSalesforceEntity(WebToEntity.SitecoreXDBContactObject);
}
return entity;
}
/// <summary>
/// Get Salesforce contact by field
/// </summary>
/// <param name="contactService"></param>
/// <param name="value"></param>
/// <param name="field"></param>
/// <param name="contact"></param>
/// <returns></returns>
private bool GetContact(ContactService contactService, string value, string field, out Contact contact)
{
List<Contact> contacts = contactService.GetByFieldEquals(field, value);
if (contacts != null && contacts.Count > 0)
{
contact = contacts[0];
return (contact != null);
}
contact = null;
return false;
}
/// <summary>
/// Identify current Sitecore visitor by email
/// </summary>
private void IdentifyCurrentVisitorByEmail(string emailAddress)
{
try
{
if (SCAnalytics.Tracker.Current != null && SCAnalytics.Tracker.Current.Session != null)
{
SCAnalytics.Tracker.Current.Session.IdentifyAs("S4S", emailAddress);
}
}
catch (Exception ex)
{
Logging.Error(this, $"{nameof(IdentifyCurrentVisitorByEmail)}(): An error occurred trying to identify the current visitor for Sitecore Experience forms.", ex);
throw;
}
}
}
} |