diff --git a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockEditorVarianceHandler.cs b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockEditorVarianceHandler.cs index 003464819122..815bc0f1b08c 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockEditorVarianceHandler.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockEditorVarianceHandler.cs @@ -1,3 +1,5 @@ +using Microsoft.Extensions.DependencyInjection; +using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Blocks; using Umbraco.Cms.Core.Models.PublishedContent; @@ -9,10 +11,29 @@ namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters; public sealed class BlockEditorVarianceHandler { private readonly ILanguageService _languageService; + private readonly IContentTypeService _contentTypeService; + [Obsolete("Please use the constructor that accepts IContentTypeService. Will be removed in V16.")] public BlockEditorVarianceHandler(ILanguageService languageService) - => _languageService = languageService; + : this(languageService, StaticServiceProvider.Instance.GetRequiredService()) + { + } + public BlockEditorVarianceHandler(ILanguageService languageService, IContentTypeService contentTypeService) + { + _languageService = languageService; + _contentTypeService = contentTypeService; + } + + /// + /// Aligns a block property value for variance changes. + /// + /// The block property value to align. + /// The underlying property type. + /// The culture being handled (null if invariant). + /// + /// Used for aligning variance changes when editing content. + /// public async Task AlignPropertyVarianceAsync(BlockPropertyValue blockPropertyValue, IPropertyType propertyType, string? culture) { culture ??= await _languageService.GetDefaultIsoCodeAsync(); @@ -24,6 +45,15 @@ public async Task AlignPropertyVarianceAsync(BlockPropertyValue blockPropertyVal } } + /// + /// Aligns a block property value for variance changes. + /// + /// The block property value to align. + /// The underlying property type. + /// The containing block element. + /// + /// Used for aligning variance changes when rendering content. + /// public async Task AlignedPropertyVarianceAsync(BlockPropertyValue blockPropertyValue, IPublishedPropertyType propertyType, IPublishedElement owner) { ContentVariation propertyTypeVariation = owner.ContentType.Variations & propertyType.Variations; @@ -65,6 +95,15 @@ public async Task AlignPropertyVarianceAsync(BlockPropertyValue blockPropertyVal return null; } + /// + /// Aligns a block value for variance changes. + /// + /// The block property value to align. + /// The owner element (the content for block properties at content level, or the parent element for nested block properties). + /// The containing block element. + /// + /// Used for aligning variance changes when rendering content. + /// public async Task> AlignedExposeVarianceAsync(BlockValue blockValue, IPublishedElement owner, IPublishedElement element) { BlockItemVariation[] blockVariations = blockValue.Expose.Where(v => v.ContentKey == element.Key).ToArray(); @@ -96,9 +135,29 @@ public async Task> AlignedExposeVarianceAsync(Bl return blockVariations; } + /// + /// Aligns block value expose for variance changes. + /// + /// The block value to align. + /// + /// + /// Used for aligning variance changes when editing content. + /// + /// + /// This is expected to be invoked after all block values have been aligned for variance changes by . + /// + /// public void AlignExposeVariance(BlockValue blockValue) { var contentDataToAlign = new List(); + var elementTypesByKey = blockValue + .ContentData + .Select(cd => cd.ContentTypeKey) + .Distinct() + .Select(_contentTypeService.Get) + .WhereNotNull() + .ToDictionary(c => c.Key); + foreach (BlockItemVariation variation in blockValue.Expose) { BlockItemData? contentData = blockValue.ContentData.FirstOrDefault(cd => cd.Key == variation.ContentKey); @@ -107,6 +166,16 @@ public void AlignExposeVariance(BlockValue blockValue) continue; } + if (elementTypesByKey.TryGetValue(contentData.ContentTypeKey, out IContentType? elementType) is false) + { + continue; + } + + if (variation.Culture is not null == elementType.VariesByCulture()) + { + continue; + } + if((variation.Culture is null && contentData.Values.Any(v => v.Culture is not null)) || (variation.Culture is not null && contentData.Values.All(v => v.Culture is null))) { diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/BlockGridPropertyValueConverterTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/BlockGridPropertyValueConverterTests.cs index 14a02d88c423..8f885645995d 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/BlockGridPropertyValueConverterTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/BlockGridPropertyValueConverterTests.cs @@ -187,7 +187,7 @@ public void Ignores_Other_Layouts() private BlockGridPropertyValueConverter CreateConverter() { var publishedModelFactory = new NoopPublishedModelFactory(); - var blockVarianceHandler = new BlockEditorVarianceHandler(Mock.Of()); + var blockVarianceHandler = new BlockEditorVarianceHandler(Mock.Of(), Mock.Of()); var editor = new BlockGridPropertyValueConverter( Mock.Of(), new BlockEditorConverter(GetPublishedContentTypeCache(), Mock.Of(), publishedModelFactory, Mock.Of(), blockVarianceHandler), diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/BlockListPropertyValueConverterTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/BlockListPropertyValueConverterTests.cs index 84070ffe4fe5..3cd7b4bc394c 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/BlockListPropertyValueConverterTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/BlockListPropertyValueConverterTests.cs @@ -24,7 +24,7 @@ public class BlockListPropertyValueConverterTests : BlockPropertyValueConverterT private BlockListPropertyValueConverter CreateConverter() { var publishedModelFactory = new NoopPublishedModelFactory(); - var blockVarianceHandler = new BlockEditorVarianceHandler(Mock.Of()); + var blockVarianceHandler = new BlockEditorVarianceHandler(Mock.Of(), Mock.Of()); var editor = new BlockListPropertyValueConverter( Mock.Of(), new BlockEditorConverter(GetPublishedContentTypeCache(), Mock.Of(), publishedModelFactory, Mock.Of(), blockVarianceHandler), diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/DataValueEditorReuseTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/DataValueEditorReuseTests.cs index 39132ee7a8a1..217b820c78aa 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/DataValueEditorReuseTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/DataValueEditorReuseTests.cs @@ -36,7 +36,7 @@ public void SetUp() _propertyEditorCollection = new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty)); _dataValueReferenceFactories = new DataValueReferenceFactoryCollection(Enumerable.Empty); - var blockVarianceHandler = new BlockEditorVarianceHandler(Mock.Of()); + var blockVarianceHandler = new BlockEditorVarianceHandler(Mock.Of(), Mock.Of()); _dataValueEditorFactoryMock .Setup(m => m.Create(It.IsAny(), It.IsAny>())) diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/PropertyEditors/BlockEditorVarianceHandlerTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/PropertyEditors/BlockEditorVarianceHandlerTests.cs index 664478f16b5e..5547c930d17c 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/PropertyEditors/BlockEditorVarianceHandlerTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/PropertyEditors/BlockEditorVarianceHandlerTests.cs @@ -16,11 +16,12 @@ public class BlockEditorVarianceHandlerTests public async Task Assigns_Default_Culture_When_Culture_Variance_Is_Enabled() { var propertyValue = new BlockPropertyValue { Culture = null }; - var subject = BlockEditorVarianceHandler("da-DK"); + var owner = PublishedElement(ContentVariation.Culture); + var subject = BlockEditorVarianceHandler("da-DK", owner); var result = await subject.AlignedPropertyVarianceAsync( propertyValue, PublishedPropertyType(ContentVariation.Culture), - PublishedElement(ContentVariation.Culture)); + owner); Assert.IsNotNull(result); Assert.AreEqual("da-DK", result.Culture); } @@ -29,11 +30,12 @@ public async Task Assigns_Default_Culture_When_Culture_Variance_Is_Enabled() public async Task Removes_Default_Culture_When_Culture_Variance_Is_Disabled() { var propertyValue = new BlockPropertyValue { Culture = "da-DK" }; - var subject = BlockEditorVarianceHandler("da-DK"); + var owner = PublishedElement(ContentVariation.Nothing); + var subject = BlockEditorVarianceHandler("da-DK", owner); var result = await subject.AlignedPropertyVarianceAsync( propertyValue, PublishedPropertyType(ContentVariation.Nothing), - PublishedElement(ContentVariation.Nothing)); + owner); Assert.IsNotNull(result); Assert.AreEqual(null, result.Culture); } @@ -42,14 +44,104 @@ public async Task Removes_Default_Culture_When_Culture_Variance_Is_Disabled() public async Task Ignores_NonDefault_Culture_When_Culture_Variance_Is_Disabled() { var propertyValue = new BlockPropertyValue { Culture = "en-US" }; - var subject = BlockEditorVarianceHandler("da-DK"); + var owner = PublishedElement(ContentVariation.Nothing); + var subject = BlockEditorVarianceHandler("da-DK", owner); var result = await subject.AlignedPropertyVarianceAsync( propertyValue, PublishedPropertyType(ContentVariation.Nothing), - PublishedElement(ContentVariation.Nothing)); + owner); Assert.IsNull(result); } + [Test] + public void AlignExpose_Can_Align_Invariance() + { + var owner = PublishedElement(ContentVariation.Nothing); + var contentDataKey = Guid.NewGuid(); + var blockValue = new BlockListValue + { + ContentData = + [ + new() + { + Key = contentDataKey, + ContentTypeKey = owner.ContentType.Key, + Values = + [ + new BlockPropertyValue { Alias = "one", Culture = null, Segment = null, Value = "Value one" } + ] + } + ], + Expose = [new() { ContentKey = contentDataKey, Culture = "da-DK" }] + }; + + var subject = BlockEditorVarianceHandler("da-DK", owner); + subject.AlignExposeVariance(blockValue); + + Assert.AreEqual(null, blockValue.Expose.First().Culture); + } + + [Test] + public void AlignExpose_Can_Align_Variance() + { + var owner = PublishedElement(ContentVariation.CultureAndSegment); + var contentDataKey = Guid.NewGuid(); + var blockValue = new BlockListValue + { + ContentData = + [ + new() + { + Key = contentDataKey, + ContentTypeKey = owner.ContentType.Key, + Values = + [ + new BlockPropertyValue { Alias = "one", Culture = "en-US", Segment = "segment-one", Value = "Value one" } + ] + } + ], + Expose = [new() { ContentKey = contentDataKey, Culture = null, Segment = null }] + }; + + var subject = BlockEditorVarianceHandler("da-DK", owner); + subject.AlignExposeVariance(blockValue); + + Assert.Multiple(() => + { + var alignedExpose = blockValue.Expose.First(); + Assert.AreEqual("en-US", alignedExpose.Culture); + Assert.AreEqual("segment-one", alignedExpose.Segment); + }); + } + + [Test] + public void AlignExpose_Can_Handle_Variant_Element_Type_With_All_Invariant_Block_Values() + { + var owner = PublishedElement(ContentVariation.Culture); + var contentDataKey = Guid.NewGuid(); + var blockValue = new BlockListValue + { + ContentData = + [ + new() + { + Key = contentDataKey, + ContentTypeKey = owner.ContentType.Key, + Values = + [ + new BlockPropertyValue { Alias = "one", Culture = null, Segment = null, Value = "Value one" } + ] + } + ], + Expose = [new() { ContentKey = contentDataKey, Culture = "da-DK" }] + }; + + var subject = BlockEditorVarianceHandler("da-DK", owner); + subject.AlignExposeVariance(blockValue); + + Assert.AreEqual("da-DK", blockValue.Expose.First().Culture); + } + private static IPublishedPropertyType PublishedPropertyType(ContentVariation variation) { var propertyTypeMock = new Mock(); @@ -61,15 +153,21 @@ private static IPublishedElement PublishedElement(ContentVariation variation) { var contentTypeMock = new Mock(); contentTypeMock.SetupGet(m => m.Variations).Returns(variation); + contentTypeMock.SetupGet(m => m.Key).Returns(Guid.NewGuid()); var elementMock = new Mock(); elementMock.SetupGet(m => m.ContentType).Returns(contentTypeMock.Object); return elementMock.Object; } - private static BlockEditorVarianceHandler BlockEditorVarianceHandler(string defaultLanguageIsoCode) + private static BlockEditorVarianceHandler BlockEditorVarianceHandler(string defaultLanguageIsoCode, IPublishedElement element) { var languageServiceMock = new Mock(); languageServiceMock.Setup(m => m.GetDefaultIsoCodeAsync()).ReturnsAsync(defaultLanguageIsoCode); - return new BlockEditorVarianceHandler(languageServiceMock.Object); + var contentTypeServiceMock = new Mock(); + var elementType = new Mock(); + elementType.SetupGet(e => e.Key).Returns(element.ContentType.Key); + elementType.SetupGet(e => e.Variations).Returns(element.ContentType.Variations); + contentTypeServiceMock.Setup(c => c.Get(element.ContentType.Key)).Returns(elementType.Object); + return new BlockEditorVarianceHandler(languageServiceMock.Object, contentTypeServiceMock.Object); } }