Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
using System.Text.RegularExpressions;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Umbraco.Cms.Core;
Expand All @@ -8,14 +7,15 @@
using Umbraco.Cms.Core.Serialization;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Web;
using Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_15_0_0.LocalLinks;

namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_15_0_0;

/// <summary>
/// Migration responsible for converting rich text editor properties to the new format as part of the upgrade process to Umbraco version 15.0.0.
/// </summary>
[Obsolete("Scheduled for removal in Umbraco 18.")]
public partial class ConvertRichTextEditorProperties : ConvertBlockEditorPropertiesBase
public class ConvertRichTextEditorProperties : ConvertBlockEditorPropertiesBase
{
/// <summary>
/// Initializes a new instance of the <see cref="ConvertRichTextEditorProperties"/> class.
Expand Down Expand Up @@ -62,13 +62,7 @@ protected override object UpdateEditorValue(object editorValue)
return base.UpdateEditorValue(editorValue);
}

richTextEditorValue.Markup = BlockRegex().Replace(
richTextEditorValue.Markup,
match => UdiParser.TryParse(match.Groups["udi"].Value, out GuidUdi? guidUdi)
? match.Value
.Replace(match.Groups["attribute"].Value, "data-content-key")
.Replace(match.Groups["udi"].Value, guidUdi.Guid.ToString("D"))
: string.Empty);
richTextEditorValue.Markup = RteBlockHelper.ConvertBlockUdisToKeys(richTextEditorValue.Markup);

return richTextEditorValue;
}
Expand Down Expand Up @@ -100,7 +94,4 @@ public ConvertRichTextEditorProperties(
protected override bool IsCandidateForMigration(IPropertyType propertyType, IDataType dataType)
=> dataType.ConfigurationObject is RichTextConfiguration richTextConfiguration
&& richTextConfiguration.Blocks?.Any() is true;

[GeneratedRegex("<umb-rte-block.*(?<attribute>data-content-udi)=\"(?<udi>.[^\"]*)\".*<\\/umb-rte-block")]
private static partial Regex BlockRegex();
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
using System.Text.RegularExpressions;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Models.Blocks;

Expand Down Expand Up @@ -51,14 +50,9 @@ public bool ProcessRichText(

var newMarkup = processStringValue.Invoke(richTextValue.Markup);

// fix recursive hickup in ConvertRichTextEditorProperties
newMarkup = RteBlockHelper.BlockRegex().Replace(
newMarkup,
match => UdiParser.TryParse(match.Groups["udi"].Value, out GuidUdi? guidUdi)
? match.Value
.Replace(match.Groups["attribute"].Value, "data-content-key")
.Replace(match.Groups["udi"].Value, guidUdi.Guid.ToString("D"))
: string.Empty);
// Re-apply block UDI→key conversion in case ConvertRichTextEditorProperties missed any
// (e.g. under recursive / nested block structures the primary migration could leave behind).
newMarkup = RteBlockHelper.ConvertBlockUdisToKeys(newMarkup);

if (newMarkup.Equals(richTextValue.Markup) == false)
{
Expand All @@ -85,17 +79,3 @@ public bool ProcessRichText(
return hasChanged;
}
}

/// <summary>
/// Provides helper methods for processing rich text editor (RTE) blocks containing local links during the upgrade to Umbraco version 15.0.0.
/// </summary>
[Obsolete("Scheduled for removal in Umbraco 18.")]
public static partial class RteBlockHelper
{
/// <summary>
/// Returns a <see cref="Regex"/> that matches <c>umb-rte-block</c> elements containing a <c>data-content-udi</c> attribute in the input HTML.
/// </summary>
/// <returns>A <see cref="Regex"/> instance for identifying <c>umb-rte-block</c> elements with a <c>data-content-udi</c> attribute.</returns>
[GeneratedRegex("<umb-rte-block.*(?<attribute>data-content-udi)=\"(?<udi>.[^\"]*)\".*<\\/umb-rte-block")]
public static partial Regex BlockRegex();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
using System.Text.RegularExpressions;
using Umbraco.Cms.Core;

namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_15_0_0.LocalLinks;

/// <summary>
/// Provides helper methods for processing rich text editor (RTE) block markup by rewriting block UDIs to keys.
/// </summary>
[Obsolete("Scheduled for removal in Umbraco 18.")]
public static partial class RteBlockHelper
{
/// <summary>
/// Returns a <see cref="Regex"/> that matches <c>umb-rte-block</c> elements containing a <c>data-content-udi</c> attribute in the input HTML.
/// </summary>
/// <returns>A <see cref="Regex"/> instance for identifying <c>umb-rte-block</c> elements with a <c>data-content-udi</c> attribute.</returns>
// Non-greedy on both [^>]*? and .*? so consecutive sibling <umb-rte-block> elements are matched
// individually rather than collapsed into one span (which left all-but-last sibling UDIs un-converted).
[GeneratedRegex("<umb-rte-block\\b[^>]*?(?<attribute>data-content-udi)=\"(?<udi>[^\"]+)\"[^>]*>.*?<\\/umb-rte-block>")]
public static partial Regex BlockRegex();

/// <summary>
/// Rewrites every <c>&lt;umb-rte-block&gt;</c> element in <paramref name="markup"/> from the legacy
/// <c>data-content-udi="umb://element/..."</c> form to the v15+ <c>data-content-key="&lt;guid&gt;"</c>
/// form.
/// </summary>
/// <remarks>
/// Blocks whose UDI fails to parse are <b>dropped</b> from the output rather than preserved.
/// This mirrors the original behaviour of <c>ConvertRichTextEditorProperties</c> and should not be
/// changed without considering migrated content that may contain malformed UDIs.
/// </remarks>
/// <param name="markup">The RTE markup to convert.</param>
/// <returns>The converted markup, or the input unchanged if no convertible blocks are present.</returns>
public static string ConvertBlockUdisToKeys(string markup) =>
BlockRegex().Replace(
markup,
match => UdiParser.TryParse(match.Groups["udi"].Value, out GuidUdi? guidUdi)
? match.Value
.Replace(match.Groups["attribute"].Value, "data-content-key")
.Replace(match.Groups["udi"].Value, guidUdi.Guid.ToString("D"))
: string.Empty);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
// Copyright (c) Umbraco.
// See LICENSE for more details.

using NUnit.Framework;
using Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_15_0_0.LocalLinks;

namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Migrations;

/// <summary>
/// Tests for <see cref="RteBlockHelper"/> — the helper used by the v15 RTE block migration
/// (consumed by both <c>ConvertRichTextEditorProperties</c> and <c>LocalLinkRteProcessor</c>) to
/// rewrite legacy <c>data-content-udi</c> attributes on <c>&lt;umb-rte-block&gt;</c> elements to
/// the v15+ <c>data-content-key</c> form.
/// </summary>
[TestFixture]
#pragma warning disable CS0618 // RteBlockHelper is obsolete (scheduled for removal in Umbraco 18).
public class RteBlockHelperTests
{
[Test]
public void ConvertBlockUdisToKeys_SingleBlock_ConvertsUdiToHyphenatedGuidKey()
{
var input = @"<p>before</p><umb-rte-block data-content-udi=""umb://element/5e499fc237be4b1d974670526f3b00b7""><!--Umbraco-Block--></umb-rte-block><p>after</p>";

var result = RteBlockHelper.ConvertBlockUdisToKeys(input);

Assert.AreEqual(
@"<p>before</p><umb-rte-block data-content-key=""5e499fc2-37be-4b1d-9746-70526f3b00b7""><!--Umbraco-Block--></umb-rte-block><p>after</p>",
result);
}

/// <summary>
/// Two direct-sibling blocks must each be converted independently.
/// </summary>
[Test]
public void ConvertBlockUdisToKeys_TwoConsecutiveSiblingBlocks_ConvertsEachIndividually()
{
var input =
@"<umb-rte-block data-content-udi=""umb://element/5e499fc237be4b1d974670526f3b00b7""><!--Umbraco-Block--></umb-rte-block>" +
@"<umb-rte-block data-content-udi=""umb://element/9db16c0d251e414c874967090f2cf3cc""><!--Umbraco-Block--></umb-rte-block>";

var result = RteBlockHelper.ConvertBlockUdisToKeys(input);

Assert.AreEqual(
@"<umb-rte-block data-content-key=""5e499fc2-37be-4b1d-9746-70526f3b00b7""><!--Umbraco-Block--></umb-rte-block>" +
@"<umb-rte-block data-content-key=""9db16c0d-251e-414c-8749-67090f2cf3cc""><!--Umbraco-Block--></umb-rte-block>",
result);
}

Check warning on line 47 in tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/RteBlockHelperTests.cs

View check run for this annotation

CodeScene Delta Analysis / CodeScene Code Health Review (v17/dev)

❌ New issue: Code Duplication

The module contains 2 functions with similar structure: ConvertBlockUdisToKeys_SiblingBlocksSeparatedByParagraph_ConvertsEachIndividually,ConvertBlockUdisToKeys_TwoConsecutiveSiblingBlocks_ConvertsEachIndividually. Avoid duplicated, aka copy-pasted, code inside the module. More duplication lowers the code health.

/// <summary>
/// Exact reproducer for the markup in https://github.com/umbraco/Umbraco-CMS/issues/22979.
/// </summary>
[Test]
public void ConvertBlockUdisToKeys_ThreeConsecutiveSiblingBlocks_ConvertsEachIndividually()
{
var input =
@"<p>...</p>" +
@"<umb-rte-block data-content-udi=""umb://element/5e499fc237be4b1d974670526f3b00b7""><!--Umbraco-Block--></umb-rte-block>" +
@"<umb-rte-block data-content-udi=""umb://element/9db16c0d251e414c874967090f2cf3cc""><!--Umbraco-Block--></umb-rte-block>" +
@"<umb-rte-block data-content-udi=""umb://element/c2c956b94a0945f29a1a366ad488545b""><!--Umbraco-Block--></umb-rte-block>" +
@"<p> </p>";

var result = RteBlockHelper.ConvertBlockUdisToKeys(input);

Assert.AreEqual(
@"<p>...</p>" +
@"<umb-rte-block data-content-key=""5e499fc2-37be-4b1d-9746-70526f3b00b7""><!--Umbraco-Block--></umb-rte-block>" +
@"<umb-rte-block data-content-key=""9db16c0d-251e-414c-8749-67090f2cf3cc""><!--Umbraco-Block--></umb-rte-block>" +
@"<umb-rte-block data-content-key=""c2c956b9-4a09-45f2-9a1a-366ad488545b""><!--Umbraco-Block--></umb-rte-block>" +
@"<p> </p>",
result);
}

/// <summary>
/// Blocks separated by intervening HTML must be converted individually.
/// </summary>
[Test]
public void ConvertBlockUdisToKeys_SiblingBlocksSeparatedByParagraph_ConvertsEachIndividually()
{
var input =
@"<umb-rte-block data-content-udi=""umb://element/5e499fc237be4b1d974670526f3b00b7""><!--Umbraco-Block--></umb-rte-block>" +
@"<p>between</p>" +
@"<umb-rte-block data-content-udi=""umb://element/9db16c0d251e414c874967090f2cf3cc""><!--Umbraco-Block--></umb-rte-block>";

var result = RteBlockHelper.ConvertBlockUdisToKeys(input);

Assert.AreEqual(
@"<umb-rte-block data-content-key=""5e499fc2-37be-4b1d-9746-70526f3b00b7""><!--Umbraco-Block--></umb-rte-block>" +
@"<p>between</p>" +
@"<umb-rte-block data-content-key=""9db16c0d-251e-414c-8749-67090f2cf3cc""><!--Umbraco-Block--></umb-rte-block>",
result);
}

/// <summary>
/// A mix of isolated and consecutive sibling blocks must all be converted independently.
/// </summary>
[Test]
public void ConvertBlockUdisToKeys_MixedLayoutWithConsecutiveAndSeparatedBlocks_ConvertsEachIndividually()
{
var input =
@"<umb-rte-block data-content-udi=""umb://element/11111111111111111111111111111111""><!--Umbraco-Block--></umb-rte-block>" +
@"<p>paragraph</p>" +
@"<umb-rte-block data-content-udi=""umb://element/22222222222222222222222222222222""><!--Umbraco-Block--></umb-rte-block>" +
@"<umb-rte-block data-content-udi=""umb://element/33333333333333333333333333333333""><!--Umbraco-Block--></umb-rte-block>" +
@"<p>another</p>" +
@"<umb-rte-block data-content-udi=""umb://element/44444444444444444444444444444444""><!--Umbraco-Block--></umb-rte-block>";

var result = RteBlockHelper.ConvertBlockUdisToKeys(input);

Assert.AreEqual(
@"<umb-rte-block data-content-key=""11111111-1111-1111-1111-111111111111""><!--Umbraco-Block--></umb-rte-block>" +
@"<p>paragraph</p>" +
@"<umb-rte-block data-content-key=""22222222-2222-2222-2222-222222222222""><!--Umbraco-Block--></umb-rte-block>" +
@"<umb-rte-block data-content-key=""33333333-3333-3333-3333-333333333333""><!--Umbraco-Block--></umb-rte-block>" +
@"<p>another</p>" +
@"<umb-rte-block data-content-key=""44444444-4444-4444-4444-444444444444""><!--Umbraco-Block--></umb-rte-block>",
result);
}

/// <summary>
/// When a block's UDI fails to parse, the block is dropped from the markup.
/// </summary>
[Test]
public void ConvertBlockUdisToKeys_BlockWithMalformedUdi_IsDroppedFromOutput()
{
var input =
@"<p>before</p>" +
@"<umb-rte-block data-content-udi=""not-a-valid-udi""><!--Umbraco-Block--></umb-rte-block>" +
@"<p>after</p>";

var result = RteBlockHelper.ConvertBlockUdisToKeys(input);

Assert.AreEqual(
@"<p>before</p><p>after</p>",
result);
}

[Test]
public void ConvertBlockUdisToKeys_MarkupWithoutBlocks_ReturnsUnchanged()
{
var input = @"<p>Hello</p><p>World</p>";

var result = RteBlockHelper.ConvertBlockUdisToKeys(input);

Assert.AreEqual(input, result);
}

/// <summary>
/// Markup already in the new key form (no UDI attribute) is returned unchanged.
/// </summary>
[Test]
public void ConvertBlockUdisToKeys_MarkupAlreadyInNewKeyForm_ReturnsUnchanged()
{
var input =
@"<umb-rte-block data-content-key=""5e499fc2-37be-4b1d-9746-70526f3b00b7""><!--Umbraco-Block--></umb-rte-block>" +
@"<umb-rte-block data-content-key=""9db16c0d-251e-414c-8749-67090f2cf3cc""><!--Umbraco-Block--></umb-rte-block>";

var result = RteBlockHelper.ConvertBlockUdisToKeys(input);

Assert.AreEqual(input, result);
}

/// <summary>
/// The regex finds one match per block when several appear as direct siblings.
/// </summary>
[Test]
public void BlockRegex_ThreeConsecutiveSiblingBlocks_ProducesOneMatchPerBlock()
{
var input =
@"<umb-rte-block data-content-udi=""umb://element/5e499fc237be4b1d974670526f3b00b7""><!--Umbraco-Block--></umb-rte-block>" +
@"<umb-rte-block data-content-udi=""umb://element/9db16c0d251e414c874967090f2cf3cc""><!--Umbraco-Block--></umb-rte-block>" +
@"<umb-rte-block data-content-udi=""umb://element/c2c956b94a0945f29a1a366ad488545b""><!--Umbraco-Block--></umb-rte-block>";

Assert.AreEqual(3, RteBlockHelper.BlockRegex().Matches(input).Count);
}
}
#pragma warning restore CS0618
Loading