From 95c9d48b6e2ef94c86d67e1d807a779d58615ca6 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Tue, 18 Nov 2025 15:36:18 +0100 Subject: [PATCH 1/3] Rendering: Don't cache RTE value conversion (closes #20867) (#20880) Don't cache RTE rendering. --- .../ValueConverters/RteBlockRenderingValueConverter.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/RteBlockRenderingValueConverter.cs b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/RteBlockRenderingValueConverter.cs index 9e3c719f5e..2ec5b96b9c 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/RteBlockRenderingValueConverter.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/RteBlockRenderingValueConverter.cs @@ -72,8 +72,8 @@ public class RteBlockRenderingValueConverter : SimpleRichTextValueConverter, IDe public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) => // because that version of RTE converter parses {locallink} and renders blocks, its value has - // to be cached at the published snapshot level, because we have no idea what the block renderings may depend on actually. - PropertyCacheLevel.Snapshot; + // to be re-rendered at request time, because we have no idea what the block renderings may depend on actually. + PropertyCacheLevel.None; /// public override bool? IsValue(object? value, PropertyValueLevel level) @@ -118,7 +118,7 @@ public class RteBlockRenderingValueConverter : SimpleRichTextValueConverter, IDe public PropertyCacheLevel GetDeliveryApiPropertyCacheLevel(IPublishedPropertyType propertyType) => PropertyCacheLevel.Elements; - public PropertyCacheLevel GetDeliveryApiPropertyCacheLevelForExpansion(IPublishedPropertyType propertyType) => PropertyCacheLevel.Snapshot; + public PropertyCacheLevel GetDeliveryApiPropertyCacheLevelForExpansion(IPublishedPropertyType propertyType) => PropertyCacheLevel.None; public Type GetDeliveryApiPropertyValueType(IPublishedPropertyType propertyType) => _deliveryApiSettings.RichTextOutputAsJson From cc8d90be9153f4c3dce6160fabe23937599aa376 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Wed, 19 Nov 2025 10:43:09 +0100 Subject: [PATCH 2/3] Cherry picked 20733 --- .../core/entity-sign/components/entity-sign-bundle.element.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity-sign/components/entity-sign-bundle.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity-sign/components/entity-sign-bundle.element.ts index 731e8ba923..a872df301c 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/entity-sign/components/entity-sign-bundle.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity-sign/components/entity-sign-bundle.element.ts @@ -170,9 +170,9 @@ export class UmbEntitySignBundleElement extends UmbLitElement { .infobox { position: absolute; - top: 100%; + top: 0; margin-top: calc(-12px + var(--offset-h)); - left: 100%; + left: 19px; margin-left: -6px; background-color: transparent; padding: var(--uui-size-2); From 745d74104e521899a4fea18e93bced57edeab652 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Wed, 19 Nov 2025 14:54:12 +0100 Subject: [PATCH 3/3] 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. --- .../Models/Blocks/RichTextBlockValue.cs | 7 ++++ .../IPublishedContentTypeFactory.cs | 6 ++++ .../PublishedContentTypeFactory.cs | 16 +++++++++ .../Migrations/MigrationPlanExecutor.cs | 34 +++++++++++++++++++ .../Migrations/AdvancedMigrationTests.cs | 5 ++- .../RichTextPropertyEditorHelperTests.cs | 16 ++++++--- .../Migrations/MigrationPlanTests.cs | 4 ++- 7 files changed, 82 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Core/Models/Blocks/RichTextBlockValue.cs b/src/Umbraco.Core/Models/Blocks/RichTextBlockValue.cs index 6cbab55693..3053e02d8b 100644 --- a/src/Umbraco.Core/Models/Blocks/RichTextBlockValue.cs +++ b/src/Umbraco.Core/Models/Blocks/RichTextBlockValue.cs @@ -23,4 +23,11 @@ public class RichTextBlockValue : BlockValue /// [JsonIgnore] public override string PropertyEditorAlias => Constants.PropertyEditors.Aliases.RichText; + + /// +#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 } diff --git a/src/Umbraco.Core/Models/PublishedContent/IPublishedContentTypeFactory.cs b/src/Umbraco.Core/Models/PublishedContent/IPublishedContentTypeFactory.cs index 009666aab5..9a5a14d7d4 100644 --- a/src/Umbraco.Core/Models/PublishedContent/IPublishedContentTypeFactory.cs +++ b/src/Umbraco.Core/Models/PublishedContent/IPublishedContentTypeFactory.cs @@ -53,6 +53,12 @@ public interface IPublishedContentTypeFactory /// PublishedDataType GetDataType(int id); + /// + /// Clears the internal data type cache. + /// + void ClearDataTypeCache() + { } + /// /// Notifies the factory of datatype changes. /// diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentTypeFactory.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentTypeFactory.cs index 32fab1b539..7ea8779718 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedContentTypeFactory.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentTypeFactory.cs @@ -65,6 +65,22 @@ public class PublishedContentTypeFactory : IPublishedContentTypeFactory return dataType; } + /// + 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; + } + } + /// public void NotifyDataTypeChanges(params int[] ids) { diff --git a/src/Umbraco.Infrastructure/Migrations/MigrationPlanExecutor.cs b/src/Umbraco.Infrastructure/Migrations/MigrationPlanExecutor.cs index 8ac9fa3e74..85a8eb8717 100644 --- a/src/Umbraco.Infrastructure/Migrations/MigrationPlanExecutor.cs +++ b/src/Umbraco.Infrastructure/Migrations/MigrationPlanExecutor.cs @@ -3,7 +3,9 @@ using Microsoft.Extensions.Logging; using OpenIddict.Abstractions; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Cache; +using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Migrations; +using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Cms.Core.PublishedCache; using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Core.Services; @@ -47,9 +49,12 @@ public class MigrationPlanExecutor : IMigrationPlanExecutor private readonly DistributedCache _distributedCache; private readonly IScopeAccessor _scopeAccessor; private readonly ICoreScopeProvider _scopeProvider; + private readonly IPublishedContentTypeFactory _publishedContentTypeFactory; + private bool _rebuildCache; private bool _invalidateBackofficeUserAccess; + [Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 19.")] public MigrationPlanExecutor( ICoreScopeProvider scopeProvider, IScopeAccessor scopeAccessor, @@ -61,6 +66,33 @@ public class MigrationPlanExecutor : IMigrationPlanExecutor IKeyValueService keyValueService, IServiceScopeFactory serviceScopeFactory, AppCaches appCaches) + : this( + scopeProvider, + scopeAccessor, + loggerFactory, + migrationBuilder, + databaseFactory, + databaseCacheRebuilder, + distributedCache, + keyValueService, + serviceScopeFactory, + appCaches, + StaticServiceProvider.Instance.GetRequiredService()) + { + } + + 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; _scopeAccessor = scopeAccessor; @@ -72,6 +104,7 @@ public class MigrationPlanExecutor : IMigrationPlanExecutor _serviceScopeFactory = serviceScopeFactory; _appCaches = appCaches; _distributedCache = distributedCache; + _publishedContentTypeFactory = publishedContentTypeFactory; _logger = _loggerFactory.CreateLogger(); } @@ -269,6 +302,7 @@ public class MigrationPlanExecutor : IMigrationPlanExecutor _appCaches.IsolatedCaches.ClearAllCaches(); await _databaseCacheRebuilder.RebuildAsync(false); _distributedCache.RefreshAllPublishedSnapshot(); + _publishedContentTypeFactory.ClearDataTypeCache(); } private async Task RevokeBackofficeTokens() diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Migrations/AdvancedMigrationTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Migrations/AdvancedMigrationTests.cs index b6fe2f48ed..444f94c218 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Migrations/AdvancedMigrationTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Migrations/AdvancedMigrationTests.cs @@ -10,6 +10,7 @@ using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Configuration; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Migrations; +using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Cms.Core.PublishedCache; using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Core.Services; @@ -38,6 +39,7 @@ internal sealed class AdvancedMigrationTests : UmbracoIntegrationTest private IServiceScopeFactory ServiceScopeFactory => GetRequiredService(); private DistributedCache DistributedCache => GetRequiredService(); private IDatabaseCacheRebuilder DatabaseCacheRebuilder => GetRequiredService(); + private IPublishedContentTypeFactory PublishedContentTypeFactory => GetRequiredService(); private IMigrationPlanExecutor MigrationPlanExecutor => new MigrationPlanExecutor( CoreScopeProvider, ScopeAccessor, @@ -48,7 +50,8 @@ internal sealed class AdvancedMigrationTests : UmbracoIntegrationTest DistributedCache, Mock.Of(), ServiceScopeFactory, - AppCaches.NoCache); + AppCaches.NoCache, + PublishedContentTypeFactory); [Test] public async Task CreateTableOfTDtoAsync() diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/RichTextPropertyEditorHelperTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/RichTextPropertyEditorHelperTests.cs index 892e08cdd2..212d13687f 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/RichTextPropertyEditorHelperTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/RichTextPropertyEditorHelperTests.cs @@ -110,15 +110,16 @@ public class RichTextPropertyEditorHelperTests Assert.IsNull(value.Blocks); } - [Test] - public void Can_Parse_Blocks_With_Both_Content_And_Settings() + [TestCase(Constants.PropertyEditors.Aliases.RichText)] + [TestCase("Umbraco.TinyMCE")] + public void Can_Parse_Blocks_With_Both_Content_And_Settings(string propertyEditorAlias) { - const string input = """ + string input = """ { "markup": "

this is some markup

", "blocks": { "layout": { - "Umbraco.RichText": [{ + "[PropertyEditorAlias]": [{ "contentKey": "36cc710a-d8a6-45d0-a07f-7bbd8742cf02", "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); Assert.IsTrue(result); @@ -180,6 +182,12 @@ public class RichTextPropertyEditorHelperTests Assert.AreEqual("settingsPropertyAlias", settingsProperties.First().Alias); 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] diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/MigrationPlanTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/MigrationPlanTests.cs index 261d41128d..a38c342945 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/MigrationPlanTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/MigrationPlanTests.cs @@ -12,6 +12,7 @@ using NUnit.Framework; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Cms.Core.PublishedCache; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Sync; @@ -85,7 +86,8 @@ public class MigrationPlanTests distributedCache, Mock.Of(), Mock.Of(), - appCaches); + appCaches, + Mock.Of()); var plan = new MigrationPlan("default") .From(string.Empty)