diff --git a/uSync.Core/Versions/18.0/SyncLocalLinkProcessor.cs b/uSync.Core/Versions/18.0/SyncLocalLinkProcessor.cs index 8666194f..26da949a 100644 --- a/uSync.Core/Versions/18.0/SyncLocalLinkProcessor.cs +++ b/uSync.Core/Versions/18.0/SyncLocalLinkProcessor.cs @@ -1,11 +1,12 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Text; +using System.Text.RegularExpressions; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Core.Templates; namespace uSync.Core.Versions._18._0; @@ -18,19 +19,27 @@ public interface ISyncTypedLocalLinkProcessor public Func, Func, bool> Process { get; } } - -public class SyncLocalLinkProcessor +/// +/// Finds and replaces the legacy {localLink:x} syntax within values, mapping ids/UDIs to keys. +/// +/// +/// Umbraco's own HtmlLocalLinkParser.FindLegacyLocalLinkIds (and its LocalLinkTag model) +/// are obsolete and scheduled for removal in Umbraco 18, but uSync still needs to detect these +/// legacy (un-migrated) links when mapping content, so the parsing logic lives here instead. +/// +public partial class SyncLocalLinkProcessor { - private readonly HtmlLocalLinkParser _localLinkParser; + // copied from Umbraco.Cms.Core.Templates.HtmlLocalLinkParser.LocalLinkPattern + [GeneratedRegex(@"href=['""](?\/?(?:\{|\%7B)localLink:(?[a-zA-Z0-9-://]+)(?:\}|\%7D))", RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.IgnorePatternWhitespace, "en-GB")] + private static partial Regex LegacyLocalLinkPattern(); + private readonly IIdKeyMap _idKeyMap; private readonly IEnumerable _localLinkProcessors; public SyncLocalLinkProcessor( - HtmlLocalLinkParser localLinkParser, IIdKeyMap idKeyMap, IEnumerable localLinkProcessors) { - _localLinkParser = localLinkParser; _idKeyMap = idKeyMap; _localLinkProcessors = localLinkProcessors; } @@ -49,9 +58,9 @@ public bool ProcessToEditorValue(object? editorValue) public string ProcessStringValue(string input) { // find all legacy tags - var tags = _localLinkParser.FindLegacyLocalLinkIds(input).ToList(); + var tags = FindLegacyLocalLinkIds(input).ToList(); - foreach (HtmlLocalLinkParser.LocalLinkTag tag in tags) + foreach (LocalLinkTag tag in tags) { string newTagHref; if (tag.Udi is not null) @@ -101,4 +110,49 @@ public string ProcessStringValue(string input) return null; } + // copied from Umbraco.Cms.Core.Templates.HtmlLocalLinkParser.FindLegacyLocalLinkIds + private static IEnumerable FindLegacyLocalLinkIds(string text) + { + MatchCollection tags = LegacyLocalLinkPattern().Matches(text); + foreach (Match tag in tags) + { + if (tag.Groups.Count <= 0) + { + continue; + } + + var id = tag.Groups["guid"].Value; + + // The id could be an int or a UDI + if (UdiParser.TryParse(id, out Udi? udi)) + { + if (udi is GuidUdi guidUdi) + { + yield return new LocalLinkTag(null, guidUdi, tag.Groups["locallink"].Value); + } + } + + if (int.TryParse(id, NumberStyles.Integer, CultureInfo.InvariantCulture, out var intId)) + { + yield return new LocalLinkTag(intId, null, tag.Groups["locallink"].Value); + } + } + } + + // copied from Umbraco.Cms.Core.Templates.HtmlLocalLinkParser.LocalLinkTag + private sealed class LocalLinkTag + { + public LocalLinkTag(int? intId, GuidUdi? udi, string tagHref) + { + IntId = intId; + Udi = udi; + TagHref = tagHref; + } + + public int? IntId { get; } + + public GuidUdi? Udi { get; } + + public string TagHref { get; } + } }