Sitecore: WFFM Send Email Message does not remove field tokens when there is no Field Data

I ran into an issue with our WFFM (8.1 rev. 160523) implementation on Sitecore XP 8.1 where the email sent by WFFM “Send Message Action” does not remove un-expanded tokens from the email body. This scenario comes up when you have optional fields in your form and the user does not supply values for the optional fields. The default implementation seems to have a bug and I couldn’t find an option on the form or at the field level to change this behavior.

Thinking that may be the Field Ids in the form got messed up, I decided to create a test form from scratch with just two fields TEST FIELD 1 and TEST FIELD 2 (Optional).

Form Creation:

Send Email Editor:

Public View:

Output Email Body:
TEST FIELD 1: GIRISH
TEST FIELD 2: [TEST FIELD 2]

We basically don’t want [TEST FIELD 2] to show up at all. If you look at the html it still contains the label id token.

1
[<label id="{42435BFE-8225-445A-3568-ACBF16E6CD862}">TEST FIELD 2</label>]

Solution:
I ended up implementing a custom pipeline processor for processMessage. Basically, just dotpeeked the Sitecore.Forms.Core.Pipelines.ProcessMessage class in the Sitecore.Forms.Core.dll assembly and copied the existing code add then added the code to remove un-expanded tokens at the end. Finally, I used a patch file to hook my processor in to the pipeline. Don’t forget to publish the patch file to the App_Config/Include folder and also ensure that it’s loaded after the Sitecore.Forms.config file.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
public class ProcessMessage
{
    private readonly string srcReplacer;
    private readonly string shortHrefReplacer;
    private readonly string shortHrefMediaReplacer;
    private readonly string hrefReplacer;

    public IItemRepository ItemRepository { get; set; }

    public IFieldProvider FieldProvider { get; set; }

    public ProcessMessage()
        : this(DependenciesManager.WebUtil)
    {
    }

    public ProcessMessage(IWebUtil webUtil)
    {
        Assert.IsNotNull((object)webUtil, "webUtil");
        this.srcReplacer = string.Join(string.Empty, new string[3] { "src="", webUtil.GetServerUrl(), "/~" });
        this.shortHrefReplacer = string.Join(string.Empty, new string[3] { "
href=\"", webUtil.GetServerUrl(), "/" });
        this.shortHrefMediaReplacer = string.Join(string.Empty, new string[3] { "href=\"", webUtil.GetServerUrl(), "/~/" });
        this.hrefReplacer = this.shortHrefReplacer + "~";
    }

    public void ExpandTokens(ProcessMessageArgs args)
    {
        Assert.IsNotNull((object)this.ItemRepository, "ItemRepository");
        Assert.IsNotNull((object)this.FieldProvider, "FieldProvider");

        foreach (AdaptedControlResult field in args.Fields)
        {
            IFieldItem fieldItem = this.ItemRepository.CreateFieldItem(this.ItemRepository.GetItem(field.FieldID));
            var str1 = field.Value;
            var str2 = "";
            try
            {
                str2 = this.FieldProvider.GetAdaptedValue(field.FieldID, str1);
            }
            catch(Exception)
            {
                str2 = str1;
            }


            if(string.IsNullOrWhiteSpace(str2))
            {
                 Sitecore.Diagnostics.Log.Error("WFFM Field Error Field Name: " + field.FieldName + "; FieldID:" +     field.FieldID, this);
            }

            str2 = str2.Replace("src="/sitecore/shell/themes/standard/-", this.srcReplacer)
                        .Replace("
href="/sitecore/shell/themes/standard/-", this.hrefReplacer)
                        .Replace("on\\w*=".*?"", string.Empty);

            if (args.MessageType == MessageType.Sms)
            {
                args.Mail.Replace("[{0}]".FormatWith((object)fieldItem.FieldDisplayName), str2);
                args.Mail.Replace("[{0}]".FormatWith((object)fieldItem.Name), str2);
            }
            else
            {
                if (!string.IsNullOrEmpty(field.Parameters) && args.IsBodyHtml)
                {
                    if (field.Parameters.StartsWith("multipleline"))
                        str2 = str2.Replace(Environment.NewLine, "<br/>");
                    if (field.Parameters.StartsWith("secure") && field.Parameters.Contains("<schidden>"))
                        str2 = Regex.Replace(str2, "\\d", "*");
                }
                string str3 = Regex.Replace(args.Mail.ToString(), "\\[<label id="" + (object)fieldItem.ID + "">[^<]+?</label>]",     str2).Replace(fieldItem.ID.ToString(), str2);
                args.Mail.Clear().Append(str3);
            }
            args.From = args.From.Replace("[" + (object)fieldItem.ID + "]", str2);
            args.From = args.From.Replace(fieldItem.ID.ToString(), str2);
            args.To.Replace(string.Join(string.Empty, new string[3] { "[", fieldItem.ID.ToString(), "]" }), str2);
            args.To.Replace(string.Join(string.Empty, new string[1] { fieldItem.ID.ToString()}), str2);
            args.CC.Replace(string.Join(string.Empty, new string[3] { "[", fieldItem.ID.ToString(), "]" }), str2);
            args.CC.Replace(string.Join(string.Empty, new string[1] { fieldItem.ID.ToString()}), str2);
            args.Subject.Replace(string.Join(string.Empty, new string[3] { "[", fieldItem.ID.ToString(), "]" }), str2);
            args.From = args.From.Replace("[" + fieldItem.FieldDisplayName + "]", str2);
            args.To.Replace(string.Join(string.Empty, new string[3] { "[", fieldItem.FieldDisplayName, "]" }), str2);
            args.CC.Replace(string.Join(string.Empty, new string[3] { "[", fieldItem.FieldDisplayName, "]" }), str2);
            args.Subject.Replace(string.Join(string.Empty, new string[3] { "[", fieldItem.FieldDisplayName, "]"}), str2);
            args.From = args.From.Replace("[" + field.FieldName + "]", str2);
            args.To.Replace(string.Join(string.Empty, new string[3] { "[", field.FieldName, "]" }), str2);
            args.CC.Replace(string.Join(string.Empty, new string[3] { "[", field.FieldName, "]" }), str2);
            args.Subject.Replace(string.Join(string.Empty, new string[3] { "[", field.FieldName, "]" }), str2);

        }

        //remove unexpanded tokens
        var str4 = Regex.Replace(args.Mail.ToString(), "\\[<label id=".*">[^<]+?</label>]", "");
        args.Mail.Clear().Append(str4);
    }
}

Patch File:

1
2
3
4
5
6
7
8
9
10
11
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
<sitecore>
    <pipelines>
       <processMessage>
             <processor type="Client.Sitecore.Custom.Forms.ProcessMessage, Client.Sitecore.Custom.Forms" method="ExpandTokens" patch:instead="*[@method='ExpandTokens']">
                 <ItemRepository ref="/sitecore/wffm/itemRepository"/>
                 <FieldProvider ref="/sitecore/wffm/fieldProvider"/>
             </processor>
       </processMessage>
    </pipelines>
</sitecore>

Sitecore: mystery event that never fires (template:updated)

I needed to watch changes happening to a template so I could know about any newly added fields. These changes are triggered from an external PIM system which changes the Product or Resource templates when a new field is added to a Product for example. I’ll spare you the details of why I needed to know when this happened but I thought Sitecore has to have a way to handle this. I had used the item:saved event to handle item updates in the past but I had never had to watch for changes to a template. I opened up Sitecore.config in a text editor and started looking at the list of events in the section. Somewhere in the middle of this section, after the usual item related events, I noticed the “template:updated” event with no handler specified. I was sure that all I needed to do now was to implement an event handler in a class and hook up the handler here (of course using a patch file). But this event never gets fired for some reason. Doubting my patch files expertise I even tried directly editing the Sitecore.config but no luck. Then I though may be the template:update event is supposed to fire when an item’s template is changed but we have the item:templateChanged event which works as expected.

I could find no documentation on this event anywhere. Looking at the Event class in the Sitecore.Events namespace of the Sitecore.Kernel.dll (8.1) assembly under DotPeek I could not find any reference to the “template:updated” event, but I did see plenty of the item:* events (item:saved, item:renamed, item:deleted, etc.) in there. It has been there in the config file since Sitecore 6.6 at least. So why is this event in the Sitecore.config or web.config for 6.6? Let me know in the comments if anybody knows how to make this work or why its in the config file but not supported in the code at all.

Here is what I was using to test it and at the bottom I have a solution to work around this problem.

Simple Event Handler:

1
2
3
4
5
6
7
8
public class TemplateItemUpdateHandler
{
      public void OnTemplateUpdate(object sender, EventArgs args)
      {
          var item = Event.ExtractParameter(args, 0) as Item;
          Sitecore.Diagnotics.Log.Info("Inside the Item Update event", this);
      }
}

Patch File:

1
2
3
4
5
6
7
8
9
10
11
12
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <events>
      <event  name="template:updated">
        <patch:delete />
      </event>
      <event name="template:updated">
        <handler type="ClientName.Sitecore.Custom.Events.EventHandlers.TemplateItemUpdateHandler, ClientName.Sitecore.Custom.Events" method="OnTemplateUpdate" />
      </event>
    </events>
  </sitecore>
</configuration>

ShowConfig.aspx Output:

Test Template Change

Solution:
I ended up using the item:saved event to handle this but had to do some less than ideal Guid checking in the code. When a new field is added to the Product template the item:saved event is fired and I basically check the existence of Product Template in up the tree. The drawback of this that my handler code now runs every time any item is saved with a possible performance hit.

1
2
3
4
5
6
7
8
9
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <events>
      <event name="item:saved">
        <handler type="ClientName.Sitecore.Custom.Events.EventHandlers.TemplateItemUpdateHandler, ClientName.Sitecore.Custom.Events" method="OnTemplateUpdate"/>
      </event>
    </events>
  </sitecore>
</configuration>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
        public void OnTemplateUpdate(object sender, EventArgs args)
        {
            var item = Event.ExtractParameter(args, 0) as Item;

            if (item != null
                && item.Database == SitecoreUtility.GetMasterDatabase()
                && item.TemplateID.Guid == TemplateIds.TemplateFieldTemplateId
                && item.Parent != null && item.Parent.Parent != null
                && item.Parent.Parent.ID.Guid == TemplateIds.ProductTemplateId)
            {
                using (new SecurityDisabler())
                {
                    createNewPIMFieldMappings(item);
                }
            }
        }

Pages that mention this event:
Alan Coates – Sitecore/.NET blog: Sitecore Save Event