X
Closing this message and/or accessing our website tells us you are happy to receive all cookies on the ClearPeople website.
However, if you would like to, you can change your cookies settings at any time.
This article describes a Sitecore customisation.

This article describes a Sitecore customisation with the following sections:

  1. Motivation: Describes the reasons to implement the customisation.
  2. Usage: Describes how to use the customisation.
  3. Implementation: Describes the main details of the implementation and provides the relevant source code.
  4. Technical Details: Records the Sitecore version on which the customisation was implemented along with the version of any related module.

Motivation

A WFFM form is required to store analytic data. Some form’s fields are required to be stored and other cannot be stored due to privacy specifications. WFFM on Sitecore 8.0 allows to enable and disable form analytics. However, it is an all-or-none choice. WFFM, OOTB does not allow to specify which fields should be stored and which should be ignored.

 

Usage

The feature implemented next allows to specify in a configuration file what fields of a specific form should be masked (ignored) while storing the values submitted by the user. Masked fields can be defined by their name or their field Id.

 

Implementation

The WFFM forms analytic data is stored by the class “Sitecore.WFFM.Analytics.Providers.AnalyticsDataProvider”, implemented in the assembly “Sitecore.WFFM.Analytics” and injected into Sitecore WFFM by the configuration file “Sitecore.WFFM.Analytics.config” as follows:

 

<wffm>
    <formsDataProvider type="Sitecore.WFFM.Analytics.Providers.AnalyticsDataProvider, Sitecore.WFFM.Analytics" singleInstance="true"/>
    <facetFactory type="Sitecore.WFFM.Analytics.FacetFactory, Sitecore.WFFM.Analytics" singleInstance="true"/>
</wffm> 

The fields masking process can take place during the storage process therefore a new DiscreetAnalyticsDataProvider needs to substitute the original one to implement the required logic. The starting point is the original AnalyticsDataProvider which code can be obtained by decompiling its DLL (included at the end of the article). The method to look at is “InsertForm”. Last line within the method “InsertForm” is responsible for storing the data in MongoDB therefore just before this line the masking process will take place.

 

public void InsertForm(IFormData form)
{
    object obj = null;
    if (Warn.IsNull(Tracker.Current, "Tracker.Current") || Warn.IsNull(Tracker.Current.Session, "Tracker.Current.Session"))
    {
        return;
    }
    if (Tracker.Current.Session.CustomData.ContainsKey(Sitecore.WFFM.Analytics.Core.Constants.FormsCollectionName) && !Tracker.Current.Session.CustomData.TryGetValue(Sitecore.WFFM.Analytics.Core.Constants.FormsCollectionName, out obj))
    {
        return;
    }
    WffmContext wffmContext = obj as WffmContext;
    if (wffmContext == null)
    {
        wffmContext = new WffmContext();
        Tracker.Current.Session.CustomData.Add(Sitecore.WFFM.Analytics.Core.Constants.FormsCollectionName, wffmContext);
    }
    RemoveUnwantedData(form);
    wffmContext.Forms.Add(form);
}

A new method named “RemoveUnwantedData” will take care of removing the form fields which values are sensitive and should not be stored. Before implementing it, some scaffolding code is required in order to get the masked field definitions from the configuration file.

File: Web\WFFM\Analytics\Providers\FieldMasking.cs

 

using System;
using System.Collections.Generic;

namespace Web.WFFM.Analytics.Providers
{
    public class FieldMasking: Dictionary<Guid, FieldMaskingForm>
    {

        public void Add(FieldMaskingForm form)
        {
            this.Add(form.FormId, form);
        }
    }
}

File: Web\WFFM\Analytics\Providers\FieldMaskingField.cs

 

using System;
using System.Collections.Generic;

namespace Web.WFFM.Analytics.Providers
{
    public class FieldMaskingForm
    {
        private List>FieldMaskingField< fields = new List>FieldMaskingField<();
        private HashSet>Guid< maskedFieldIds = new HashSet>Guid<();
        private HashSet>string< maskedFieldNames = new HashSet>string<();

        public Guid FormId { get; set; }

        public HashSet>Guid< MaskedFieldIds { get { return this.maskedFieldIds; } }
        public HashSet>string< MaskedFieldNames { get { return this.maskedFieldNames; } }

       public void Add(FieldMaskingField field)
        {
            if (field.Id == null && field.Name == null)
            {
                throw new ArgumentException("The field needs to have an Id or a Name.");
            }
            if (field.Id != null) maskedFieldIds.Add(field.Id);
            if (field.Name != null) maskedFieldNames.Add(field.Name);
            this.fields.Add(field);
        }
    }
}

File: Web\WFFM\Analytics\Providers\FieldMaskingForm.cs

 

using System;

namespace Web.WFFM.Analytics.Providers
{
    public class FieldMaskingField
    {
        public Guid Id { get; set; }
        public string Name { get; set; }
    }
}

The classes provide above will hold the data relative to the fields that need to be ignored. They will be exposed to the method “RemoveUnwantedData” through a new property that needs to be added to the new class “DiscreetAnalyticsDataProvider”:

 

public FieldMasking FieldMasks { get; set; }

The property “FieldMasks” will be automatically populated by WFFM once it is properly configured. Now that all scaffolding code is in place the “RemoveUnwantedData” can be implemented:

 

private void RemoveUnwantedData(IFormData form)
{
    var masks = this.FieldMasks;
    if (masks == null || masks.Count == 0) return;

    Guid formId = form.FormID;

    if (masks.ContainsKey(formId))
    {
        var maskedForm = masks[formId];
        var maskedFieldIds = maskedForm.MaskedFieldIds;
        var maskedFieldNames = maskedForm.MaskedFieldNames;

        var newFields = new List<IFieldData>();

        foreach (var field in form.Fields)
        {
            var fieldId = field.FieldId;
            var fieldName = field.FieldName;
            if (maskedFieldIds.Any(id => id == fieldId) || maskedFieldNames.Any(n => n == fieldName))
            {
                field.Data = string.Empty;
                field.Value = string.Empty;
            }
            else
            {
                newFields.Add(field);
            }
        }

        form.Fields = newFields;

    }
}

As previously mentioned, for the new code to work Sitecore configuration needs to be changed with two main purposes:

  1. Replace original “AnalyticsDataProvider” class with the new “DiscreetAnalyticsDataProvider” class.
  2. Indicate what fields need to be masked (ignored).

Following best practices, the original Sitecore configuration files will remain untouched and the changes will be done with a new patching configuration file which name can be any. The only relevant thing is that needs to be read after the Sitecore original one (Website\App_Config\Include\Sitecore.WFFM.Analytics.config). A good way to achieve this is by adding a subfolder under “App_Config\Include” with the name of the current feature.

 

The required configuration file content is:

 

<?xml version="1.0" encoding="utf-8"?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>

    <wffm>
      <formsDataProvider type="Web.WFFM.Analytics.Providers.DiscreetAnalyticsDataProvider, Website" singleInstance="true" patch:instead="formsDataProvider" >
        <FieldMasks ref="wffm/fieldMasking" />
      </formsDataProvider>

      <fieldMasking type="Web.WFFM.Analytics.Providers.FieldMasking, Website">
        <forms hint="list:Add">

          <form type="Web.WFFM.Analytics.Providers.FieldMaskingForm, Website">
            <FormId>{00001111-2222-AAAA-BBBB-CCCCDDDDEEEE}</FormId>
            <fields hint="list:Add">
              <field type="Web.WFFM.Analytics.Providers.FieldMaskingField, Website"><name>Field1</name></field>
              <field type="Web.WFFM.Analytics.Providers.FieldMaskingField, Website"><name>Field2</name></field>
              <field type="Web.WFFM.Analytics.Providers.FieldMaskingField, Website"><name>Field3</name></field>
            </fields>
          </form>

          <form type="Web.WFFM.Analytics.Providers.FieldMaskingForm, Website">
            <FormId>{33334444-5555-AAAA-BBBB-123412341234}</FormId>
            <fields hint="list:Add">
              <field type="Web.WFFM.Analytics.Providers.FieldMaskingField, Website"><name>FieldA</name></field>
              <field type="Web.WFFM.Analytics.Providers.FieldMaskingField, Website"><name>FieldB</name></field>
              <field type="Web.WFFM.Analytics.Providers.FieldMaskingField, Website"><name>FieldC</name></field>
            </fields>
          </form>

        </forms>
      </fieldMasking>
      
    </wffm>

  </sitecore>
</configuration>

Please notice that the xml element “FormId” and the field names need to be updated with the details of the actual form which fields need to be masked. Below is the entire code of the class “DiscreetAnalyticsDataProvider”.

 

File: Web\WFFM\Analytics\Providers\DiscreetAnalyticsDataProvider.cs

 

using Sitecore.Analytics;
using Sitecore.Analytics.Reporting;
using Sitecore.Configuration;
using Sitecore.Data;
using Sitecore.Diagnostics;
using Sitecore.WFFM.Analytics;
using Sitecore.WFFM.Analytics.Core;
using Sitecore.WFFM.Analytics.Model;
using Sitecore.WFFM.Analytics.Providers;
using Sitecore.WFFM.Analytics.Providers.Common;
using Sitecore.WFFM.Analytics.Queries;
using Sitecore.WFFM.Core.Extensions;
using System;
using System.Collections.Generic;
using System.Linq;

namespace Web.WFFM.Analytics.Providers
{
    public class DiscreetAnalyticsDataProvider : IWfmDataProvider
    {
        public ReportDataProviderBase ReportsProvider
        {
            get
            {
                return Assert.ResultNotNull<ReportDataProvider>(Factory.CreateObject("reporting/dataProvider", true) as ReportDataProvider, "ReportDataProvider is null.");
            }
        }
        public IEnumerable<IFormData> GetFormData(Guid formId)
        {
            ID formDataReportQuery = IDs.FormDataReportQuery;
            FormDataReportQuery formDataReportQuery2 = new FormDataReportQuery(formId, formDataReportQuery, this.ReportsProvider, null);
            formDataReportQuery2.Execute();
            return formDataReportQuery2.Data;
        }
        public void InsertForm(IFormData form)
        {
            object obj = null;
            if (Warn.IsNull(Tracker.Current, "Tracker.Current") || Warn.IsNull(Tracker.Current.Session, "Tracker.Current.Session"))
            {
                return;
            }
            if (Tracker.Current.Session.CustomData.ContainsKey(Sitecore.WFFM.Analytics.Core.Constants.FormsCollectionName) && !Tracker.Current.Session.CustomData.TryGetValue(Sitecore.WFFM.Analytics.Core.Constants.FormsCollectionName, out obj))
            {
                return;
            }
            WffmContext wffmContext = obj as WffmContext;
            if (wffmContext == null)
            {
                wffmContext = new WffmContext();
                Tracker.Current.Session.CustomData.Add(Sitecore.WFFM.Analytics.Core.Constants.FormsCollectionName, wffmContext);
            }
            RemoveUnwantedData(form);
            wffmContext.Forms.Add(form);
        }

        public FieldMasking FieldMasks { get; set; }

        private void RemoveUnwantedData(IFormData form)
        {
            var masks = this.FieldMasks;
            if (masks == null || masks.Count == 0) return;

            Guid formId = form.FormID;

            if (masks.ContainsKey(formId))
            {
                var maskedForm = masks[formId];
                var maskedFieldIds = maskedForm.MaskedFieldIds;
                var maskedFieldNames = maskedForm.MaskedFieldNames;

                var newFields = new List<IFieldData>();

                foreach (var field in form.Fields)
                {
                    var fieldId = field.FieldId;
                    var fieldName = field.FieldName;
                    if (maskedFieldIds.Any(id => id == fieldId) || maskedFieldNames.Any(n => n == fieldName))
                    {
                        field.Data = string.Empty;
                        field.Value = string.Empty;
                    }
                    else
                    {
                        newFields.Add(field);
                    }
                }

                form.Fields = newFields;

            }
        }

        public IEnumerable<IFormContactsResult> GetFormsStatisticsByContact(Guid formId, PageCriteria pageCriteria)
        {
            ID formStatisticsByContactsReportQuery = IDs.FormStatisticsByContactsReportQuery;
            FormStatisticsByContactReportQuery formStatisticsByContactReportQuery = new FormStatisticsByContactReportQuery(formId, formStatisticsByContactsReportQuery, this.ReportsProvider, null, null);
            formStatisticsByContactReportQuery.Execute();
            return formStatisticsByContactReportQuery.Data.Skip(pageCriteria.PageIndex).Take(pageCriteria.PageSize);
        }
        public IFormStatistics GetFormStatistics(Guid formId)
        {
            ID formSubmitStatisticsReportQuery = IDs.FormSubmitStatisticsReportQuery;
            FormSummaryReportQuery formSummaryReportQuery = new FormSummaryReportQuery(formId, formSubmitStatisticsReportQuery, this.ReportsProvider, null);
            formSummaryReportQuery.Execute();
            return new FormStatistics
            {
                Dropouts = formSummaryReportQuery.Dropouts,
                SubmitsCount = formSummaryReportQuery.SubmitsCount,
               Visits = formSummaryReportQuery.Visits,
                SuccessSubmits = formSummaryReportQuery.Success
            };
        }
        public IEnumerable<IFormFieldStatistics> GetFormFieldsStatistics(Guid formId)
        {
            ID formFieldsStatisticsReportQuery = IDs.FormFieldsStatisticsReportQuery;
            FormFieldsStatisticsReportQuery formFieldsStatisticsReportQuery2 = new FormFieldsStatisticsReportQuery(formId, formFieldsStatisticsReportQuery, this.ReportsProvider, null);
            formFieldsStatisticsReportQuery2.Execute();
            return formFieldsStatisticsReportQuery2.Data;
        }
    }

}

Technical Details

The provided code has been tested for the following Sitecore versions:

  • Sitecore: 8.0 rev. 150812
  • WFFM: 8.0 rev. 150625

Author bio

Benjamin Moles
Benjamin Moles
.NET Developer
I'm a passionate .NET and SQL developer. I've worked with a broad variety of technologies and I am currently contributing to the success of ClearPeople's Sitecore projects. Beside IT stuff, I love to spend time with family and play with remote-controlled helicopters.

Comments


comments powered by Disqus

Related Articles

Sign up to our Newsletter

Every now and then, we'd like to send you information that delivers, develops and promotes our products and services that are relevant to you. Submitting your details tells us that you're OK with this and you also agree to our Privacy & Cookies policy. You can, of course, opt out of these communications at any time.