From 4c05a114c5b582eef5ebb36e1e38312214b910b3 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Wed, 15 Oct 2025 13:10:39 +0100 Subject: [PATCH 1/4] Fixes 20476 - Changes icon to be no entry sign (#20496) --- .../documents/documents/entity-sign/is-protected/manifest.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-sign/is-protected/manifest.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-sign/is-protected/manifest.ts index a9ef60b149..a87addb16a 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-sign/is-protected/manifest.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-sign/is-protected/manifest.ts @@ -9,7 +9,7 @@ export const manifests: UmbExtensionManifest = { forEntityFlags: ['Umb.IsProtected'], weight: 1000, meta: { - iconName: 'icon-lock', + iconName: 'icon-block', label: 'Protected', iconColorAlias: 'red', }, From a504fd1ef88d0ce926e865a317af91ec6dd247ce Mon Sep 17 00:00:00 2001 From: Laura Neto <12862535+lauraneto@users.noreply.github.com> Date: Thu, 16 Oct 2025 08:22:11 +0200 Subject: [PATCH 2/4] Bump version to 16.3.0 --- src/Umbraco.Web.UI.Client/package-lock.json | 4 ++-- src/Umbraco.Web.UI.Client/package.json | 2 +- version.json | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/package-lock.json b/src/Umbraco.Web.UI.Client/package-lock.json index 33947947a9..71e0a69db2 100644 --- a/src/Umbraco.Web.UI.Client/package-lock.json +++ b/src/Umbraco.Web.UI.Client/package-lock.json @@ -1,12 +1,12 @@ { "name": "@umbraco-cms/backoffice", - "version": "16.3.0-rc", + "version": "16.3.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@umbraco-cms/backoffice", - "version": "16.3.0-rc", + "version": "16.3.0", "license": "MIT", "workspaces": [ "./src/packages/*", diff --git a/src/Umbraco.Web.UI.Client/package.json b/src/Umbraco.Web.UI.Client/package.json index 3c831031ac..de785cc205 100644 --- a/src/Umbraco.Web.UI.Client/package.json +++ b/src/Umbraco.Web.UI.Client/package.json @@ -1,7 +1,7 @@ { "name": "@umbraco-cms/backoffice", "license": "MIT", - "version": "16.3.0-rc", + "version": "16.3.0", "type": "module", "exports": { ".": null, diff --git a/version.json b/version.json index cd1e47b49d..e71c12fcd9 100644 --- a/version.json +++ b/version.json @@ -1,6 +1,6 @@ { "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/main/src/NerdBank.GitVersioning/version.schema.json", - "version": "16.3.0-rc4", + "version": "16.3.0", "assemblyVersion": { "precision": "build" }, From 369b020d9db0315ce88c6e2835509639267c3444 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Thu, 16 Oct 2025 12:56:32 +0200 Subject: [PATCH 3/4] Explicitly flush isolated caches by key for content updates (#20519) * Explicitly flush isolated caches by key for content updates * Apply suggestions from code review --------- Co-authored-by: Andy Butland --- .../Implement/DocumentRepository.cs | 5 + .../Repositories/Implement/MediaRepository.cs | 5 + ...ontentServiceNotificationWithCacheTests.cs | 148 ++++++++++++++++++ .../MediaServiceNotificationWithCacheTests.cs | 146 +++++++++++++++++ 4 files changed, 304 insertions(+) create mode 100644 tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentServiceNotificationWithCacheTests.cs create mode 100644 tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/MediaServiceNotificationWithCacheTests.cs diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentRepository.cs index 80b0796635..8cccdd9247 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentRepository.cs @@ -1328,6 +1328,11 @@ public class DocumentRepository : ContentRepositoryBase(entity.Key)); + // troubleshooting //if (Database.ExecuteScalar($"SELECT COUNT(*) FROM {Constants.DatabaseSchema.Tables.DocumentVersion} JOIN {Constants.DatabaseSchema.Tables.ContentVersion} ON {Constants.DatabaseSchema.Tables.DocumentVersion}.id={Constants.DatabaseSchema.Tables.ContentVersion}.id WHERE published=1 AND nodeId=" + content.Id) > 1) //{ diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MediaRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MediaRepository.cs index d08607ec09..75dc8a3eb7 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MediaRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MediaRepository.cs @@ -480,6 +480,11 @@ public class MediaRepository : ContentRepositoryBase(entity.Key)); } protected override void PersistDeletedItem(IMedia entity) diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentServiceNotificationWithCacheTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentServiceNotificationWithCacheTests.cs new file mode 100644 index 0000000000..8bfe6859e7 --- /dev/null +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentServiceNotificationWithCacheTests.cs @@ -0,0 +1,148 @@ +using Microsoft.Extensions.DependencyInjection; +using Moq; +using NUnit.Framework; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Cache; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.ContentEditing; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement; +using Umbraco.Cms.Tests.Common.Builders; +using Umbraco.Cms.Tests.Common.Testing; +using Umbraco.Cms.Tests.Integration.Testing; + +namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services; + +[TestFixture] +[UmbracoTest( + Database = UmbracoTestOptions.Database.NewSchemaPerTest, + PublishedRepositoryEvents = true, + WithApplication = true, + Logger = UmbracoTestOptions.Logger.Console)] +internal sealed class ContentServiceNotificationWithCacheTests : UmbracoIntegrationTest +{ + private IContentType _contentType; + + private IContentTypeService ContentTypeService => GetRequiredService(); + + private IContentService ContentService => GetRequiredService(); + + private IContentEditingService ContentEditingService => GetRequiredService(); + + protected override void ConfigureTestServices(IServiceCollection services) + => services.AddSingleton(AppCaches.Create(Mock.Of())); + + [SetUp] + public async Task SetupTest() + { + ContentRepositoryBase.ThrowOnWarning = true; + + _contentType = ContentTypeBuilder.CreateBasicContentType(); + _contentType.AllowedAsRoot = true; + await ContentTypeService.CreateAsync(_contentType, Constants.Security.SuperUserKey); + } + + [TearDown] + public void Teardown() => ContentRepositoryBase.ThrowOnWarning = false; + + protected override void CustomTestSetup(IUmbracoBuilder builder) => builder + .AddNotificationHandler() + .AddNotificationHandler(); + + [Test] + public async Task Saving_Saved_Get_Value() + { + var createAttempt = await ContentEditingService.CreateAsync( + new ContentCreateModel + { + ContentTypeKey = _contentType.Key, + Variants = [ + new() { Name = "Initial name" } + ], + }, + Constants.Security.SuperUserKey); + + Assert.Multiple(() => + { + Assert.IsTrue(createAttempt.Success); + Assert.IsNotNull(createAttempt.Result.Content); + }); + + var savingWasCalled = false; + var savedWasCalled = false; + + ContentNotificationHandler.SavingContent = notification => + { + savingWasCalled = true; + + var saved = notification.SavedEntities.First(); + var documentById = ContentService.GetById(saved.Id)!; + var documentByKey = ContentService.GetById(saved.Key)!; + + Assert.Multiple(() => + { + Assert.AreEqual("Updated name", saved.Name); + Assert.AreEqual("Initial name", documentById.Name); + Assert.AreEqual("Initial name", documentByKey.Name); + }); + }; + + ContentNotificationHandler.SavedContent = notification => + { + savedWasCalled = true; + + var saved = notification.SavedEntities.First(); + var documentById = ContentService.GetById(saved.Id)!; + var documentByKey = ContentService.GetById(saved.Key)!; + + Assert.Multiple(() => + { + Assert.AreEqual("Updated name", saved.Name); + Assert.AreEqual("Updated name", documentById.Name); + Assert.AreEqual("Updated name", documentByKey.Name); + }); + }; + + try + { + var updateAttempt = await ContentEditingService.UpdateAsync( + createAttempt.Result.Content!.Key, + new ContentUpdateModel + { + Variants = [ + new() { Name = "Updated name" } + ], + }, + Constants.Security.SuperUserKey); + + Assert.Multiple(() => + { + Assert.IsTrue(updateAttempt.Success); + Assert.IsNotNull(updateAttempt.Result.Content); + }); + + Assert.IsTrue(savingWasCalled); + Assert.IsTrue(savedWasCalled); + } + finally + { + ContentNotificationHandler.SavingContent = null; + ContentNotificationHandler.SavedContent = null; + } + } + + internal sealed class ContentNotificationHandler : + INotificationHandler, + INotificationHandler + { + public static Action? SavingContent { get; set; } + + public static Action? SavedContent { get; set; } + + public void Handle(ContentSavedNotification notification) => SavedContent?.Invoke(notification); + + public void Handle(ContentSavingNotification notification) => SavingContent?.Invoke(notification); + } +} diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/MediaServiceNotificationWithCacheTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/MediaServiceNotificationWithCacheTests.cs new file mode 100644 index 0000000000..d4de99daba --- /dev/null +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/MediaServiceNotificationWithCacheTests.cs @@ -0,0 +1,146 @@ +using Microsoft.Extensions.DependencyInjection; +using Moq; +using NUnit.Framework; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Cache; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.ContentEditing; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement; +using Umbraco.Cms.Tests.Common.Testing; +using Umbraco.Cms.Tests.Integration.Testing; + +namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services; + +[TestFixture] +[UmbracoTest( + Database = UmbracoTestOptions.Database.NewSchemaPerTest, + PublishedRepositoryEvents = true, + WithApplication = true, + Logger = UmbracoTestOptions.Logger.Console)] +internal sealed class MediaServiceNotificationWithCacheTests : UmbracoIntegrationTest +{ + private IMediaType _mediaType; + + private IMediaTypeService MediaTypeService => GetRequiredService(); + + private IMediaService MediaService => GetRequiredService(); + + private IMediaEditingService MediaEditingService => GetRequiredService(); + + protected override void ConfigureTestServices(IServiceCollection services) + => services.AddSingleton(AppCaches.Create(Mock.Of())); + + [SetUp] + public void SetupTest() + { + ContentRepositoryBase.ThrowOnWarning = true; + + _mediaType = MediaTypeService.Get("folder") + ?? throw new ApplicationException("Could not find the \"folder\" media type"); + } + + [TearDown] + public void Teardown() => ContentRepositoryBase.ThrowOnWarning = false; + + protected override void CustomTestSetup(IUmbracoBuilder builder) => builder + .AddNotificationHandler() + .AddNotificationHandler(); + + [Test] + public async Task Saving_Saved_Get_Value() + { + var createAttempt = await MediaEditingService.CreateAsync( + new MediaCreateModel + { + ContentTypeKey = _mediaType.Key, + Variants = [ + new() { Name = "Initial name" } + ], + }, + Constants.Security.SuperUserKey); + + Assert.Multiple(() => + { + Assert.IsTrue(createAttempt.Success); + Assert.IsNotNull(createAttempt.Result.Content); + }); + + var savingWasCalled = false; + var savedWasCalled = false; + + MediaNotificationHandler.SavingMedia = notification => + { + savingWasCalled = true; + + var saved = notification.SavedEntities.First(); + var documentById = MediaService.GetById(saved.Id)!; + var documentByKey = MediaService.GetById(saved.Key)!; + + Assert.Multiple(() => + { + Assert.AreEqual("Updated name", saved.Name); + Assert.AreEqual("Initial name", documentById.Name); + Assert.AreEqual("Initial name", documentByKey.Name); + }); + }; + + MediaNotificationHandler.SavedMedia = notification => + { + savedWasCalled = true; + + var saved = notification.SavedEntities.First(); + var documentById = MediaService.GetById(saved.Id)!; + var documentByKey = MediaService.GetById(saved.Key)!; + + Assert.Multiple(() => + { + Assert.AreEqual("Updated name", saved.Name); + Assert.AreEqual("Updated name", documentById.Name); + Assert.AreEqual("Updated name", documentByKey.Name); + }); + }; + + try + { + var updateAttempt = await MediaEditingService.UpdateAsync( + createAttempt.Result.Content!.Key, + new MediaUpdateModel + { + Variants = [ + new() { Name = "Updated name" } + ], + }, + Constants.Security.SuperUserKey); + + Assert.Multiple(() => + { + Assert.IsTrue(updateAttempt.Success); + Assert.IsNotNull(updateAttempt.Result.Content); + }); + + Assert.IsTrue(savingWasCalled); + Assert.IsTrue(savedWasCalled); + } + finally + { + MediaNotificationHandler.SavingMedia = null; + MediaNotificationHandler.SavedMedia = null; + } + } + + internal sealed class MediaNotificationHandler : + INotificationHandler, + INotificationHandler + { + public static Action? SavingMedia { get; set; } + + public static Action? SavedMedia { get; set; } + + public void Handle(MediaSavedNotification notification) => SavedMedia?.Invoke(notification); + + public void Handle(MediaSavingNotification notification) => SavingMedia?.Invoke(notification); + } +} From 31bcbc1147f66cab1837a9afe4732ad0bb35b959 Mon Sep 17 00:00:00 2001 From: Ben White Date: Thu, 16 Oct 2025 20:06:10 +0100 Subject: [PATCH 4/4] Don't use non-generic ILogger as a fallback in BlockEditorPropertyValueEditor (#20532) Update logger service retrieval in BlockEditorPropertyValueEditor --- .../PropertyEditors/BlockEditorPropertyValueEditor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Infrastructure/PropertyEditors/BlockEditorPropertyValueEditor.cs b/src/Umbraco.Infrastructure/PropertyEditors/BlockEditorPropertyValueEditor.cs index 10c752088b..84299d6399 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/BlockEditorPropertyValueEditor.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/BlockEditorPropertyValueEditor.cs @@ -51,7 +51,7 @@ public abstract class BlockEditorPropertyValueEditor : BlockVal languageService, ioHelper, attribute, - StaticServiceProvider.Instance.GetRequiredService()) + StaticServiceProvider.Instance.GetRequiredService>>()) { }