-
Notifications
You must be signed in to change notification settings - Fork 2.6k
/
RteMacroRenderingValueConverter.cs
134 lines (116 loc) · 5.24 KB
/
RteMacroRenderingValueConverter.cs
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
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
using System.Text;
using Umbraco.Core;
using Umbraco.Core.Macros;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Models.PublishedContent;
using Umbraco.Core.PropertyEditors.ValueConverters;
using Umbraco.Web.Templates;
using System.Linq;
using HtmlAgilityPack;
using Umbraco.Core.Cache;
using Umbraco.Core.Services;
using Umbraco.Web.Composing;
using Umbraco.Web.Macros;
using System.Web;
namespace Umbraco.Web.PropertyEditors.ValueConverters
{
/// <summary>
/// A value converter for TinyMCE that will ensure any macro content is rendered properly even when
/// used dynamically.
/// </summary>
[DefaultPropertyValueConverter]
public class RteMacroRenderingValueConverter : TinyMceValueConverter
{
private readonly IUmbracoContextAccessor _umbracoContextAccessor;
private readonly IMacroRenderer _macroRenderer;
public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType)
{
// because that version of RTE converter parses {locallink} and executes macros, its value has
// to be cached at the published snapshot level, because we have no idea what the macros may depend on actually.
return PropertyCacheLevel.Snapshot;
}
public RteMacroRenderingValueConverter(IUmbracoContextAccessor umbracoContextAccessor, IMacroRenderer macroRenderer)
{
_umbracoContextAccessor = umbracoContextAccessor;
_macroRenderer = macroRenderer;
}
// NOT thread-safe over a request because it modifies the
// global UmbracoContext.Current.InPreviewMode status. So it
// should never execute in // over the same UmbracoContext with
// different preview modes.
private string RenderRteMacros(string source, bool preview)
{
var umbracoContext = _umbracoContextAccessor.UmbracoContext;
using (umbracoContext.ForcedPreview(preview)) // force for macro rendering
{
var sb = new StringBuilder();
MacroTagParser.ParseMacros(
source,
//callback for when text block is found
textBlock => sb.Append(textBlock),
//callback for when macro syntax is found
(macroAlias, macroAttributes) => sb.Append(_macroRenderer.Render(
macroAlias,
umbracoContext.PublishedRequest?.PublishedContent,
//needs to be explicitly casted to Dictionary<string, object>
macroAttributes.ConvertTo(x => (string)x, x => x)).GetAsText()));
return sb.ToString();
}
}
public override object ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview)
{
var converted = Convert(inter, preview);
return new HtmlString(converted == null ? string.Empty : converted);
}
private string Convert(object source, bool preview)
{
if (source == null)
{
return null;
}
var sourceString = source.ToString();
// ensures string is parsed for {localLink} and urls and media are resolved correctly
sourceString = TemplateUtilities.ParseInternalLinks(sourceString, preview, Current.UmbracoContext);
sourceString = TemplateUtilities.ResolveUrlsFromTextString(sourceString);
sourceString = TemplateUtilities.ResolveMediaFromTextString(sourceString);
// ensure string is parsed for macros and macros are executed correctly
sourceString = RenderRteMacros(sourceString, preview);
// find and remove the rel attributes used in the Umbraco UI from img tags
var doc = new HtmlDocument();
doc.LoadHtml(sourceString);
if (doc.ParseErrors.Any() == false && doc.DocumentNode != null)
{
// Find all images with rel attribute
var imgNodes = doc.DocumentNode.SelectNodes("//img[@rel]");
var modified = false;
if (imgNodes != null)
{
foreach (var img in imgNodes)
{
var nodeId = img.GetAttributeValue("rel", string.Empty);
if (int.TryParse(nodeId, out _))
{
img.Attributes.Remove("rel");
modified = true;
}
}
}
// Find all a and img tags with a data-udi attribute
var dataUdiNodes = doc.DocumentNode.SelectNodes("(//a|//img)[@data-udi]");
if (dataUdiNodes != null)
{
foreach (var node in dataUdiNodes)
{
node.Attributes.Remove("data-udi");
modified = true;
}
}
if (modified)
{
return doc.DocumentNode.OuterHtml;
}
}
return sourceString;
}
}
}