Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

Code Block
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 takepush 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 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;
            }
        }        
    }
}