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(); } diff --git a/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs index 862837381a24..cbdcc505de22 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,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; + + } } } 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)); + } + + } + } + } + } + } } diff --git a/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs index 42777f11add0..93baf045b4cf 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,11 +164,15 @@ public IEnumerable GetReferences(object value) foreach (var udi in _imageSourceParser.FindUdisFromDataAttributes(asString)) yield return new UmbracoEntityReference(udi); - + foreach (var udi in _localLinkParser.FindUdisFromLocalLinks(asString)) yield return new UmbracoEntityReference(udi); + 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? + } } 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! diff --git a/src/Umbraco.Web/Templates/HtmlMacroParameterParser.cs b/src/Umbraco.Web/Templates/HtmlMacroParameterParser.cs new file mode 100644 index 000000000000..d851f23a8170 --- /dev/null +++ b/src/Umbraco.Web/Templates/HtmlMacroParameterParser.cs @@ -0,0 +1,145 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +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 + { + 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; + } + } + } + } + /// + /// 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 + /// + private IEnumerable GetUmbracoEntityReferencesFromMacroParameters(string macroAlias, Dictionary macroParameters, IMacro macroConfig, ParameterEditorCollection parameterEditors) + { + var foundUmbracoEntityReferences = new List(); + foreach (var parameter in macroConfig.Properties) + { + if (macroParameters.TryGetValue(parameter.Alias, out string parameterValue)) + { + 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) + { + //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 + { + _logger.Info("{0} doesn't have a ValueEditor that implements IDataValueReference", parameterEditor.Alias); + } + } + } + } + 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 return umbracoEntityReference; + }; + } + /// + /// Parses out media UDIs from Macro Grid Control Parameters + /// + /// + public IEnumerable FindUmbracoEntityReferencesFromGridControlMacros(IEnumerable macroGridControls) + { + List>> foundMacros = new List>>(); + + foreach (var macroGridControl in macroGridControls) + { + //deserialise JSON of Macro Grid Control to a class + var gridMacro = macroGridControl.Value.ToObject(); + //collect any macro parameters that contain the media udi format + if (gridMacro != null && gridMacro.MacroParameters != null && gridMacro.MacroParameters.Any()) + { + foundMacros.Add(new Tuple>(gridMacro.MacroAlias, gridMacro.MacroParameters)); + } + } + foreach (var umbracoEntityReference in GetUmbracoEntityReferencesFromMacros(foundMacros)) + { + yield return umbracoEntityReference; + }; + } + // 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 @@ +