Experience Editor bug: Multi-line text field deletes extra lines on save

Search for a command to run...

No comments yet. Be the first to comment.
Polly is great library for easily configuring different kinds of policies around code execution, be it retry, circuit breaker, caching or lots of other things. It's probably most often used around HTTP requests. If you haven't heard about it you defi...

This is part 2 of my series on unit testing in .NET (C#) and will be about my conventions regarding naming unit tests, how to structure them and which parts to test. And lastly I will help you increase readability of assertions and potential error me...

I feel like unit testing is very important and very beneficial in a lot of ways. It helps with not breaking existing functionality and thereby also making you more confident in changing code without fear of breaking important stuff. Often it also mak...

I've been working with Swagger through NSwag a lot recently and also needed to get it to work when hidden behind a reverse proxy - i.e. another service forwarding a request to the service exposing the Swagger UI. Some of the issues I've had along the...

The other day I was writing a unit test to verify what was supposed to happen when a Task would throw an exception when awaited. It wasn't as straight forward as I first assumed, but I quickly found this Stack Overflow post that helped me: https://st...

There is a pretty annoying bug when editing multi-line text fields through the Experience Editor which has been there since Sitecore 8.1 Update-1 and is not fixed as of 8.1 Update-3.
If you enter multiple lines into a multi-line text field when editing with the Experience Editor and save, then only the first line is actually saved – all extra lines are deleted.
This happens because of a greedy Regular Expression – <br.*/?> – in the class Sitecore.ExperienceEditor.WebUtility which removes all content between the first <br> and the last > in the content.
Sitecore is aware of the problem and has registered the bug, but they do not have a temporaray fix for it, as they usually have.
The WebUtility class is a static class only meant for internal use by Sitecore and as such is not replacable through config files as most other things are.
You can read a bit more about this issue including another multi-line bug here and here.
While WebUtility being static makes the issue a bit more problematic to fix globally it is not impossible to at least workaround the Experience Editor issue.
By creating a fixed version of the relevant method in the WebUtility class and replacing the CallServerSavePipeline processor with our own we can workaround the problem. We get a bit dependent on changes to these parts by Sitecore in future releases, but hopefully this will be fixed in the next release so we don’t need to do this fix.
We start out by creating our fixed version of the WebUtility.GetFields(Database, Dictionary<string, string>) method. I’ve highlighted the interesting part below.
public class WebUtility
{
public static IEnumerable<PageEditorField> GetFields(Database database, Dictionary<string, string> dictionaryForm)
{
Assert.ArgumentNotNull(dictionaryForm, "dictionaryForm");
var list = new List<PageEditorField>();
foreach (var key in dictionaryForm.Keys)
{
if (!key.StartsWith("fld_", StringComparison.InvariantCulture) &&
!key.StartsWith("flds_", StringComparison.InvariantCulture))
{
continue;
}
var text = key;
var str1 = dictionaryForm[key];
var indexOfDollarSign = text.IndexOf('$');
if (indexOfDollarSign >= 0)
{
text = StringUtil.Left(text, indexOfDollarSign);
}
var values = text.Split('_');
var itemId = ShortID.DecodeID(values[1]);
var fieldId = ShortID.DecodeID(values[2]);
var language = Language.Parse(values[3]);
var version = Sitecore.Data.Version.Parse(values[4]);
var revision = values[5];
var item = database.GetItem(itemId);
if (item == null)
continue;
var field = item.Fields[fieldId];
if (key.StartsWith("flds_", StringComparison.InvariantCulture))
{
str1 = (string)WebUtil.GetSessionValue(str1);
if (string.IsNullOrEmpty(str1))
str1 = field.Value;
}
switch (field.TypeKey)
{
case "html":
case "rich text":
str1 = str1.TrimEnd(' ');
break;
case "text":
str1 = StringUtil.RemoveTags(str1);
break;
// FIX START
case "multi-line text":
case "memo":
str1 = StringUtil.RemoveTags(new Regex("<br.*?/*>", RegexOptions.IgnoreCase)
.Replace(str1, "\r\n"));
break;
// FIX END
}
var pageEditorField = new PageEditorField()
{
ControlId = text,
FieldID = fieldId,
ItemID = itemId,
Language = language,
Revision = revision,
Value = str1,
Version = version
};
list.Add(pageEditorField);
}
return list;
}
}
Next up we need to create a new processor which gets called when you save through the Experience Editor.
public class CallServerSavePipeline : PipelineProcessorRequest<PageContext>
{
public override PipelineProcessorResponseValue ProcessRequest()
{
var responseValue = new PipelineProcessorResponseValue();
var pipeline = PipelineFactory.GetPipeline("saveUI");
pipeline.ID = ShortID.Encode(ID.NewID);
var saveArgs = RequestContext.GetSaveArgsPatched(); // FIX
using (new ClientDatabaseSwitcher(RequestContext.Item.Database))
{
pipeline.Start(saveArgs);
CacheManager.GetItemCache(RequestContext.Item.Database).Clear();
responseValue.AbortMessage = Translate.Text(saveArgs.Error);
return responseValue;
}
}
}
On the highlighted line we call a new extension method on PageContext, which is just a fixed version of the original method GetSaveArgs() except we call our new WebUtility.GetFields() method instead.
public static class PageContextExtensions
{
public static SaveArgs GetSaveArgsPatched(this PageContext pageContext)
{
var args = PipelineUtil.GenerateSaveArgs(pageContext.Item,
WebUtility.GetFields(pageContext.Item.Database, pageContext.FieldValues), // FIX
"",
pageContext.LayoutSource,
"",
Sitecore.ExperienceEditor.WebUtility.GetCurrentLayoutFieldId().ToString());
args.HasSheerUI = false;
new ParseXml().Process(args);
return args;
}
}
At last all we need to do is to replace the processor with our new one.
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/" xmlns:set="http://www.sitecore.net/xmlconfig/set/">
<sitecore>
<sitecore.experienceeditor.speak.requests>
<request name="ExperienceEditor.Save.CallServerSavePipeline"
set:type="YourAsembly.CallServerSavePipeline, YourAssembly"/>
</sitecore.experienceeditor.speak.requests>
</sitecore>
</configuration>
That’s it! Now your multi-line text fields will work correctly when editing them in the Experience Editor. Hopefully Sitecore will fix this soon in the core product.