Files
Umbraco-CMS/src/Umbraco.Infrastructure/Templates/HtmlMacroParameterParser.cs
Andy Butland 5810c2d35b Further NRT updates for Deploy (#12375)
* Nullability modifications to grid cell values and connector.

* Nullable updates to IDataTypeConfigurationConnector.

* Apply suggestions from code review

Co-authored-by: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com>

Co-authored-by: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com>
(cherry picked from commit 109d2f191e)
2022-05-10 09:51:01 +02:00

157 lines
8.0 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.Editors;
using Umbraco.Cms.Core.PropertyEditors;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Infrastructure.Macros;
using Umbraco.Extensions;
namespace Umbraco.Cms.Infrastructure.Templates
{
public sealed class HtmlMacroParameterParser : IHtmlMacroParameterParser
{
private readonly IMacroService _macroService;
private readonly ILogger<HtmlMacroParameterParser> _logger;
private readonly ParameterEditorCollection _parameterEditors;
public HtmlMacroParameterParser(IMacroService macroService, ILogger<HtmlMacroParameterParser> logger, ParameterEditorCollection parameterEditors)
{
_macroService = macroService;
_logger = logger;
_parameterEditors = parameterEditors;
}
/// <summary>
/// Parses out media UDIs from an HTML string based on embedded macro parameter values.
/// </summary>
/// <param name="text">HTML string</param>
/// <returns></returns>
public IEnumerable<UmbracoEntityReference> FindUmbracoEntityReferencesFromEmbeddedMacros(string text)
{
// There may be more than one macro with the same alias on the page so using a tuple
var foundMacros = new List<Tuple<string?, Dictionary<string, string>>>();
// This legacy ParseMacros() already finds the macros within a Rich Text Editor using regexes
// It seems to lowercase the macro parameter alias - so making the dictionary case insensitive
MacroTagParser.ParseMacros(text, textblock => { }, (macroAlias, macroAttributes) => foundMacros.Add(new Tuple<string?, Dictionary<string, string>>(macroAlias, new Dictionary<string, string>(macroAttributes, StringComparer.OrdinalIgnoreCase))));
foreach (var umbracoEntityReference in GetUmbracoEntityReferencesFromMacros(foundMacros))
{
yield return umbracoEntityReference;
}
}
/// <summary>
/// Parses out media UDIs from Macro Grid Control parameters.
/// </summary>
/// <param name="macroGridControls"></param>
/// <returns></returns>
public IEnumerable<UmbracoEntityReference> FindUmbracoEntityReferencesFromGridControlMacros(IEnumerable<GridValue.GridControl> macroGridControls)
{
var foundMacros = new List<Tuple<string?, Dictionary<string, string>>>();
foreach (var macroGridControl in macroGridControls)
{
// Deserialise JSON of Macro Grid Control to a class
var gridMacro = macroGridControl.Value?.ToObject<GridMacro>();
// Collect any macro parameters that contain the media udi format
if (gridMacro is not null && gridMacro.MacroParameters is not null && gridMacro.MacroParameters.Any())
{
foundMacros.Add(new Tuple<string?, Dictionary<string, string>>(gridMacro.MacroAlias, gridMacro.MacroParameters));
}
}
foreach (var umbracoEntityReference in GetUmbracoEntityReferencesFromMacros(foundMacros))
{
yield return umbracoEntityReference;
}
}
private IEnumerable<UmbracoEntityReference> GetUmbracoEntityReferencesFromMacros(List<Tuple<string?, Dictionary<string, string>>> macros)
{
if (_macroService is not IMacroWithAliasService macroWithAliasService)
{
yield break;
}
var uniqueMacroAliases = macros.Select(f => f.Item1).Distinct();
// TODO: Tracking Macro references
// Here we are finding the used macros' Udis (there should be a Related Macro relation type - but Relations don't accept 'Macro' as an option)
var foundMacroUmbracoEntityReferences = new List<UmbracoEntityReference>();
// Get all the macro configs in one hit for these unique macro aliases - this is now cached with a custom cache policy
var macroConfigs = macroWithAliasService.GetAll(uniqueMacroAliases.WhereNotNull().ToArray());
foreach (var macro in macros)
{
var macroConfig = macroConfigs.FirstOrDefault(f => f.Alias == macro.Item1);
if (macroConfig is null)
{
continue;
}
foundMacroUmbracoEntityReferences.Add(new UmbracoEntityReference(Udi.Create(Constants.UdiEntityType.Macro, macroConfig.Key)));
// Only do this if the macros actually have parameters
if (macroConfig.Properties is not null && macroConfig.Properties.Keys.Any(f => f != "macroAlias"))
{
foreach (var umbracoEntityReference in GetUmbracoEntityReferencesFromMacroParameters(macro.Item2, macroConfig, _parameterEditors))
{
yield return umbracoEntityReference;
}
}
}
}
/// <summary>
/// Finds media UDIs in Macro Parameter Values by calling the GetReference method for all the Macro Parameter Editors for a particular macro.
/// </summary>
/// <param name="macroParameters">The parameters for the macro a dictionary of key/value strings</param>
/// <param name="macroConfig">The macro configuration for this particular macro - contains the types of editors used for each parameter</param>
/// <param name="parameterEditors">A list of all the registered parameter editors used in the Umbraco implmentation - to look up the corresponding property editor for a macro parameter</param>
/// <returns></returns>
private IEnumerable<UmbracoEntityReference> GetUmbracoEntityReferencesFromMacroParameters(Dictionary<string, string> macroParameters, IMacro macroConfig, ParameterEditorCollection parameterEditors)
{
var foundUmbracoEntityReferences = new List<UmbracoEntityReference>();
foreach (var parameter in macroConfig.Properties)
{
if (macroParameters.TryGetValue(parameter.Alias, out string? parameterValue))
{
var parameterEditorAlias = parameter.EditorAlias;
// Lookup propertyEditor from the registered ParameterEditors with the implmementation to avoid looking up for each parameter
var parameterEditor = parameterEditors.FirstOrDefault(f => string.Equals(f.Alias, parameterEditorAlias, StringComparison.OrdinalIgnoreCase));
if (parameterEditor is not 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
if (parameterEditor.GetValueEditor() is IDataValueReference parameterValueEditor)
{
foreach (var entityReference in parameterValueEditor.GetReferences(parameterValue))
{
foundUmbracoEntityReferences.Add(entityReference);
}
}
else
{
_logger.LogInformation("{0} doesn't have a ValueEditor that implements IDataValueReference", parameterEditor.Alias);
}
}
}
}
return foundUmbracoEntityReferences;
}
// Poco class to deserialise the Json for a Macro Control
private class GridMacro
{
[JsonProperty("macroAlias")]
public string? MacroAlias { get; set; }
[JsonProperty("macroParamsDictionary")]
public Dictionary<string, string>? MacroParameters { get; set; }
}
}
}