From 5d9e104bb9285039713ac3300dbbf5f6b76e8fa5 Mon Sep 17 00:00:00 2001 From: Kevin Jump Date: Thu, 23 Jul 2026 12:34:54 +0100 Subject: [PATCH] Inline legacy local link parsing to drop obsolete HtmlLocalLinkParser API HtmlLocalLinkParser.FindLegacyLocalLinkIds and its LocalLinkTag model are obsolete (CS0618) and scheduled for removal in Umbraco 18, but uSync still needs to detect un-migrated {localLink:x} references when mapping RTE content. Copies the regex and parsing logic directly into SyncLocalLinkProcessor (verified against the Umbraco 18 source) so we own it going forward instead of depending on removed API surface. Co-Authored-By: Claude Sonnet 5 --- .../Versions/18.0/SyncLocalLinkProcessor.cs | 70 ++++++++++++++++--- 1 file changed, 62 insertions(+), 8 deletions(-) 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; } + } }