A couple of weeks ago, I architected a Web Forms for Marketers (WFFM) solution that sends credit card information to a third-party credit card processor — via a custom form verification step — and blanks out sensitive credit card information before saving into the WFFM database.
Out of the box, WFFM will save credit card information in plain text to its database — yes, the data is hidden in the form report, although is saved to the database as plain text. This information being saved as plain text is a security risk.
One could create a solution similar to what I had done in my article discussing data encryption — albeit it might be in your best interest to avoid any headaches and potential security issues around saving and holding onto credit card information.
I can’t share the solution I built a couple of weeks ago — I built it for my current employer. I will, however, share a similar solution where I blank out the value of a field containing a social security number — a unique identifier of a citizen of the United States — of a person posing a question to the Internal Revenue Service (IRS) of the United States (yes, I chose this topic since tax season is upon us here in the US, and I loathe doing taxes :)).
First, I created a custom Web Forms for Marketers field for social security numbers. It is just a composite control containing three textboxes separated by labels containing hyphens:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Web.UI;
using System.Web.UI.WebControls;
using Sitecore.Form.Core.Attributes;
using Sitecore.Form.Core.Controls.Data;
using Sitecore.Form.Core.Visual;
using Sitecore.Form.Web.UI.Controls;
namespace Sitecore.Sandbox.WFFM.Controls
{
public class SocialSecurityNumber : InputControl
{
private const string Hyphen = "-";
private static readonly string baseCssClassName = "scfSingleLineTextBorder";
protected TextBox firstPart;
protected System.Web.UI.WebControls.Label firstMiddleSeparator;
protected TextBox middlePart;
protected System.Web.UI.WebControls.Label middleLastSeparator;
protected TextBox lastPart;
public SocialSecurityNumber() : this(HtmlTextWriterTag.Div)
{
firstPart = new TextBox();
firstMiddleSeparator = new System.Web.UI.WebControls.Label { Text = Hyphen };
middlePart = new TextBox();
middleLastSeparator = new System.Web.UI.WebControls.Label { Text = Hyphen };
lastPart = new TextBox();
}
public SocialSecurityNumber(HtmlTextWriterTag tag)
: base(tag)
{
this.CssClass = baseCssClassName;
}
protected override void OnInit(EventArgs e)
{
SetCssClasses();
SetTextBoxeWidths();
SetMaxLengths();
SetTextBoxModes();
AddChildControls();
}
private void SetCssClasses()
{
help.CssClass = "scfSingleLineTextUsefulInfo";
title.CssClass = "scfSingleLineTextLabel";
generalPanel.CssClass = "scfSingleLineGeneralPanel";
}
private void SetTextBoxeWidths()
{
firstPart.Style.Add("width", "40px");
middlePart.Style.Add("width", "35px");
lastPart.Style.Add("width", "50px");
}
private void SetMaxLengths()
{
firstPart.MaxLength = 3;
middlePart.MaxLength = 2;
lastPart.MaxLength = 4;
}
private void SetTextBoxModes()
{
firstPart.TextMode = TextBoxMode.SingleLine;
middlePart.TextMode = TextBoxMode.SingleLine;
lastPart.TextMode = TextBoxMode.SingleLine;
}
private void AddChildControls()
{
Controls.AddAt(0, generalPanel);
Controls.AddAt(0, title);
generalPanel.Controls.Add(firstPart);
generalPanel.Controls.Add(firstMiddleSeparator);
generalPanel.Controls.Add(middlePart);
generalPanel.Controls.Add(middleLastSeparator);
generalPanel.Controls.Add(lastPart);
generalPanel.Controls.Add(help);
}
public override string ID
{
get
{
return firstPart.ID;
}
set
{
title.ID = string.Concat(value, "_text");
firstPart.ID = value;
base.ID = string.Concat(value, "_scope");
title.AssociatedControlID = firstPart.ID;
}
}
public override ControlResult Result
{
get
{
return GetNewControlResult();
}
}
private ControlResult GetNewControlResult()
{
TrimAllTextBoxes();
return new ControlResult(ControlName, GetValue(), string.Empty);
}
private string GetValue()
{
bool hasValue = !string.IsNullOrEmpty(firstPart.Text)
&& !string.IsNullOrEmpty(middlePart.Text)
&& !string.IsNullOrEmpty(lastPart.Text);
if (hasValue)
{
return string.Concat(firstPart.Text, firstMiddleSeparator.Text, middlePart.Text, middleLastSeparator.Text, lastPart.Text);
}
return string.Empty;
}
private void TrimAllTextBoxes()
{
firstPart.Text = firstPart.Text.Trim();
middlePart.Text = middlePart.Text.Trim();
lastPart.Text = lastPart.Text.Trim();
}
}
}
I then registered this custom field in Sitecore. I added my custom field item under /sitecore/system/Modules/Web Forms for Marketers/Settings/Field Types/Custom:
Next, I decided to create a generic interface that defines objects that will excise or rip out values from a given object of a certain type, and returns a new object of another type — although both types could be the same:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Sitecore.Sandbox.Utilities.Excisors.Base
{
public interface IExcisor<T, U>
{
U Excise(T source);
}
}
My WFFM excisor will take in a collection of WFFM fields and return a new collection where the targeted field values — field values that I don’t want saved into the WFFM database — are set to the empty string.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Sitecore.Form.Core.Controls.Data;
using Sitecore.Form.Core.Client.Data.Submit;
namespace Sitecore.Sandbox.Utilities.Excisors.Base
{
public interface IWFFMFieldValuesExcisor : IExcisor<AdaptedResultList, AdaptedResultList>
{
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Sitecore.Diagnostics;
using Sitecore.Form.Core.Client.Data.Submit;
using Sitecore.Form.Core.Controls.Data;
using Sitecore.Sandbox.Utilities.Excisors.Base;
namespace Sitecore.Sandbox.Utilities.Excisors
{
public class WFFMFieldValuesExcisor : IWFFMFieldValuesExcisor
{
private IEnumerable<string> FieldNamesForExtraction { get; set; }
private WFFMFieldValuesExcisor(IEnumerable<string> fieldNamesForExtraction)
{
SetFieldNamesForExtraction(fieldNamesForExtraction);
}
private void SetFieldNamesForExtraction(IEnumerable<string> fieldNamesForExtraction)
{
Assert.ArgumentNotNull(fieldNamesForExtraction, "fieldNamesForExtraction");
FieldNamesForExtraction = fieldNamesForExtraction;
}
public AdaptedResultList Excise(AdaptedResultList fields)
{
if(fields == null || fields.Count() < 1)
{
return fields;
}
List<AdaptedControlResult> adaptedControlResults = new List<AdaptedControlResult>();
foreach(AdaptedControlResult field in fields)
{
adaptedControlResults.Add(GetExtractValueFieldIfApplicable(field));
}
return adaptedControlResults;
}
private AdaptedControlResult GetExtractValueFieldIfApplicable(AdaptedControlResult field)
{
if(ShouldExtractFieldValue(field))
{
return GetExtractedValueField(field);
}
return field;
}
private bool ShouldExtractFieldValue(ControlResult field)
{
return FieldNamesForExtraction.Contains(field.FieldName);
}
private static AdaptedControlResult GetExtractedValueField(ControlResult field)
{
return new AdaptedControlResult(CreateNewControlResultWithEmptyValue(field), true);
}
private static ControlResult CreateNewControlResultWithEmptyValue(ControlResult field)
{
ControlResult controlResult = new ControlResult(field.FieldName, string.Empty, field.Parameters);
controlResult.FieldID = field.FieldID;
return controlResult;
}
public static IWFFMFieldValuesExcisor CreateNewWFFMFieldValuesExcisor(IEnumerable<string> fieldNamesForExtraction)
{
return new WFFMFieldValuesExcisor(fieldNamesForExtraction);
}
}
}
For scalability purposes and to avoid hard-coding field name values, I put my targeted fields — I only have one at the moment — into a config file:
<?xml version="1.0" encoding="utf-8"?> <configuration xmlns:patch="http://www.sitecore.net/xmlconfig/"> <sitecore> <settings> <setting name="ExciseFields.SocialSecurityNumberField" value="Social Security Number" /> </settings> </sitecore> </configuration>
I then created a custom save to database action where I call my utility class to rip out my specified targeted fields, and pass that through onto the base save to database action class:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Sitecore.Configuration;
using Sitecore.Data;
using Sitecore.Form.Core.Client.Data.Submit;
using Sitecore.Form.Submit;
using Sitecore.Sandbox.Utilities.Excisors;
using Sitecore.Sandbox.Utilities.Excisors.Base;
namespace Sitecore.Sandbox.WFFM.Actions
{
public class ExciseFieldValuesThenSaveToDatabase : SaveToDatabase
{
private static readonly IEnumerable<string> FieldsToExcise = new string[] { Settings.GetSetting("ExciseFields.SocialSecurityNumberField") };
public override void Execute(ID formId, AdaptedResultList fields, object[] data)
{
base.Execute(formId, ExciseFields(fields), data);
}
private AdaptedResultList ExciseFields(AdaptedResultList fields)
{
IWFFMFieldValuesExcisor excisor = WFFMFieldValuesExcisor.CreateNewWFFMFieldValuesExcisor(FieldsToExcise);
return excisor.Excise(fields);
}
}
}
I created a new item to register my custom save to database action under /sitecore/system/Modules/Web Forms for Marketers/Settings/Actions/Save Actions:

I then created my form:
I set this form on a new page item, and published all of my changes above.
Now, it’s time to test this out. I navigated to my form, and filled it in as an irate tax payer might:
Looking at my form’s report, I see that the social security number was blanked out before being saved to the database:
A similar solution could also be used to add new field values into the collection of fields — values of fields that wouldn’t be available on the form, but should be displayed in the form report. An example might include a transaction ID or approval ID returned from a third-party credit card processor. Instead of ripping values, values would be inserted into the collection of fields, and then passed along to the base save to database action class.








[…] a previous article, I discussed how one could remove Web Forms for Marketers (WFFM) field values before saving form […]
Very interesting post! It seems there isn’t that much WFFM documentation out there. This is good to know in the event that I ever use WFFM to collect payment information. I’ll have to bookmark your site!
On a somewhat related note – do you know if WFFM offers an opt-out option, so that people who have already submitted a form can remove themselves from the related database as well? All ideas welcome.
Hi Jeff,
Would this be in the context of a newsletter signup form?
Mike
Great Post on WFFM.I plan to create a form with countries > states populated, so seeing how custom fields are created really helps me out!
I used this ref. example but I am not able to see the ” Social security field ” in The form .