Migrations: Handles rich text blocks created with TinyMCE in convert local links migration and refreshes internal datatype cache following migration requiring cache rebuild (closes #20885) (#20887)

Handles rich text blocks created with TinyMCE in convert local links migration.
Refreshes internal datatype cache following migration requiring cache rebuild.
This commit is contained in:
Andy Butland
2025-11-19 14:54:12 +01:00
committed by GitHub
parent 386611bc70
commit a488d77ce7
7 changed files with 82 additions and 6 deletions

View File

@@ -23,4 +23,11 @@ public class RichTextBlockValue : BlockValue<RichTextBlockLayoutItem>
/// <inheritdoc /> /// <inheritdoc />
[JsonIgnore] [JsonIgnore]
public override string PropertyEditorAlias => Constants.PropertyEditors.Aliases.RichText; public override string PropertyEditorAlias => Constants.PropertyEditors.Aliases.RichText;
/// <inheritdoc />
#pragma warning disable CS0672 // Member overrides obsolete member
#pragma warning disable CS0618 // Type or member is obsolete
public override bool SupportsBlockLayoutAlias(string alias) => base.SupportsBlockLayoutAlias(alias) || alias.Equals("Umbraco.TinyMCE");
#pragma warning restore CS0618 // Type or member is obsolete
#pragma warning restore CS0672 // Member overrides obsolete member
} }

View File

@@ -53,6 +53,12 @@ public interface IPublishedContentTypeFactory
/// </summary> /// </summary>
PublishedDataType GetDataType(int id); PublishedDataType GetDataType(int id);
/// <summary>
/// Clears the internal data type cache.
/// </summary>
void ClearDataTypeCache()
{ }
/// <summary> /// <summary>
/// Notifies the factory of datatype changes. /// Notifies the factory of datatype changes.
/// </summary> /// </summary>

View File

@@ -65,6 +65,22 @@ public class PublishedContentTypeFactory : IPublishedContentTypeFactory
return dataType; return dataType;
} }
/// <inheritdoc />
public void ClearDataTypeCache()
{
if (_publishedDataTypes is null)
{
// Not initialized yet, so skip and avoid lock
return;
}
lock (_publishedDataTypesLocker)
{
// Clear cache (and let it lazy initialize again later)
_publishedDataTypes = null;
}
}
/// <inheritdoc /> /// <inheritdoc />
public void NotifyDataTypeChanges(params int[] ids) public void NotifyDataTypeChanges(params int[] ids)
{ {

View File

@@ -3,7 +3,9 @@ using Microsoft.Extensions.Logging;
using OpenIddict.Abstractions; using OpenIddict.Abstractions;
using Umbraco.Cms.Core; using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Cache;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Migrations; using Umbraco.Cms.Core.Migrations;
using Umbraco.Cms.Core.Models.PublishedContent;
using Umbraco.Cms.Core.PublishedCache; using Umbraco.Cms.Core.PublishedCache;
using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Core.Scoping;
using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Services;
@@ -47,9 +49,12 @@ public class MigrationPlanExecutor : IMigrationPlanExecutor
private readonly DistributedCache _distributedCache; private readonly DistributedCache _distributedCache;
private readonly IScopeAccessor _scopeAccessor; private readonly IScopeAccessor _scopeAccessor;
private readonly ICoreScopeProvider _scopeProvider; private readonly ICoreScopeProvider _scopeProvider;
private readonly IPublishedContentTypeFactory _publishedContentTypeFactory;
private bool _rebuildCache; private bool _rebuildCache;
private bool _invalidateBackofficeUserAccess; private bool _invalidateBackofficeUserAccess;
[Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 19.")]
public MigrationPlanExecutor( public MigrationPlanExecutor(
ICoreScopeProvider scopeProvider, ICoreScopeProvider scopeProvider,
IScopeAccessor scopeAccessor, IScopeAccessor scopeAccessor,
@@ -61,6 +66,33 @@ public class MigrationPlanExecutor : IMigrationPlanExecutor
IKeyValueService keyValueService, IKeyValueService keyValueService,
IServiceScopeFactory serviceScopeFactory, IServiceScopeFactory serviceScopeFactory,
AppCaches appCaches) AppCaches appCaches)
: this(
scopeProvider,
scopeAccessor,
loggerFactory,
migrationBuilder,
databaseFactory,
databaseCacheRebuilder,
distributedCache,
keyValueService,
serviceScopeFactory,
appCaches,
StaticServiceProvider.Instance.GetRequiredService<IPublishedContentTypeFactory>())
{
}
public MigrationPlanExecutor(
ICoreScopeProvider scopeProvider,
IScopeAccessor scopeAccessor,
ILoggerFactory loggerFactory,
IMigrationBuilder migrationBuilder,
IUmbracoDatabaseFactory databaseFactory,
IDatabaseCacheRebuilder databaseCacheRebuilder,
DistributedCache distributedCache,
IKeyValueService keyValueService,
IServiceScopeFactory serviceScopeFactory,
AppCaches appCaches,
IPublishedContentTypeFactory publishedContentTypeFactory)
{ {
_scopeProvider = scopeProvider; _scopeProvider = scopeProvider;
_scopeAccessor = scopeAccessor; _scopeAccessor = scopeAccessor;
@@ -72,6 +104,7 @@ public class MigrationPlanExecutor : IMigrationPlanExecutor
_serviceScopeFactory = serviceScopeFactory; _serviceScopeFactory = serviceScopeFactory;
_appCaches = appCaches; _appCaches = appCaches;
_distributedCache = distributedCache; _distributedCache = distributedCache;
_publishedContentTypeFactory = publishedContentTypeFactory;
_logger = _loggerFactory.CreateLogger<MigrationPlanExecutor>(); _logger = _loggerFactory.CreateLogger<MigrationPlanExecutor>();
} }
@@ -269,6 +302,7 @@ public class MigrationPlanExecutor : IMigrationPlanExecutor
_appCaches.IsolatedCaches.ClearAllCaches(); _appCaches.IsolatedCaches.ClearAllCaches();
await _databaseCacheRebuilder.RebuildAsync(false); await _databaseCacheRebuilder.RebuildAsync(false);
_distributedCache.RefreshAllPublishedSnapshot(); _distributedCache.RefreshAllPublishedSnapshot();
_publishedContentTypeFactory.ClearDataTypeCache();
} }
private async Task RevokeBackofficeTokens() private async Task RevokeBackofficeTokens()

View File

@@ -10,6 +10,7 @@ using Umbraco.Cms.Core.Cache;
using Umbraco.Cms.Core.Configuration; using Umbraco.Cms.Core.Configuration;
using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Events;
using Umbraco.Cms.Core.Migrations; using Umbraco.Cms.Core.Migrations;
using Umbraco.Cms.Core.Models.PublishedContent;
using Umbraco.Cms.Core.PublishedCache; using Umbraco.Cms.Core.PublishedCache;
using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Core.Scoping;
using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Services;
@@ -38,6 +39,7 @@ internal sealed class AdvancedMigrationTests : UmbracoIntegrationTest
private IServiceScopeFactory ServiceScopeFactory => GetRequiredService<IServiceScopeFactory>(); private IServiceScopeFactory ServiceScopeFactory => GetRequiredService<IServiceScopeFactory>();
private DistributedCache DistributedCache => GetRequiredService<DistributedCache>(); private DistributedCache DistributedCache => GetRequiredService<DistributedCache>();
private IDatabaseCacheRebuilder DatabaseCacheRebuilder => GetRequiredService<IDatabaseCacheRebuilder>(); private IDatabaseCacheRebuilder DatabaseCacheRebuilder => GetRequiredService<IDatabaseCacheRebuilder>();
private IPublishedContentTypeFactory PublishedContentTypeFactory => GetRequiredService<IPublishedContentTypeFactory>();
private IMigrationPlanExecutor MigrationPlanExecutor => new MigrationPlanExecutor( private IMigrationPlanExecutor MigrationPlanExecutor => new MigrationPlanExecutor(
CoreScopeProvider, CoreScopeProvider,
ScopeAccessor, ScopeAccessor,
@@ -48,7 +50,8 @@ internal sealed class AdvancedMigrationTests : UmbracoIntegrationTest
DistributedCache, DistributedCache,
Mock.Of<IKeyValueService>(), Mock.Of<IKeyValueService>(),
ServiceScopeFactory, ServiceScopeFactory,
AppCaches.NoCache); AppCaches.NoCache,
PublishedContentTypeFactory);
[Test] [Test]
public async Task CreateTableOfTDtoAsync() public async Task CreateTableOfTDtoAsync()

View File

@@ -110,15 +110,16 @@ public class RichTextPropertyEditorHelperTests
Assert.IsNull(value.Blocks); Assert.IsNull(value.Blocks);
} }
[Test] [TestCase(Constants.PropertyEditors.Aliases.RichText)]
public void Can_Parse_Blocks_With_Both_Content_And_Settings() [TestCase("Umbraco.TinyMCE")]
public void Can_Parse_Blocks_With_Both_Content_And_Settings(string propertyEditorAlias)
{ {
const string input = """ string input = """
{ {
"markup": "<p>this is some markup</p><umb-rte-block data-content-key=\"36cc710a-d8a6-45d0-a07f-7bbd8742cf02\"><!--Umbraco-Block--></umb-rte-block>", "markup": "<p>this is some markup</p><umb-rte-block data-content-key=\"36cc710a-d8a6-45d0-a07f-7bbd8742cf02\"><!--Umbraco-Block--></umb-rte-block>",
"blocks": { "blocks": {
"layout": { "layout": {
"Umbraco.RichText": [{ "[PropertyEditorAlias]": [{
"contentKey": "36cc710a-d8a6-45d0-a07f-7bbd8742cf02", "contentKey": "36cc710a-d8a6-45d0-a07f-7bbd8742cf02",
"settingsKey": "d2eeef66-4111-42f4-a164-7a523eaffbc2" "settingsKey": "d2eeef66-4111-42f4-a164-7a523eaffbc2"
} }
@@ -143,6 +144,7 @@ public class RichTextPropertyEditorHelperTests
} }
} }
"""; """;
input = input.Replace("[PropertyEditorAlias]", propertyEditorAlias);
var result = RichTextPropertyEditorHelper.TryParseRichTextEditorValue(input, JsonSerializer(), Logger(), out RichTextEditorValue? value); var result = RichTextPropertyEditorHelper.TryParseRichTextEditorValue(input, JsonSerializer(), Logger(), out RichTextEditorValue? value);
Assert.IsTrue(result); Assert.IsTrue(result);
@@ -180,6 +182,12 @@ public class RichTextPropertyEditorHelperTests
Assert.AreEqual("settingsPropertyAlias", settingsProperties.First().Alias); Assert.AreEqual("settingsPropertyAlias", settingsProperties.First().Alias);
Assert.AreEqual("A settings property value", settingsProperties.First().Value); Assert.AreEqual("A settings property value", settingsProperties.First().Value);
}); });
Assert.IsTrue(value.Blocks.Layout.ContainsKey(Constants.PropertyEditors.Aliases.RichText));
var layout = value.Blocks.Layout[Constants.PropertyEditors.Aliases.RichText];
Assert.AreEqual(1, layout.Count());
Assert.AreEqual(Guid.Parse("36cc710a-d8a6-45d0-a07f-7bbd8742cf02"), layout.First().ContentKey);
Assert.AreEqual(Guid.Parse("d2eeef66-4111-42f4-a164-7a523eaffbc2"), layout.First().SettingsKey);
} }
[Test] [Test]

View File

@@ -12,6 +12,7 @@ using NUnit.Framework;
using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Cache;
using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Events;
using Umbraco.Cms.Core.Models.PublishedContent;
using Umbraco.Cms.Core.PublishedCache; using Umbraco.Cms.Core.PublishedCache;
using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Sync; using Umbraco.Cms.Core.Sync;
@@ -85,7 +86,8 @@ public class MigrationPlanTests
distributedCache, distributedCache,
Mock.Of<IKeyValueService>(), Mock.Of<IKeyValueService>(),
Mock.Of<IServiceScopeFactory>(), Mock.Of<IServiceScopeFactory>(),
appCaches); appCaches,
Mock.Of<IPublishedContentTypeFactory>());
var plan = new MigrationPlan("default") var plan = new MigrationPlan("default")
.From(string.Empty) .From(string.Empty)