From 4eb54e4ed941b26376546233402721d3415c3f31 Mon Sep 17 00:00:00 2001
From: Marc Moriyamas
Date: Mon, 10 Aug 2020 16:08:34 +0100
Subject: [PATCH 1/9] Add Parser to find Udi's in Macro Parameters in Macros in
RTE + Grid
---
.../Templates/HtmlMacroParameterParser.cs | 108 ++++++++++++++++++
src/Umbraco.Web/Umbraco.Web.csproj | 1 +
2 files changed, 109 insertions(+)
create mode 100644 src/Umbraco.Web/Templates/HtmlMacroParameterParser.cs
diff --git a/src/Umbraco.Web/Templates/HtmlMacroParameterParser.cs b/src/Umbraco.Web/Templates/HtmlMacroParameterParser.cs
new file mode 100644
index 000000000000..c944ab45024d
--- /dev/null
+++ b/src/Umbraco.Web/Templates/HtmlMacroParameterParser.cs
@@ -0,0 +1,108 @@
+using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text.RegularExpressions;
+using Umbraco.Core;
+using Umbraco.Core.Models;
+using Umbraco.Web.Macros;
+
+namespace Umbraco.Web.Templates
+{
+
+ public sealed class HtmlMacroParameterParser
+ {
+ public HtmlMacroParameterParser()
+ {
+
+ }
+
+ ///
+ /// Parses out media UDIs from an html string based on embedded macro parameter values
+ ///
+ ///
+ ///
+ public IEnumerable FindUdisFromMacroParameters(string text)
+ {
+ // we already have a method for finding Macros within a Rich Text Editor using regexes: Umbraco.Web.Macros.MacroTagParser
+ // it searches for 'text blocks' which I think are the old way of embedding macros in a template, to allow inline code - we're not interested in those in V8
+ // but we are interested in the foundMacros, and in particular their parameters and values
+ // (aside TODO: at somepoint we might want to track Macro usage?)
+ // ideally we'd determine each type of parameter editor and call it's GetReferences implementation
+ // however this information isn't stored in the embedded macro markup and would involve 'looking up' types for each parameter
+ // (or we could change the macros markup to include details of the editor)
+ // but pragmatically we don't really care what type of property editor it is if it stores media udis
+ // we want to track media udis wherever they are used regardless of how they are picked?
+ // so here we parse the parameters and check to see if they are in the format umb://media
+ // see also https://github.com/umbraco/Umbraco-CMS/pull/8388
+
+ List macroParameterValues = new List();
+ MacroTagParser.ParseMacros(text, textblock => { }, (macroAlias, macroAttributes) => macroParameterValues.AddRange(macroAttributes.Values.ToList().Where(f=>f.StartsWith("umb://" + Constants.UdiEntityType.Media + "/"))));
+
+ if (macroParameterValues.Any())
+ {
+ // we have media udis! - but we could also have a csv of multiple media udis items
+ foreach (var macroParameterValue in macroParameterValues.Distinct())
+ {
+ string[] potentialUdis = macroParameterValue.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
+ foreach (var potentialUdi in potentialUdis.Distinct())
+ {
+ if(Udi.TryParse(potentialUdi, out var udi))
+ {
+ yield return udi;
+ }
+ }
+ }
+ }
+ else
+ {
+ yield break;
+ }
+ }
+ ///
+ /// Parses out media UDIs from Macro Grid Control Parameters
+ ///
+ ///
+ public IEnumerable FindUdisFromGridControlMacroParameters(IEnumerable macroValues)
+ {
+ List macroParameterValues = new List();
+ foreach (var macroValue in macroValues)
+ {
+ //deserialise JSON of Macro Grid Control to a class
+ var gridMacro = macroValue.Value.ToObject();
+ //collect any macro parameters that contain the media udi format
+ if (gridMacro != null && gridMacro.MacroParameters!=null && gridMacro.MacroParameters.Any())
+ {
+ macroParameterValues.AddRange(gridMacro.MacroParameters.Values.ToList().Where(f => f.Contains("umb://" + Constants.UdiEntityType.Media + "/")));
+ }
+ }
+ if (macroParameterValues.Any())
+ {
+ // we have media udis! - but we could also have a csv of multiple items
+ foreach (var macroParameterValue in macroParameterValues.Distinct())
+ {
+ string[] potentialUdis = macroParameterValue.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
+ foreach (var potentialUdi in potentialUdis.Distinct())
+ {
+ if (Udi.TryParse(potentialUdi, out var udi))
+ {
+ yield return udi;
+ }
+ }
+ }
+ }
+ else
+ {
+ yield break;
+ }
+ }
+ // poco class to deserialise the Json for a Macro Control
+ private class GridMacro
+ {
+ [JsonProperty("macroAlias")]
+ public string MacroAlias { get; set; }
+ [JsonProperty("macroParamsDictionary")]
+ public Dictionary MacroParameters { get; set; }
+ }
+ }
+}
diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj
index 2ca7d6d7ae65..b599e606cabf 100755
--- a/src/Umbraco.Web/Umbraco.Web.csproj
+++ b/src/Umbraco.Web/Umbraco.Web.csproj
@@ -272,6 +272,7 @@
+
From bf5b356b39464f4e4a038accb0217f3498b7f68e Mon Sep 17 00:00:00 2001
From: Marc Moriyamas
Date: Mon, 10 Aug 2020 16:09:06 +0100
Subject: [PATCH 2/9] Register HtmlMacroParameterParser with DI
---
src/Umbraco.Web/Runtime/WebInitialComposer.cs | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/Umbraco.Web/Runtime/WebInitialComposer.cs b/src/Umbraco.Web/Runtime/WebInitialComposer.cs
index 60521e6d907b..b0172e16a443 100644
--- a/src/Umbraco.Web/Runtime/WebInitialComposer.cs
+++ b/src/Umbraco.Web/Runtime/WebInitialComposer.cs
@@ -109,6 +109,7 @@ public override void Compose(Composition composition)
composition.RegisterUnique();
composition.RegisterUnique();
composition.RegisterUnique();
+ composition.RegisterUnique();
composition.RegisterUnique();
// register the umbraco helper - this is Transient! very important!
From 5a0bc3dcbce0cb2e83cba5b500eba111508f6d90 Mon Sep 17 00:00:00 2001
From: Marc Moriyamas
Date: Mon, 10 Aug 2020 16:10:06 +0100
Subject: [PATCH 3/9] Some Tests to cover the parsing of text/json for Macros
and their parameters
---
.../HtmlMacroParameterParserTests.cs | 154 ++++++++++++++++++
src/Umbraco.Tests/Umbraco.Tests.csproj | 1 +
2 files changed, 155 insertions(+)
create mode 100644 src/Umbraco.Tests/Templates/HtmlMacroParameterParserTests.cs
diff --git a/src/Umbraco.Tests/Templates/HtmlMacroParameterParserTests.cs b/src/Umbraco.Tests/Templates/HtmlMacroParameterParserTests.cs
new file mode 100644
index 000000000000..39a48bbe4c9e
--- /dev/null
+++ b/src/Umbraco.Tests/Templates/HtmlMacroParameterParserTests.cs
@@ -0,0 +1,154 @@
+using Umbraco.Core.Logging;
+using Moq;
+using NUnit.Framework;
+using Umbraco.Tests.Testing.Objects.Accessors;
+using Umbraco.Web.Templates;
+using Umbraco.Web;
+using Umbraco.Core.Models.PublishedContent;
+using Umbraco.Web.Routing;
+using Umbraco.Tests.Testing.Objects;
+using System.Web;
+using System;
+using System.Linq;
+using Umbraco.Core.Models;
+using Umbraco.Core;
+using System.Diagnostics;
+using Newtonsoft.Json.Linq;
+using System.Collections.Generic;
+
+namespace Umbraco.Tests.Templates
+{
+ [TestFixture]
+ public class HtmlMacroParameterParserTests
+ {
+ [Test]
+ public void Returns_Udis_From_Single_MediaPicker_Macro_Parameters_In_Macros_In_Html()
+ {
+ //Two macros, one single parameter, single Media Picker, one multiple paramters of single media picker
+ var input = @"This is some normal text before the macro
+
+ This is a paragraph after the macro and before the next
+
+ some more text
";
+
+ var macroParameterParser = new HtmlMacroParameterParser();
+
+ var result = macroParameterParser.FindUdisFromMacroParameters(input).ToList();
+ Assert.AreEqual(3, result.Count);
+ Assert.AreEqual(Udi.Parse("umb://media/eee91c05b2e84031a056dcd7f28eff89"), result[0]);
+ Assert.AreEqual(Udi.Parse("umb://media/fa763e0d0ceb408c8720365d57e06e32"), result[1]);
+ Assert.AreEqual(Udi.Parse("umb://media/90ba0d3dba6e4c9fa1953db78352ba73"), result[2]);
+ }
+ [Test]
+ public void Returns_Empty_With_From_Single_MediaPicker_With_No_Macro_Parameters_In_Macros_In_Html()
+ {
+ //Two macros, one single parameter, single Media Picker, one multiple paramters of single media picker
+ var input = @"This is some normal text before the macro
+
+ This is a paragraph after the macro and before the next
+ Some other value
"" />
+ some more text
";
+
+ var macroParameterParser = new HtmlMacroParameterParser();
+
+ var result = macroParameterParser.FindUdisFromMacroParameters(input).ToList();
+ Assert.AreEqual(0, result.Count);
+ }
+ //NB: When multiple media pickers store udis instead of ints! - see https://github.com/umbraco/Umbraco-CMS/pull/8388
+ [Test]
+ public void Returns_Empty_When_No_Macros_In_Html()
+ {
+ //Two macros, one single parameter, single Media Picker, one multiple paramters of single media picker
+ var input = @"This is some normal text before the macro
+ This is a paragraph after the macro and before the next
+ some more text
";
+
+ var macroParameterParser = new HtmlMacroParameterParser();
+
+ var result = macroParameterParser.FindUdisFromMacroParameters(input).ToList();
+ Assert.AreEqual(0, result.Count);
+ }
+ [Test]
+ public void Returns_Udis_From_Multiple_MediaPicker_Macro_Parameters_In_Macros_In_Html()
+ {
+ //Two macros, one single parameter, single Media Picker, one multiple paramters of single media picker
+ var input = @"This is some normal text before the macro
+
+ This is a paragraph after the macro
";
+
+ var macroParameterParser = new HtmlMacroParameterParser();
+
+ var result = macroParameterParser.FindUdisFromMacroParameters(input).ToList();
+ Assert.AreEqual(3, result.Count);
+ Assert.AreEqual(Udi.Parse("umb://media/eee91c05b2e84031a056dcd7f28eff89"), result[0]);
+ Assert.AreEqual(Udi.Parse("umb://media/fa763e0d0ceb408c8720365d57e06e32"), result[1]);
+ Assert.AreEqual(Udi.Parse("umb://media/bb763e0d0ceb408c8720365d57e06444"), result[2]);
+ }
+ [Test]
+ public void Returns_Udis_From_Single_MediaPicker_Macro_Parameters_In_Grid_Macros()
+ {
+ // create a list of GridValue.GridControls with Editor GridEditor alias macro
+ List gridControls = new List();
+
+ // single media picker macro parameter
+ var macroGridControl = GetMacroGridControl(@"{ ""macroAlias"": ""SingleMediaPicker"", ""macroParamsDictionary"": { ""singleMediaPicker"": ""umb://media/90ba0d3dba6e4c9fa1953db78352ba73"" }}");
+ gridControls.Add(macroGridControl);
+
+ var macroParameterParser = new HtmlMacroParameterParser();
+
+ var result = macroParameterParser.FindUdisFromGridControlMacroParameters(gridControls).ToList();
+ Assert.AreEqual(1, result.Count);
+ Assert.AreEqual(Udi.Parse("umb://media/90ba0d3dba6e4c9fa1953db78352ba73"), result[0]);
+
+ }
+ [Test]
+ public void Returns_Empty_From_Single_MediaPicker_With_No_Macro_Parameters_In_Grid_Macros()
+ {
+ // create a list of GridValue.GridControls with Editor GridEditor alias macro
+ List gridControls = new List();
+
+ // single media picker macro parameter
+ var macroGridControl = GetMacroGridControl(@"{ ""macroAlias"": ""SingleMediaPicker"", ""macroParamsDictionary"": {}}");
+ gridControls.Add(macroGridControl);
+
+ var macroParameterParser = new HtmlMacroParameterParser();
+
+ var result = macroParameterParser.FindUdisFromGridControlMacroParameters(gridControls).ToList();
+ Assert.AreEqual(0, result.Count);
+
+ }
+ //NB: When multiple media pickers store udis instead of ints! - see https://github.com/umbraco/Umbraco-CMS/pull/8388
+ [Test]
+ public void Returns_Udis_From_Multiple_MediaPicker_Macro_Parameters_In_Grid_Macros()
+ {
+ // create a list of GridValue.GridControls with Editor GridEditor alias macro
+ List gridControls = new List();
+
+ // multiple media picker macro parameter
+ var macroGridControl = GetMacroGridControl(@"{ ""macroAlias"": ""SingleMediaPicker"", ""macroParamsDictionary"": { ""multipleMediaPicker"": ""umb://media/eee91c05b2e84031a056dcd7f28eff89,umb://media/fa763e0d0ceb408c8720365d57e06e32,umb://media/bb763e0d0ceb408c8720365d57e06444"" }}");
+ gridControls.Add(macroGridControl);
+
+ var macroParameterParser = new HtmlMacroParameterParser();
+
+ var result = macroParameterParser.FindUdisFromGridControlMacroParameters(gridControls).ToList();
+ Assert.AreEqual(3, result.Count);
+ Assert.AreEqual(Udi.Parse("umb://media/eee91c05b2e84031a056dcd7f28eff89"), result[0]);
+ Assert.AreEqual(Udi.Parse("umb://media/fa763e0d0ceb408c8720365d57e06e32"), result[1]);
+ Assert.AreEqual(Udi.Parse("umb://media/bb763e0d0ceb408c8720365d57e06444"), result[2]);
+
+ }
+
+ //setup a Macro Grid Control based on Json of the Macro
+ private GridValue.GridControl GetMacroGridControl(string macroJson)
+ {
+ var macroGridEditor = new GridValue.GridEditor();
+ macroGridEditor.Alias = "macro";
+ macroGridEditor.View = "macro";
+ var macroGridControl = new GridValue.GridControl();
+ macroGridControl.Editor = macroGridEditor;
+ macroGridControl.Value = JToken.Parse(macroJson);
+ return macroGridControl;
+ }
+
+ }
+}
diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj
index 3c359cdde82b..5604c6915eeb 100644
--- a/src/Umbraco.Tests/Umbraco.Tests.csproj
+++ b/src/Umbraco.Tests/Umbraco.Tests.csproj
@@ -167,6 +167,7 @@
+
From 3b38bc64ee39caa2421afa810c9c9b2d32e547cb Mon Sep 17 00:00:00 2001
From: Marc Moriyamas
Date: Mon, 10 Aug 2020 16:10:47 +0100
Subject: [PATCH 4/9] Inject the HtmlMacroParameterParser into
GridPropertyEditor + RichTextPropertyEditor to hunt for media Udis in Macro
Parameters
---
.../PropertyEditors/GridPropertyEditor.cs | 57 +++++++++++++++----
.../PropertyEditors/RichTextPropertyEditor.cs | 23 +++++---
2 files changed, 63 insertions(+), 17 deletions(-)
diff --git a/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs
index 862837381a24..eafbe303672a 100644
--- a/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs
+++ b/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs
@@ -29,6 +29,7 @@ public class GridPropertyEditor : DataEditor
{
private IUmbracoContextAccessor _umbracoContextAccessor;
private readonly HtmlImageSourceParser _imageSourceParser;
+ private readonly HtmlMacroParameterParser _macroParameterParser;
private readonly RichTextEditorPastedImages _pastedImages;
private readonly HtmlLocalLinkParser _localLinkParser;
private readonly IImageUrlGenerator _imageUrlGenerator;
@@ -39,15 +40,25 @@ public GridPropertyEditor(ILogger logger,
HtmlImageSourceParser imageSourceParser,
RichTextEditorPastedImages pastedImages,
HtmlLocalLinkParser localLinkParser)
- : this(logger, umbracoContextAccessor, imageSourceParser, pastedImages, localLinkParser, Current.ImageUrlGenerator)
+ : this(logger, umbracoContextAccessor, imageSourceParser, pastedImages, localLinkParser, Current.Factory.GetInstance(),Current.ImageUrlGenerator)
+ {
+ }
+ [Obsolete("Use the constructor which takes an HtmlMacroParameterParser")]
+ public GridPropertyEditor(ILogger logger,
+ IUmbracoContextAccessor umbracoContextAccessor,
+ HtmlImageSourceParser imageSourceParser,
+ RichTextEditorPastedImages pastedImages,
+ HtmlLocalLinkParser localLinkParser,
+ IImageUrlGenerator imageUrlGenerator)
+ : this(logger, umbracoContextAccessor, imageSourceParser, pastedImages, localLinkParser, Current.Factory.GetInstance(), imageUrlGenerator)
{
}
-
public GridPropertyEditor(ILogger logger,
IUmbracoContextAccessor umbracoContextAccessor,
HtmlImageSourceParser imageSourceParser,
RichTextEditorPastedImages pastedImages,
HtmlLocalLinkParser localLinkParser,
+ HtmlMacroParameterParser macroParameterParser,
IImageUrlGenerator imageUrlGenerator)
: base(logger)
{
@@ -55,6 +66,7 @@ public GridPropertyEditor(ILogger logger,
_imageSourceParser = imageSourceParser;
_pastedImages = pastedImages;
_localLinkParser = localLinkParser;
+ _macroParameterParser = macroParameterParser;
_imageUrlGenerator = imageUrlGenerator;
}
@@ -72,6 +84,7 @@ internal class GridPropertyValueEditor : DataValueEditor, IDataValueReference
{
private readonly IUmbracoContextAccessor _umbracoContextAccessor;
private readonly HtmlImageSourceParser _imageSourceParser;
+ private readonly HtmlMacroParameterParser _macroParameterParser;
private readonly RichTextEditorPastedImages _pastedImages;
private readonly RichTextPropertyEditor.RichTextPropertyValueEditor _richTextPropertyValueEditor;
private readonly MediaPickerPropertyEditor.MediaPickerPropertyValueEditor _mediaPickerPropertyValueEditor;
@@ -83,23 +96,34 @@ public GridPropertyValueEditor(DataEditorAttribute attribute,
HtmlImageSourceParser imageSourceParser,
RichTextEditorPastedImages pastedImages,
HtmlLocalLinkParser localLinkParser)
- : this(attribute, umbracoContextAccessor, imageSourceParser, pastedImages, localLinkParser, Current.ImageUrlGenerator)
+ : this(attribute, umbracoContextAccessor, imageSourceParser, pastedImages, localLinkParser,Current.Factory.GetInstance(), Current.ImageUrlGenerator)
+ {
+ }
+ [Obsolete("Use the constructor which takes an HtmlMacroParameterParser")]
+ public GridPropertyValueEditor(DataEditorAttribute attribute,
+ IUmbracoContextAccessor umbracoContextAccessor,
+ HtmlImageSourceParser imageSourceParser,
+ RichTextEditorPastedImages pastedImages,
+ HtmlLocalLinkParser localLinkParser,
+ IImageUrlGenerator imageUrlGenerator)
+ : this(attribute, umbracoContextAccessor, imageSourceParser, pastedImages, localLinkParser, Current.Factory.GetInstance(), imageUrlGenerator)
{
}
-
public GridPropertyValueEditor(DataEditorAttribute attribute,
IUmbracoContextAccessor umbracoContextAccessor,
HtmlImageSourceParser imageSourceParser,
RichTextEditorPastedImages pastedImages,
HtmlLocalLinkParser localLinkParser,
+ HtmlMacroParameterParser macroParameterParser,
IImageUrlGenerator imageUrlGenerator)
: base(attribute)
{
_umbracoContextAccessor = umbracoContextAccessor;
_imageSourceParser = imageSourceParser;
_pastedImages = pastedImages;
- _richTextPropertyValueEditor = new RichTextPropertyEditor.RichTextPropertyValueEditor(attribute, umbracoContextAccessor, imageSourceParser, localLinkParser, pastedImages, _imageUrlGenerator);
+ _richTextPropertyValueEditor = new RichTextPropertyEditor.RichTextPropertyValueEditor(attribute, umbracoContextAccessor, imageSourceParser, localLinkParser, macroParameterParser, pastedImages, _imageUrlGenerator);
_mediaPickerPropertyValueEditor = new MediaPickerPropertyEditor.MediaPickerPropertyValueEditor(attribute);
+ _macroParameterParser = macroParameterParser;
_imageUrlGenerator = imageUrlGenerator;
}
@@ -125,7 +149,7 @@ public override object FromEditor(ContentPropertyData editorValue, object curren
var mediaParent = config?.MediaParentId;
var mediaParentId = mediaParent == null ? Guid.Empty : mediaParent.Guid;
- var grid = DeserializeGridValue(rawJson, out var rtes, out _);
+ var grid = DeserializeGridValue(rawJson, out var rtes, out _, out _);
var userId = _umbracoContextAccessor.UmbracoContext?.Security?.CurrentUser?.Id ?? Constants.Security.SuperUserId;
@@ -158,7 +182,7 @@ public override object ToEditor(Property property, IDataTypeService dataTypeServ
var val = property.GetValue(culture, segment)?.ToString();
if (val.IsNullOrWhiteSpace()) return string.Empty;
- var grid = DeserializeGridValue(val, out var rtes, out _);
+ var grid = DeserializeGridValue(val, out var rtes, out _, out _);
//process the rte values
foreach (var rte in rtes.ToList())
@@ -172,7 +196,7 @@ public override object ToEditor(Property property, IDataTypeService dataTypeServ
return grid;
}
- private GridValue DeserializeGridValue(string rawJson, out IEnumerable richTextValues, out IEnumerable mediaValues)
+ private GridValue DeserializeGridValue(string rawJson, out IEnumerable richTextValues, out IEnumerable mediaValues, out IEnumerable macroValues)
{
var grid = JsonConvert.DeserializeObject(rawJson);
@@ -180,7 +204,8 @@ private GridValue DeserializeGridValue(string rawJson, out IEnumerable x.Rows.SelectMany(r => r.Areas).SelectMany(a => a.Controls)).ToArray();
richTextValues = controls.Where(x => x.Editor.Alias.ToLowerInvariant() == "rte");
mediaValues = controls.Where(x => x.Editor.Alias.ToLowerInvariant() == "media");
-
+ // Find all the macros
+ macroValues = controls.Where(x => x.Editor.Alias.ToLowerInvariant() == "macro");
return grid;
}
@@ -196,7 +221,7 @@ public IEnumerable GetReferences(object value)
if (rawJson.IsNullOrWhiteSpace())
yield break;
- DeserializeGridValue(rawJson, out var richTextEditorValues, out var mediaValues);
+ DeserializeGridValue(rawJson, out var richTextEditorValues, out var mediaValues, out var macroValues);
foreach (var umbracoEntityReference in richTextEditorValues.SelectMany(x =>
_richTextPropertyValueEditor.GetReferences(x.Value)))
@@ -205,6 +230,18 @@ public IEnumerable GetReferences(object value)
foreach (var umbracoEntityReference in mediaValues.SelectMany(x =>
_mediaPickerPropertyValueEditor.GetReferences(x.Value["udi"])))
yield return umbracoEntityReference;
+
+ //Macros don't really have a Property Editor for which we can call GetRefererences
+ //where does the responsibility lie for MacroParametersEditors to report their references?
+ //when we don't easily know the property type for a parameter - without 'looking up' which would be expensive?
+ //pragmatically we only care if the parameter has a value that is a media udi eg umb://media
+ //so we 'could' just loop through all parameter values here and add references for any values that are media udis... eg they are in use! however they are picked
+ //Is the HtmlMacroParameterParser the right place to put this method?
+ var udis = _macroParameterParser.FindUdisFromGridControlMacroParameters(macroValues);
+ foreach (var udi in udis)
+ {
+ yield return new UmbracoEntityReference(udi);
+ }
}
}
}
diff --git a/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs
index 42777f11add0..2d8dd6312c9a 100644
--- a/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs
+++ b/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs
@@ -30,6 +30,7 @@ public class RichTextPropertyEditor : DataEditor
private IUmbracoContextAccessor _umbracoContextAccessor;
private readonly HtmlImageSourceParser _imageSourceParser;
private readonly HtmlLocalLinkParser _localLinkParser;
+ private readonly HtmlMacroParameterParser _macroParameterParser;
private readonly RichTextEditorPastedImages _pastedImages;
private readonly IImageUrlGenerator _imageUrlGenerator;
@@ -39,19 +40,24 @@ public class RichTextPropertyEditor : DataEditor
///
[Obsolete("Use the constructor which takes an IImageUrlGenerator")]
public RichTextPropertyEditor(ILogger logger, IUmbracoContextAccessor umbracoContextAccessor, HtmlImageSourceParser imageSourceParser, HtmlLocalLinkParser localLinkParser, RichTextEditorPastedImages pastedImages)
- : this(logger, umbracoContextAccessor, imageSourceParser, localLinkParser, pastedImages, Current.ImageUrlGenerator)
+ : this(logger, umbracoContextAccessor, imageSourceParser, localLinkParser,Current.Factory.GetInstance(), pastedImages, Current.ImageUrlGenerator)
+ {
+ }
+ [Obsolete("Use the constructor which takes a HtmlMacroParameterParser instead")]
+ public RichTextPropertyEditor(ILogger logger, IUmbracoContextAccessor umbracoContextAccessor, HtmlImageSourceParser imageSourceParser, HtmlLocalLinkParser localLinkParser, RichTextEditorPastedImages pastedImages, IImageUrlGenerator imageUrlGenerator)
+ : this(logger, umbracoContextAccessor, imageSourceParser, localLinkParser, Current.Factory.GetInstance(), pastedImages, imageUrlGenerator)
{
}
-
///
/// The constructor will setup the property editor based on the attribute if one is found
///
- public RichTextPropertyEditor(ILogger logger, IUmbracoContextAccessor umbracoContextAccessor, HtmlImageSourceParser imageSourceParser, HtmlLocalLinkParser localLinkParser, RichTextEditorPastedImages pastedImages, IImageUrlGenerator imageUrlGenerator)
+ public RichTextPropertyEditor(ILogger logger, IUmbracoContextAccessor umbracoContextAccessor, HtmlImageSourceParser imageSourceParser, HtmlLocalLinkParser localLinkParser, HtmlMacroParameterParser macroParameterParser, RichTextEditorPastedImages pastedImages, IImageUrlGenerator imageUrlGenerator)
: base(logger)
{
_umbracoContextAccessor = umbracoContextAccessor;
_imageSourceParser = imageSourceParser;
_localLinkParser = localLinkParser;
+ _macroParameterParser = macroParameterParser;
_pastedImages = pastedImages;
_imageUrlGenerator = imageUrlGenerator;
}
@@ -60,7 +66,7 @@ public RichTextPropertyEditor(ILogger logger, IUmbracoContextAccessor umbracoCon
/// Create a custom value editor
///
///
- protected override IDataValueEditor CreateValueEditor() => new RichTextPropertyValueEditor(Attribute, _umbracoContextAccessor, _imageSourceParser, _localLinkParser, _pastedImages, _imageUrlGenerator);
+ protected override IDataValueEditor CreateValueEditor() => new RichTextPropertyValueEditor(Attribute, _umbracoContextAccessor, _imageSourceParser, _localLinkParser, _macroParameterParser,_pastedImages, _imageUrlGenerator);
protected override IConfigurationEditor CreateConfigurationEditor() => new RichTextConfigurationEditor();
@@ -74,15 +80,17 @@ internal class RichTextPropertyValueEditor : DataValueEditor, IDataValueReferenc
private IUmbracoContextAccessor _umbracoContextAccessor;
private readonly HtmlImageSourceParser _imageSourceParser;
private readonly HtmlLocalLinkParser _localLinkParser;
+ private readonly HtmlMacroParameterParser _macroParameterParser;
private readonly RichTextEditorPastedImages _pastedImages;
private readonly IImageUrlGenerator _imageUrlGenerator;
- public RichTextPropertyValueEditor(DataEditorAttribute attribute, IUmbracoContextAccessor umbracoContextAccessor, HtmlImageSourceParser imageSourceParser, HtmlLocalLinkParser localLinkParser, RichTextEditorPastedImages pastedImages, IImageUrlGenerator imageUrlGenerator)
+ public RichTextPropertyValueEditor(DataEditorAttribute attribute, IUmbracoContextAccessor umbracoContextAccessor, HtmlImageSourceParser imageSourceParser, HtmlLocalLinkParser localLinkParser,HtmlMacroParameterParser macroParameterParser, RichTextEditorPastedImages pastedImages, IImageUrlGenerator imageUrlGenerator)
: base(attribute)
{
_umbracoContextAccessor = umbracoContextAccessor;
_imageSourceParser = imageSourceParser;
_localLinkParser = localLinkParser;
+ _macroParameterParser = macroParameterParser;
_pastedImages = pastedImages;
_imageUrlGenerator = imageUrlGenerator;
}
@@ -156,10 +164,11 @@ public IEnumerable GetReferences(object value)
foreach (var udi in _imageSourceParser.FindUdisFromDataAttributes(asString))
yield return new UmbracoEntityReference(udi);
-
+ foreach (var udi in _macroParameterParser.FindUdisFromMacroParameters(asString))
+ yield return new UmbracoEntityReference(udi);
foreach (var udi in _localLinkParser.FindUdisFromLocalLinks(asString))
yield return new UmbracoEntityReference(udi);
-
+ // is the TODO below a reference to needing to scan for media used in Macro parameters (see _macroParameterParser above) or a more general future TODO for some kind of 'tracking macro use' function? (is there an issue for this?)
//TODO: Detect Macros too ... but we can save that for a later date, right now need to do media refs
}
}
From b7c937b2b536b6dd20c2237dd24f907cec0ed012 Mon Sep 17 00:00:00 2001
From: Marc Moriyamas
Date: Mon, 17 Aug 2020 12:55:56 +0100
Subject: [PATCH 5/9] Add GetAll implementation to MacroService for multiple
Macro Aliases
Calling new MacroRepository methods cached with a CustomCachePolicy which gets cleared in the MacroCacheRefresher
---
.../Repositories/IMacroRepository.cs | 3 +++
.../Repositories/Implement/MacroRepository.cs | 24 ++++++++++++++++++-
src/Umbraco.Core/Services/IMacroService.cs | 6 ++---
.../Services/Implement/MacroService.cs | 10 ++++++--
.../Services/MacroServiceTests.cs | 14 +++++++++++
src/Umbraco.Web/Cache/MacroCacheRefresher.cs | 5 +++-
6 files changed, 55 insertions(+), 7 deletions(-)
diff --git a/src/Umbraco.Core/Persistence/Repositories/IMacroRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IMacroRepository.cs
index 1ed08352ed64..dc957a3bcc7f 100644
--- a/src/Umbraco.Core/Persistence/Repositories/IMacroRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/IMacroRepository.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Generic;
using Umbraco.Core.Models;
namespace Umbraco.Core.Persistence.Repositories
@@ -7,6 +8,8 @@ public interface IMacroRepository : IReadWriteQueryRepository, IRea
{
//IEnumerable GetAll(params string[] aliases);
+ IMacro GetByAlias(string alias);
+ IEnumerable GetAllByAlias(string[] aliases);
}
}
diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/MacroRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/MacroRepository.cs
index 1f0b45614b1d..b140e51213c6 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Implement/MacroRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Implement/MacroRepository.cs
@@ -15,9 +15,12 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
{
internal class MacroRepository : NPocoRepositoryBase, IMacroRepository
{
+ private readonly IRepositoryCachePolicy _macroByAliasCachePolicy;
public MacroRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger)
: base(scopeAccessor, cache, logger)
- { }
+ {
+ _macroByAliasCachePolicy = new DefaultRepositoryCachePolicy(GlobalIsolatedCache, ScopeAccessor, DefaultOptions);
+ }
protected override IMacro PerformGet(int id)
{
@@ -213,5 +216,24 @@ protected override void PersistUpdatedItem(IMacro entity)
entity.ResetDirtyProperties();
}
+
+ public IMacro GetByAlias(string alias)
+ {
+ return _macroByAliasCachePolicy.Get(alias, PerformGetByAlias, PerformGetAllByAlias);
+ }
+ public IEnumerable GetAllByAlias(string[] aliases)
+ {
+ return _macroByAliasCachePolicy.GetAll(aliases, PerformGetAllByAlias);
+ }
+ private IMacro PerformGetByAlias(string alias)
+ {
+ var query = Query().Where(x => x.Alias.Equals(alias));
+ return PerformGetByQuery(query).FirstOrDefault();
+ }
+ private IEnumerable PerformGetAllByAlias(params string[] aliases)
+ {
+ var query = Query().WhereIn(x => x.Alias, aliases);
+ return PerformGetByQuery(query);
+ }
}
}
diff --git a/src/Umbraco.Core/Services/IMacroService.cs b/src/Umbraco.Core/Services/IMacroService.cs
index 597c986f3769..cb120d951b07 100644
--- a/src/Umbraco.Core/Services/IMacroService.cs
+++ b/src/Umbraco.Core/Services/IMacroService.cs
@@ -18,12 +18,12 @@ public interface IMacroService : IService
IMacro GetByAlias(string alias);
/////
- ///// Gets a list all available objects
+ ///// Gets a list of available objects by Alias
/////
///// Optional array of aliases to limit the results
///// An enumerable list of objects
- //IEnumerable GetAll(params string[] aliases);
-
+ IEnumerable GetAll(params string[] aliases);
+
IEnumerable GetAll();
IEnumerable GetAll(params int[] ids);
diff --git a/src/Umbraco.Core/Services/Implement/MacroService.cs b/src/Umbraco.Core/Services/Implement/MacroService.cs
index a6631aae4c2e..db71bbf114dd 100644
--- a/src/Umbraco.Core/Services/Implement/MacroService.cs
+++ b/src/Umbraco.Core/Services/Implement/MacroService.cs
@@ -34,8 +34,14 @@ public IMacro GetByAlias(string alias)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
{
- var q = Query().Where(x => x.Alias == alias);
- return _macroRepository.Get(q).FirstOrDefault();
+ return _macroRepository.GetByAlias(alias);
+ }
+ }
+ public IEnumerable GetAll(params string[] aliases)
+ {
+ using (var scope = ScopeProvider.CreateScope(autoComplete: true))
+ {
+ return _macroRepository.GetAllByAlias(aliases);
}
}
diff --git a/src/Umbraco.Tests/Services/MacroServiceTests.cs b/src/Umbraco.Tests/Services/MacroServiceTests.cs
index 69e816585eb4..97c3bea2e0b4 100644
--- a/src/Umbraco.Tests/Services/MacroServiceTests.cs
+++ b/src/Umbraco.Tests/Services/MacroServiceTests.cs
@@ -54,7 +54,21 @@ public void Can_Get_By_Alias()
Assert.IsNotNull(macro);
Assert.AreEqual("Test1", macro.Name);
}
+ [Test]
+ public void Can_Get_By_Aliases()
+ {
+ // Arrange
+ var macroService = ServiceContext.MacroService;
+ // Act
+ var macros = macroService.GetAll("test1","test2");
+
+ //assert
+ Assert.IsNotNull(macros);
+ Assert.AreEqual(2, macros.Count());
+ Assert.AreEqual("Test1", macros.ToArray()[0].Name);
+ Assert.AreEqual("Test2", macros.ToArray()[1].Name);
+ }
[Test]
public void Can_Get_All()
{
diff --git a/src/Umbraco.Web/Cache/MacroCacheRefresher.cs b/src/Umbraco.Web/Cache/MacroCacheRefresher.cs
index 0cecba7b7bec..5c19fb4c6301 100644
--- a/src/Umbraco.Web/Cache/MacroCacheRefresher.cs
+++ b/src/Umbraco.Web/Cache/MacroCacheRefresher.cs
@@ -52,6 +52,7 @@ public override void Refresh(string json)
if (macroRepoCache)
{
macroRepoCache.Result.Clear(RepositoryCacheKeys.GetKey(payload.Id));
+ macroRepoCache.Result.Clear(RepositoryCacheKeys.GetKey(payload.Alias));//repository caching of macro definition by alias
}
}
@@ -99,12 +100,14 @@ internal static string[] GetAllMacroCacheKeys()
return new[]
{
CacheKeys.MacroContentCacheKey, // macro render cache
- CacheKeys.MacroFromAliasCacheKey, // lookup macro by alias
+ CacheKeys.MacroFromAliasCacheKey // lookup macro by alias
+
};
}
internal static string[] GetCacheKeysForAlias(string alias)
{
+
return GetAllMacroCacheKeys().Select(x => x + alias).ToArray();
}
From ed0180f3a2380537ce6ee47cf6e20eeaaa4117fe Mon Sep 17 00:00:00 2001
From: Marc Moriyamas
Date: Mon, 17 Aug 2020 12:56:26 +0100
Subject: [PATCH 6/9] Remove previous tests based on scraping the embedded
parameter udis
---
.../HtmlMacroParameterParserTests.cs | 154 ------------------
src/Umbraco.Tests/Umbraco.Tests.csproj | 1 -
2 files changed, 155 deletions(-)
delete mode 100644 src/Umbraco.Tests/Templates/HtmlMacroParameterParserTests.cs
diff --git a/src/Umbraco.Tests/Templates/HtmlMacroParameterParserTests.cs b/src/Umbraco.Tests/Templates/HtmlMacroParameterParserTests.cs
deleted file mode 100644
index 39a48bbe4c9e..000000000000
--- a/src/Umbraco.Tests/Templates/HtmlMacroParameterParserTests.cs
+++ /dev/null
@@ -1,154 +0,0 @@
-using Umbraco.Core.Logging;
-using Moq;
-using NUnit.Framework;
-using Umbraco.Tests.Testing.Objects.Accessors;
-using Umbraco.Web.Templates;
-using Umbraco.Web;
-using Umbraco.Core.Models.PublishedContent;
-using Umbraco.Web.Routing;
-using Umbraco.Tests.Testing.Objects;
-using System.Web;
-using System;
-using System.Linq;
-using Umbraco.Core.Models;
-using Umbraco.Core;
-using System.Diagnostics;
-using Newtonsoft.Json.Linq;
-using System.Collections.Generic;
-
-namespace Umbraco.Tests.Templates
-{
- [TestFixture]
- public class HtmlMacroParameterParserTests
- {
- [Test]
- public void Returns_Udis_From_Single_MediaPicker_Macro_Parameters_In_Macros_In_Html()
- {
- //Two macros, one single parameter, single Media Picker, one multiple paramters of single media picker
- var input = @"This is some normal text before the macro
-
- This is a paragraph after the macro and before the next
-
- some more text
";
-
- var macroParameterParser = new HtmlMacroParameterParser();
-
- var result = macroParameterParser.FindUdisFromMacroParameters(input).ToList();
- Assert.AreEqual(3, result.Count);
- Assert.AreEqual(Udi.Parse("umb://media/eee91c05b2e84031a056dcd7f28eff89"), result[0]);
- Assert.AreEqual(Udi.Parse("umb://media/fa763e0d0ceb408c8720365d57e06e32"), result[1]);
- Assert.AreEqual(Udi.Parse("umb://media/90ba0d3dba6e4c9fa1953db78352ba73"), result[2]);
- }
- [Test]
- public void Returns_Empty_With_From_Single_MediaPicker_With_No_Macro_Parameters_In_Macros_In_Html()
- {
- //Two macros, one single parameter, single Media Picker, one multiple paramters of single media picker
- var input = @"This is some normal text before the macro
-
- This is a paragraph after the macro and before the next
- Some other value"" />
- some more text
";
-
- var macroParameterParser = new HtmlMacroParameterParser();
-
- var result = macroParameterParser.FindUdisFromMacroParameters(input).ToList();
- Assert.AreEqual(0, result.Count);
- }
- //NB: When multiple media pickers store udis instead of ints! - see https://github.com/umbraco/Umbraco-CMS/pull/8388
- [Test]
- public void Returns_Empty_When_No_Macros_In_Html()
- {
- //Two macros, one single parameter, single Media Picker, one multiple paramters of single media picker
- var input = @"This is some normal text before the macro
- This is a paragraph after the macro and before the next
- some more text
";
-
- var macroParameterParser = new HtmlMacroParameterParser();
-
- var result = macroParameterParser.FindUdisFromMacroParameters(input).ToList();
- Assert.AreEqual(0, result.Count);
- }
- [Test]
- public void Returns_Udis_From_Multiple_MediaPicker_Macro_Parameters_In_Macros_In_Html()
- {
- //Two macros, one single parameter, single Media Picker, one multiple paramters of single media picker
- var input = @"This is some normal text before the macro
-
- This is a paragraph after the macro
";
-
- var macroParameterParser = new HtmlMacroParameterParser();
-
- var result = macroParameterParser.FindUdisFromMacroParameters(input).ToList();
- Assert.AreEqual(3, result.Count);
- Assert.AreEqual(Udi.Parse("umb://media/eee91c05b2e84031a056dcd7f28eff89"), result[0]);
- Assert.AreEqual(Udi.Parse("umb://media/fa763e0d0ceb408c8720365d57e06e32"), result[1]);
- Assert.AreEqual(Udi.Parse("umb://media/bb763e0d0ceb408c8720365d57e06444"), result[2]);
- }
- [Test]
- public void Returns_Udis_From_Single_MediaPicker_Macro_Parameters_In_Grid_Macros()
- {
- // create a list of GridValue.GridControls with Editor GridEditor alias macro
- List gridControls = new List();
-
- // single media picker macro parameter
- var macroGridControl = GetMacroGridControl(@"{ ""macroAlias"": ""SingleMediaPicker"", ""macroParamsDictionary"": { ""singleMediaPicker"": ""umb://media/90ba0d3dba6e4c9fa1953db78352ba73"" }}");
- gridControls.Add(macroGridControl);
-
- var macroParameterParser = new HtmlMacroParameterParser();
-
- var result = macroParameterParser.FindUdisFromGridControlMacroParameters(gridControls).ToList();
- Assert.AreEqual(1, result.Count);
- Assert.AreEqual(Udi.Parse("umb://media/90ba0d3dba6e4c9fa1953db78352ba73"), result[0]);
-
- }
- [Test]
- public void Returns_Empty_From_Single_MediaPicker_With_No_Macro_Parameters_In_Grid_Macros()
- {
- // create a list of GridValue.GridControls with Editor GridEditor alias macro
- List gridControls = new List();
-
- // single media picker macro parameter
- var macroGridControl = GetMacroGridControl(@"{ ""macroAlias"": ""SingleMediaPicker"", ""macroParamsDictionary"": {}}");
- gridControls.Add(macroGridControl);
-
- var macroParameterParser = new HtmlMacroParameterParser();
-
- var result = macroParameterParser.FindUdisFromGridControlMacroParameters(gridControls).ToList();
- Assert.AreEqual(0, result.Count);
-
- }
- //NB: When multiple media pickers store udis instead of ints! - see https://github.com/umbraco/Umbraco-CMS/pull/8388
- [Test]
- public void Returns_Udis_From_Multiple_MediaPicker_Macro_Parameters_In_Grid_Macros()
- {
- // create a list of GridValue.GridControls with Editor GridEditor alias macro
- List gridControls = new List();
-
- // multiple media picker macro parameter
- var macroGridControl = GetMacroGridControl(@"{ ""macroAlias"": ""SingleMediaPicker"", ""macroParamsDictionary"": { ""multipleMediaPicker"": ""umb://media/eee91c05b2e84031a056dcd7f28eff89,umb://media/fa763e0d0ceb408c8720365d57e06e32,umb://media/bb763e0d0ceb408c8720365d57e06444"" }}");
- gridControls.Add(macroGridControl);
-
- var macroParameterParser = new HtmlMacroParameterParser();
-
- var result = macroParameterParser.FindUdisFromGridControlMacroParameters(gridControls).ToList();
- Assert.AreEqual(3, result.Count);
- Assert.AreEqual(Udi.Parse("umb://media/eee91c05b2e84031a056dcd7f28eff89"), result[0]);
- Assert.AreEqual(Udi.Parse("umb://media/fa763e0d0ceb408c8720365d57e06e32"), result[1]);
- Assert.AreEqual(Udi.Parse("umb://media/bb763e0d0ceb408c8720365d57e06444"), result[2]);
-
- }
-
- //setup a Macro Grid Control based on Json of the Macro
- private GridValue.GridControl GetMacroGridControl(string macroJson)
- {
- var macroGridEditor = new GridValue.GridEditor();
- macroGridEditor.Alias = "macro";
- macroGridEditor.View = "macro";
- var macroGridControl = new GridValue.GridControl();
- macroGridControl.Editor = macroGridEditor;
- macroGridControl.Value = JToken.Parse(macroJson);
- return macroGridControl;
- }
-
- }
-}
diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj
index 5604c6915eeb..3c359cdde82b 100644
--- a/src/Umbraco.Tests/Umbraco.Tests.csproj
+++ b/src/Umbraco.Tests/Umbraco.Tests.csproj
@@ -167,7 +167,6 @@
-
From e3989263204d0b9b339084a282f149c9b66587c2 Mon Sep 17 00:00:00 2001
From: Marc Moriyamas
Date: Mon, 17 Aug 2020 12:57:43 +0100
Subject: [PATCH 7/9] Update Macro Parameter Parser, to use the Macro
Configuration, to find the types of Macro Parameter Property Editors To
GetUdiReferences
Utilises the new GetAll endpoint in the MacroService
---
.../Templates/HtmlMacroParameterParser.cs | 149 +++++++++++-------
1 file changed, 93 insertions(+), 56 deletions(-)
diff --git a/src/Umbraco.Web/Templates/HtmlMacroParameterParser.cs b/src/Umbraco.Web/Templates/HtmlMacroParameterParser.cs
index c944ab45024d..d851f23a8170 100644
--- a/src/Umbraco.Web/Templates/HtmlMacroParameterParser.cs
+++ b/src/Umbraco.Web/Templates/HtmlMacroParameterParser.cs
@@ -4,97 +4,134 @@
using System.Linq;
using System.Text.RegularExpressions;
using Umbraco.Core;
+using Umbraco.Core.Composing;
using Umbraco.Core.Models;
+using Umbraco.Core.Models.Editors;
+using Umbraco.Core.PropertyEditors;
+using Umbraco.Core.Services;
using Umbraco.Web.Macros;
+using Umbraco.Core.Logging;
namespace Umbraco.Web.Templates
{
public sealed class HtmlMacroParameterParser
{
- public HtmlMacroParameterParser()
+ private readonly IMacroService _macroService;
+ private readonly ILogger _logger;
+ //private readonly ParameterEditorCollection _parameterEditors;
+ public HtmlMacroParameterParser(IMacroService macroService, ILogger logger)//, ParameterEditorCollection parameterEditorCollection)
{
-
+ //I get an error at boot injecting this here, what would be the best way of referencing it?
+ //_parameterEditors = parameterEditorCollection;
+ _macroService = macroService;
+ _logger = logger;
}
+ private IEnumerable GetUmbracoEntityReferencesFromMacros(List>> macros)
+ {
+ var uniqueMacroAliases = macros.Select(f => f.Item1).Distinct();
+ //TODO: tracking Macro references...? am finding the used Macros Udis here - but not sure what to do with them - eg seems like there should be a Related Macro relation type - but Relations don't accept 'Macro' as an option
+ var foundMacroUmbracoEntityReferences = new List();
+ //Get all the macro configs in one hit for these unique macro aliases - this is now cached with a custom cache policy
+ var macroConfigs = _macroService.GetAll(uniqueMacroAliases.ToArray());
+ //get the registered parametereditors with the implmementation to avoid looking up for each parameter
+ // can we inject this instead of using Current?? - I get a boot error when I attempt this directly into the parser constructor - could/should it be passed in via the method?
+ var parameterEditors = Current.ParameterEditors;
+ foreach (var macro in macros)
+ {
+ var macroConfig = macroConfigs.FirstOrDefault(f => f.Alias == macro.Item1);
+ foundMacroUmbracoEntityReferences.Add(new UmbracoEntityReference(Udi.Create(Constants.UdiEntityType.Macro, macroConfig.Key)));
+ // only do this if the macros actually have parameters
+ if (macroConfig.Properties != null && macroConfig.Properties.Keys.Any(f=>f!= "macroAlias"))
+ {
+ foreach (var umbracoEntityReference in GetUmbracoEntityReferencesFromMacroParameters(macro.Item1, macro.Item2, macroConfig, parameterEditors))
+ {
+ yield return umbracoEntityReference;
+ }
+ }
+ }
+ }
///
- /// Parses out media UDIs from an html string based on embedded macro parameter values
+ /// Finds media UDIs in Macro Parameter Values by calling the GetReference method for all the Macro Parameter Editors for a particular Macro
///
- ///
+ /// The alias of the macro
+ /// The parameters for the macro a dictionary of key/value strings
+ /// The macro configuration for this particular macro - contains the types of editors used for each parameter
+ /// A list of all the registered parameter editors used in the Umbraco implmentation - to look up the corresponding property editor for a macro parameter
///
- public IEnumerable FindUdisFromMacroParameters(string text)
+ private IEnumerable GetUmbracoEntityReferencesFromMacroParameters(string macroAlias, Dictionary macroParameters, IMacro macroConfig, ParameterEditorCollection parameterEditors)
{
- // we already have a method for finding Macros within a Rich Text Editor using regexes: Umbraco.Web.Macros.MacroTagParser
- // it searches for 'text blocks' which I think are the old way of embedding macros in a template, to allow inline code - we're not interested in those in V8
- // but we are interested in the foundMacros, and in particular their parameters and values
- // (aside TODO: at somepoint we might want to track Macro usage?)
- // ideally we'd determine each type of parameter editor and call it's GetReferences implementation
- // however this information isn't stored in the embedded macro markup and would involve 'looking up' types for each parameter
- // (or we could change the macros markup to include details of the editor)
- // but pragmatically we don't really care what type of property editor it is if it stores media udis
- // we want to track media udis wherever they are used regardless of how they are picked?
- // so here we parse the parameters and check to see if they are in the format umb://media
- // see also https://github.com/umbraco/Umbraco-CMS/pull/8388
-
- List macroParameterValues = new List();
- MacroTagParser.ParseMacros(text, textblock => { }, (macroAlias, macroAttributes) => macroParameterValues.AddRange(macroAttributes.Values.ToList().Where(f=>f.StartsWith("umb://" + Constants.UdiEntityType.Media + "/"))));
-
- if (macroParameterValues.Any())
- {
- // we have media udis! - but we could also have a csv of multiple media udis items
- foreach (var macroParameterValue in macroParameterValues.Distinct())
+ var foundUmbracoEntityReferences = new List();
+ foreach (var parameter in macroConfig.Properties)
+ {
+ if (macroParameters.TryGetValue(parameter.Alias, out string parameterValue))
{
- string[] potentialUdis = macroParameterValue.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
- foreach (var potentialUdi in potentialUdis.Distinct())
+ var parameterEditorAlias = parameter.EditorAlias;
+ //lookup propertyEditor from core current ParameterEditorCollection
+ var parameterEditor = parameterEditors.FirstOrDefault(f => String.Equals(f.Alias,parameterEditorAlias,StringComparison.OrdinalIgnoreCase));
+ if (parameterEditor != null)
{
- if(Udi.TryParse(potentialUdi, out var udi))
+ //get the ParameterValueEditor for this PropertyEditor (where the GetReferences method is implemented) - cast As IDataValueReference to determine if 'it is' implemented for the editor
+ IDataValueReference parameterValueEditor = parameterEditor.GetValueEditor() as IDataValueReference;
+ if (parameterValueEditor != null)
+ {
+ foreach (var entityReference in parameterValueEditor.GetReferences(parameterValue))
+ {
+ foundUmbracoEntityReferences.Add(entityReference);
+ }
+ }
+ else
{
- yield return udi;
+ _logger.Info("{0} doesn't have a ValueEditor that implements IDataValueReference", parameterEditor.Alias);
}
}
}
}
- else
+ return foundUmbracoEntityReferences;
+ }
+ ///
+ /// Parses out media UDIs from an html string based on embedded macro parameter values
+ ///
+ ///
+ ///
+ public IEnumerable FindUmbracoEntityReferencesFromEmbeddedMacros(string text)
+ {
+ // we already have a method for finding Macros within a Rich Text Editor using regexes: Umbraco.Web.Macros.MacroTagParser
+ // it searches for 'text blocks' which I think are the old way of embedding macros in a template, to allow inline code - we're not interested in those in V8
+ // but we are interested in the foundMacros,
+ // there may be more than one macro with the same alias on the page so using a tuple
+ List>> foundMacros = new List>>();
+
+ //though this legacy ParseMacros does find the macros it seems to lowercase the macro parameter alias - so making the dictionary case insensitive
+ MacroTagParser.ParseMacros(text, textblock => { }, (macroAlias, macroAttributes) => foundMacros.Add(new Tuple>(macroAlias, new Dictionary(macroAttributes,StringComparer.OrdinalIgnoreCase))));
+ foreach (var umbracoEntityReference in GetUmbracoEntityReferencesFromMacros(foundMacros))
{
- yield break;
- }
+ yield return umbracoEntityReference;
+ };
}
///
/// Parses out media UDIs from Macro Grid Control Parameters
///
///
- public IEnumerable FindUdisFromGridControlMacroParameters(IEnumerable macroValues)
+ public IEnumerable FindUmbracoEntityReferencesFromGridControlMacros(IEnumerable macroGridControls)
{
- List macroParameterValues = new List();
- foreach (var macroValue in macroValues)
+ List>> foundMacros = new List>>();
+
+ foreach (var macroGridControl in macroGridControls)
{
//deserialise JSON of Macro Grid Control to a class
- var gridMacro = macroValue.Value.ToObject();
+ var gridMacro = macroGridControl.Value.ToObject();
//collect any macro parameters that contain the media udi format
- if (gridMacro != null && gridMacro.MacroParameters!=null && gridMacro.MacroParameters.Any())
+ if (gridMacro != null && gridMacro.MacroParameters != null && gridMacro.MacroParameters.Any())
{
- macroParameterValues.AddRange(gridMacro.MacroParameters.Values.ToList().Where(f => f.Contains("umb://" + Constants.UdiEntityType.Media + "/")));
- }
- }
- if (macroParameterValues.Any())
- {
- // we have media udis! - but we could also have a csv of multiple items
- foreach (var macroParameterValue in macroParameterValues.Distinct())
- {
- string[] potentialUdis = macroParameterValue.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
- foreach (var potentialUdi in potentialUdis.Distinct())
- {
- if (Udi.TryParse(potentialUdi, out var udi))
- {
- yield return udi;
- }
- }
+ foundMacros.Add(new Tuple>(gridMacro.MacroAlias, gridMacro.MacroParameters));
}
}
- else
+ foreach (var umbracoEntityReference in GetUmbracoEntityReferencesFromMacros(foundMacros))
{
- yield break;
- }
+ yield return umbracoEntityReference;
+ };
}
// poco class to deserialise the Json for a Macro Control
private class GridMacro
@@ -102,7 +139,7 @@ private class GridMacro
[JsonProperty("macroAlias")]
public string MacroAlias { get; set; }
[JsonProperty("macroParamsDictionary")]
- public Dictionary MacroParameters { get; set; }
+ public Dictionary MacroParameters { get; set; }
}
}
}
From 7ab1028273de5b43515cdc0c097823a811325e45 Mon Sep 17 00:00:00 2001
From: Marc Moriyamas
Date: Mon, 17 Aug 2020 12:59:13 +0100
Subject: [PATCH 8/9] Update Grid + RichTextEditor Property Editors to use the
MacroParameterParser
---
.../PropertyEditors/GridPropertyEditor.cs | 15 ++++-----------
.../PropertyEditors/RichTextPropertyEditor.cs | 9 ++++++---
2 files changed, 10 insertions(+), 14 deletions(-)
diff --git a/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs
index eafbe303672a..cbdcc505de22 100644
--- a/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs
+++ b/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs
@@ -230,18 +230,11 @@ public IEnumerable GetReferences(object value)
foreach (var umbracoEntityReference in mediaValues.SelectMany(x =>
_mediaPickerPropertyValueEditor.GetReferences(x.Value["udi"])))
yield return umbracoEntityReference;
+
+ foreach (var umbracoEntityReference in _macroParameterParser.FindUmbracoEntityReferencesFromGridControlMacros(macroValues))
+ yield return umbracoEntityReference;
- //Macros don't really have a Property Editor for which we can call GetRefererences
- //where does the responsibility lie for MacroParametersEditors to report their references?
- //when we don't easily know the property type for a parameter - without 'looking up' which would be expensive?
- //pragmatically we only care if the parameter has a value that is a media udi eg umb://media
- //so we 'could' just loop through all parameter values here and add references for any values that are media udis... eg they are in use! however they are picked
- //Is the HtmlMacroParameterParser the right place to put this method?
- var udis = _macroParameterParser.FindUdisFromGridControlMacroParameters(macroValues);
- foreach (var udi in udis)
- {
- yield return new UmbracoEntityReference(udi);
- }
+
}
}
}
diff --git a/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs
index 2d8dd6312c9a..93baf045b4cf 100644
--- a/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs
+++ b/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs
@@ -164,12 +164,15 @@ public IEnumerable GetReferences(object value)
foreach (var udi in _imageSourceParser.FindUdisFromDataAttributes(asString))
yield return new UmbracoEntityReference(udi);
- foreach (var udi in _macroParameterParser.FindUdisFromMacroParameters(asString))
- yield return new UmbracoEntityReference(udi);
+
foreach (var udi in _localLinkParser.FindUdisFromLocalLinks(asString))
yield return new UmbracoEntityReference(udi);
- // is the TODO below a reference to needing to scan for media used in Macro parameters (see _macroParameterParser above) or a more general future TODO for some kind of 'tracking macro use' function? (is there an issue for this?)
+
+ foreach (var umbracoEntityReference in _macroParameterParser.FindUmbracoEntityReferencesFromEmbeddedMacros(asString))
+ yield return umbracoEntityReference;
//TODO: Detect Macros too ... but we can save that for a later date, right now need to do media refs
+ //UPDATE: We are getting the Macros in 'FindUmbracoEntityReferencesFromEmbeddedMacros' do we just return the Macro Udis here too? do they need their own relationAlias?
+
}
}
From 27007cee0a2d4a31d0c915611e0a06c8fd7a5465 Mon Sep 17 00:00:00 2001
From: Marc Moriyamas
Date: Mon, 17 Aug 2020 13:00:34 +0100
Subject: [PATCH 9/9] Update MultiMediaPicker Parameter editor to have it's own
ValueEditor to handle legacy Ids
(there is a PR to store these as Udis in the future, this will handle both scenarios)
---
.../MultipleMediaPickerParameterEditor.cs | 45 ++++++++++++++++++-
1 file changed, 44 insertions(+), 1 deletion(-)
diff --git a/src/Umbraco.Web/PropertyEditors/ParameterEditors/MultipleMediaPickerParameterEditor.cs b/src/Umbraco.Web/PropertyEditors/ParameterEditors/MultipleMediaPickerParameterEditor.cs
index 1208a5eecc33..63107f048385 100644
--- a/src/Umbraco.Web/PropertyEditors/ParameterEditors/MultipleMediaPickerParameterEditor.cs
+++ b/src/Umbraco.Web/PropertyEditors/ParameterEditors/MultipleMediaPickerParameterEditor.cs
@@ -1,6 +1,12 @@
-using Umbraco.Core;
+using System;
+using System.Collections.Generic;
+using Umbraco.Core;
+using Umbraco.Core.Composing;
using Umbraco.Core.Logging;
+using Umbraco.Core.Models;
+using Umbraco.Core.Models.Editors;
using Umbraco.Core.PropertyEditors;
+using static Umbraco.Web.PropertyEditors.MediaPickerPropertyEditor;
namespace Umbraco.Web.PropertyEditors.ParameterEditors
{
@@ -23,5 +29,42 @@ public MultipleMediaPickerParameterEditor(ILogger logger)
{
DefaultConfiguration.Add("multiPicker", "1");
}
+ protected override IDataValueEditor CreateValueEditor() => new MultipleMediaPickerPropertyValueEditor(Attribute);
+
+ internal class MultipleMediaPickerPropertyValueEditor : DataValueEditor, IDataValueReference
+ {
+ public MultipleMediaPickerPropertyValueEditor(DataEditorAttribute attribute) : base(attribute)
+ {
+ }
+
+ public IEnumerable GetReferences(object value)
+ {
+ var asString = value is string str ? str : value?.ToString();
+
+ if (string.IsNullOrEmpty(asString)) yield break;
+
+ foreach (var udiStr in asString.Split(','))
+ {
+ if (Udi.TryParse(udiStr, out var udi))
+ {
+ yield return new UmbracoEntityReference(udi);
+ }
+ // for legacy reasons the multiple media picker parameter editor is configured to store as ints not udis - there is a PR to perhaps change this: https://github.com/umbraco/Umbraco-CMS/pull/8388
+ // but adding below should support both scenarios, or should this be added as a fallback on the MediaPickerPropertyValueEditor
+ if (int.TryParse(udiStr, out var id))
+ {
+ //TODO: inject the service?
+ var guidAttempt = Current.Services.EntityService.GetKey(id, UmbracoObjectTypes.Media);
+ var guid = guidAttempt.Success ? guidAttempt.Result : Guid.Empty;
+ if (guid != Guid.Empty)
+ {
+ yield return new UmbracoEntityReference(new GuidUdi(Constants.UdiEntityType.Media, guid));
+ }
+
+ }
+ }
+ }
+ }
+
}
}