Sitecore WFFM - Fields in email are not replaced

Sitecore WFFM - Fields in email are not replaced

I recently ran into an issue with emails sent from Web Forms For Marketers. Fields weren’t being replaced with the values entered in the form by users, but if I made a new web form it was working fine.

When editing the message for the Save Email Message save action, you insert the fields from the web form by selecting them in the Insert Field dropdown. When you do that the field is inserted into the rich text field like [Field Name]. What you don’t see is the actual generated HTML:

[<label id="{5C29A68B-EA75-404C-84EA-2378F5A39189}">Field Name</label>]

When the message is being sent Sitecore is then replacing these with entered values. This is done in the processMessage pipeline, specifically the ExpandTokens method:

<pipelines>
  <!-- ... -->
  <processMessage>
    <processor type="Sitecore.Form.Core.Pipelines.ProcessMessage.ProcessMessage, Sitecore.Forms.Core" 
               method="ExpandLinks" />
    <!-- This is the one -->
    <processor type="Sitecore.Form.Core.Pipelines.ProcessMessage.ProcessMessage, Sitecore.Forms.Core" 
               method="ExpandTokens" />
    <processor type="Sitecore.Form.Core.Pipelines.ProcessMessage.ProcessMessage, Sitecore.Forms.Core" 
               method="AddHostToItemLink" />
    <processor type="Sitecore.Form.Core.Pipelines.ProcessMessage.ProcessMessage, Sitecore.Forms.Core" 
               method="AddHostToMediaItem" />
    <processor type="Sitecore.Form.Core.Pipelines.ProcessMessage.ProcessMessage, Sitecore.Forms.Core" 
               method="AddAttachments" />
    <processor type="Sitecore.Form.Core.Pipelines.ProcessMessage.ProcessMessage, Sitecore.Forms.Core" 
               method="BuildToFromRecipient" />
    <processor type="Sitecore.Form.Core.Pipelines.ProcessMessage.ProcessMessage, Sitecore.Forms.Core" 
               method="SendEmail" />
  </processMessage>
  <!-- ... -->
</pipelines>

If you decompile Sitecore.Forms.Core.dll you will see how the replacement is actually done.

// Simplified a little bit
message.Replace(string.Format("[<label id=\"{0}\">{1}</label>]", field.ID, field.Title), enteredValue);
message.Replace(string.Format("[<label id=\"{0}\">{1}</label>]", field.ID, field.Name), enteredValue);

It is basically doing a simple string.Replace(...) – once using the field title and once using the field name. As you might have noticed, this requires the HTML to be untouched since added by using the Insert Field dropdown.

If you want to see the final HTML of your email message you will need to enable** raw values** (from the View tab) and then take the value from the Save Actions field and HTML decode it twice. The message HTML is then enclosed in the <mail> tag. (If there is an easier way, please let me know!)

What I noticed was that for some reason my HTML looked like this:

[<label id=\"{BABBA6E7-32B2-407F-8D9E-296D176056F2}\" style=\"font-weight: lighter;\">
    Field Name
</label><span style=\"font-weight: lighter;\">]

Some <span> tags and style attributes had somehow gotten in there and that is what was causing the issue.

Solution

One solution would be to remake the web forms with the invalid HTML but as we didn’t know how this happened and couldn’t be sure it wouldn’t happen again we needed another solution.

With Sitecore’s pipelines it is rather easy to replace default functionality, so that ended up being our solution – replacing the ExpandTokens part of the processMessage pipeline.

using System;
using System.Text.RegularExpressions;
using Sitecore.Form.Core.Configuration;
using Sitecore.Form.Core.Controls.Data;
using Sitecore.Form.Core.Pipelines.ProcessMessage;
using Sitecore.Form.Core.Utility;
using Sitecore.Forms.Core.Data;
using Sitecore.StringExtensions;

namespace YourCompany.Pipelines.ProcessMessage
{
    public class ProcessMessage
    {
        private static readonly string srcReplacer = string.Join("", "src=\"", Sitecore.Web.WebUtil.GetServerUrl(), "/~");
        private static readonly string shortHrefReplacer = string.Join("", "href=\"", Sitecore.Web.WebUtil.GetServerUrl(), "/");
        private static readonly string hrefReplacer = shortHrefReplacer + "~";

        public void ExpandTokens(ProcessMessageArgs args)
        {
            // FIX 1 START
            var mail = args.Mail.ToString();

            mail = Regex.Replace(mail, "<span .*?>(.*?)</span>", "$1");

            args.Mail.Clear();
            args.Mail.Append(mail);
            // FIX 1 END

            foreach (AdaptedControlResult adaptedControlResult in args.Fields)
            {
                var field = new FieldItem(StaticSettings.ContextDatabase.GetItem(adaptedControlResult.FieldID));
                var value = adaptedControlResult.Value;
                var adaptedValue = FieldReflectionUtil.GetAdaptedValue(field, value);
                adaptedValue = Regex.Replace(adaptedValue, "src=\"/sitecore/shell/themes/standard/~", srcReplacer);
                adaptedValue = Regex.Replace(adaptedValue, "href=\"/sitecore/shell/themes/standard/~",  hrefReplacer);
                adaptedValue = Regex.Replace(adaptedValue, "on\\w*=\".*?\"", string.Empty);

                if (args.MessageType == MessageType.SMS)
                {
                    args.Mail.Replace("[{0}]".FormatWith(field.FieldDisplayName), adaptedValue);
                    args.Mail.Replace("[{0}]".FormatWith(field.Name), adaptedValue);
                }
                else
                {
                    if (!string.IsNullOrEmpty(adaptedControlResult.Parameters) &&
                        adaptedControlResult.Parameters.StartsWith("multipleline") && args.IsBodyHtml)
                    {
                        adaptedValue = adaptedValue.Replace(Environment.NewLine, "<br/>");
                    }

                    // FIX 2 START
                    args.Mail.Replace("[<label id=\"{0}\">{1}</label>]".FormatWith(field.ID, field.Title), adaptedValue);
                    args.Mail.Replace("[<label id=\"{0}\">{1}</label>]".FormatWith(field.ID, field.Name), adaptedValue);

                    mail = args.Mail.ToString();
                    mail = Regex.Replace(mail, @"\[.*?<label id=""{0}"".*?>.*?<\/label>.*?\]".FormatWith(field.ID), adaptedValue);

                    args.Mail.Clear();
                    args.Mail.Append(mail);
                    // FIX 2 END
                }

                args.From = args.From.Replace("[" + field.ID.ToString() + "]", value);
                args.To.Replace(string.Join("", "[", field.ID.ToString(), "]"), value);
                args.CC.Replace(string.Join("", "[", field.ID.ToString(), "]"), value);
                args.Subject.Replace(string.Join("", "[", field.ID.ToString(), "]"), value);

                args.From = args.From.Replace("[" + field.FieldDisplayName + "]", value);
                args.To.Replace(string.Join("", "[", field.FieldDisplayName, "]"), value);
                args.CC.Replace(string.Join("", "[", field.FieldDisplayName, "]"), value);
                args.Subject.Replace(string.Join("", "[", field.FieldDisplayName, "]"), value);

                args.From = args.From.Replace("[" + adaptedControlResult.FieldName + "]", value);
                args.To.Replace(string.Join("", "[", adaptedControlResult.FieldName, "]"), value);
                args.CC.Replace(string.Join("", "[", adaptedControlResult.FieldName, "]"), value);
                args.Subject.Replace(string.Join("", "[", adaptedControlResult.FieldName, "]"), value);
            }
        }
    }
}

I have marked the lines that differ from Sitecore’s default implementation. First I’m removing the <span> tags completely. Next I’m replacing the inserted <label> tags with Regex instead of a simple string.Replace(...) as it will be more tolerant to changes in the HTML.

To replace the default implementation with our own, we just create a config file in the Include folder with the following content:

<?xml version="1.0" encoding="utf-8"?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/" 
               xmlns:x="http://www.sitecore.net/xmlconfig/" xmlns:set="http://www.sitecore.net/xmlconfig/set/">
<sitecore>
    <pipelines>
      <processMessage>
        <processor method="ExpandTokens" 
                   set:type="YourCompany.Pipelines.ProcessMessage.ProcessMessage, YourAssembly"/>
      </processMessage>
    </pipelines>
  </sitecore>
</configuration>

You have to make sure this config file is listed after Sitecore.Forms.config (alphabetically) or by just dropping it in a subfolder like App_Config/Include/YourCompany.