Handle "expose" for variant elements with all invariant properties (#17621)
Co-authored-by: Elitsa <elm@umbraco.dk>
This commit is contained in:
@@ -1,3 +1,5 @@
|
|||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Umbraco.Cms.Core.DependencyInjection;
|
||||||
using Umbraco.Cms.Core.Models;
|
using Umbraco.Cms.Core.Models;
|
||||||
using Umbraco.Cms.Core.Models.Blocks;
|
using Umbraco.Cms.Core.Models.Blocks;
|
||||||
using Umbraco.Cms.Core.Models.PublishedContent;
|
using Umbraco.Cms.Core.Models.PublishedContent;
|
||||||
@@ -9,10 +11,29 @@ namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters;
|
|||||||
public sealed class BlockEditorVarianceHandler
|
public sealed class BlockEditorVarianceHandler
|
||||||
{
|
{
|
||||||
private readonly ILanguageService _languageService;
|
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)
|
public BlockEditorVarianceHandler(ILanguageService languageService)
|
||||||
=> _languageService = languageService;
|
: this(languageService, StaticServiceProvider.Instance.GetRequiredService<IContentTypeService>())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public BlockEditorVarianceHandler(ILanguageService languageService, IContentTypeService contentTypeService)
|
||||||
|
{
|
||||||
|
_languageService = languageService;
|
||||||
|
_contentTypeService = contentTypeService;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Aligns a block property value for variance changes.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="blockPropertyValue">The block property value to align.</param>
|
||||||
|
/// <param name="propertyType">The underlying property type.</param>
|
||||||
|
/// <param name="culture">The culture being handled (null if invariant).</param>
|
||||||
|
/// <remarks>
|
||||||
|
/// Used for aligning variance changes when editing content.
|
||||||
|
/// </remarks>
|
||||||
public async Task AlignPropertyVarianceAsync(BlockPropertyValue blockPropertyValue, IPropertyType propertyType, string? culture)
|
public async Task AlignPropertyVarianceAsync(BlockPropertyValue blockPropertyValue, IPropertyType propertyType, string? culture)
|
||||||
{
|
{
|
||||||
culture ??= await _languageService.GetDefaultIsoCodeAsync();
|
culture ??= await _languageService.GetDefaultIsoCodeAsync();
|
||||||
@@ -24,6 +45,15 @@ public sealed class BlockEditorVarianceHandler
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Aligns a block property value for variance changes.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="blockPropertyValue">The block property value to align.</param>
|
||||||
|
/// <param name="propertyType">The underlying property type.</param>
|
||||||
|
/// <param name="owner">The containing block element.</param>
|
||||||
|
/// <remarks>
|
||||||
|
/// Used for aligning variance changes when rendering content.
|
||||||
|
/// </remarks>
|
||||||
public async Task<BlockPropertyValue?> AlignedPropertyVarianceAsync(BlockPropertyValue blockPropertyValue, IPublishedPropertyType propertyType, IPublishedElement owner)
|
public async Task<BlockPropertyValue?> AlignedPropertyVarianceAsync(BlockPropertyValue blockPropertyValue, IPublishedPropertyType propertyType, IPublishedElement owner)
|
||||||
{
|
{
|
||||||
ContentVariation propertyTypeVariation = owner.ContentType.Variations & propertyType.Variations;
|
ContentVariation propertyTypeVariation = owner.ContentType.Variations & propertyType.Variations;
|
||||||
@@ -65,6 +95,15 @@ public sealed class BlockEditorVarianceHandler
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Aligns a block value for variance changes.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="blockValue">The block property value to align.</param>
|
||||||
|
/// <param name="owner">The owner element (the content for block properties at content level, or the parent element for nested block properties).</param>
|
||||||
|
/// <param name="element">The containing block element.</param>
|
||||||
|
/// <remarks>
|
||||||
|
/// Used for aligning variance changes when rendering content.
|
||||||
|
/// </remarks>
|
||||||
public async Task<IEnumerable<BlockItemVariation>> AlignedExposeVarianceAsync(BlockValue blockValue, IPublishedElement owner, IPublishedElement element)
|
public async Task<IEnumerable<BlockItemVariation>> AlignedExposeVarianceAsync(BlockValue blockValue, IPublishedElement owner, IPublishedElement element)
|
||||||
{
|
{
|
||||||
BlockItemVariation[] blockVariations = blockValue.Expose.Where(v => v.ContentKey == element.Key).ToArray();
|
BlockItemVariation[] blockVariations = blockValue.Expose.Where(v => v.ContentKey == element.Key).ToArray();
|
||||||
@@ -96,9 +135,29 @@ public sealed class BlockEditorVarianceHandler
|
|||||||
return blockVariations;
|
return blockVariations;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Aligns block value expose for variance changes.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="blockValue">The block value to align.</param>
|
||||||
|
/// <remarks>
|
||||||
|
/// <para>
|
||||||
|
/// Used for aligning variance changes when editing content.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// This is expected to be invoked after all block values have been aligned for variance changes by <see cref="AlignPropertyVarianceAsync"/>.
|
||||||
|
/// </para>
|
||||||
|
/// </remarks>
|
||||||
public void AlignExposeVariance(BlockValue blockValue)
|
public void AlignExposeVariance(BlockValue blockValue)
|
||||||
{
|
{
|
||||||
var contentDataToAlign = new List<BlockItemData>();
|
var contentDataToAlign = new List<BlockItemData>();
|
||||||
|
var elementTypesByKey = blockValue
|
||||||
|
.ContentData
|
||||||
|
.Select(cd => cd.ContentTypeKey)
|
||||||
|
.Distinct()
|
||||||
|
.Select(_contentTypeService.Get)
|
||||||
|
.WhereNotNull()
|
||||||
|
.ToDictionary(c => c.Key);
|
||||||
|
|
||||||
foreach (BlockItemVariation variation in blockValue.Expose)
|
foreach (BlockItemVariation variation in blockValue.Expose)
|
||||||
{
|
{
|
||||||
BlockItemData? contentData = blockValue.ContentData.FirstOrDefault(cd => cd.Key == variation.ContentKey);
|
BlockItemData? contentData = blockValue.ContentData.FirstOrDefault(cd => cd.Key == variation.ContentKey);
|
||||||
@@ -107,6 +166,16 @@ public sealed class BlockEditorVarianceHandler
|
|||||||
continue;
|
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))
|
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)))
|
|| (variation.Culture is not null && contentData.Values.All(v => v.Culture is null)))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -187,7 +187,7 @@ public class BlockGridPropertyValueConverterTests : BlockPropertyValueConverterT
|
|||||||
private BlockGridPropertyValueConverter CreateConverter()
|
private BlockGridPropertyValueConverter CreateConverter()
|
||||||
{
|
{
|
||||||
var publishedModelFactory = new NoopPublishedModelFactory();
|
var publishedModelFactory = new NoopPublishedModelFactory();
|
||||||
var blockVarianceHandler = new BlockEditorVarianceHandler(Mock.Of<ILanguageService>());
|
var blockVarianceHandler = new BlockEditorVarianceHandler(Mock.Of<ILanguageService>(), Mock.Of<IContentTypeService>());
|
||||||
var editor = new BlockGridPropertyValueConverter(
|
var editor = new BlockGridPropertyValueConverter(
|
||||||
Mock.Of<IProfilingLogger>(),
|
Mock.Of<IProfilingLogger>(),
|
||||||
new BlockEditorConverter(GetPublishedContentTypeCache(), Mock.Of<ICacheManager>(), publishedModelFactory, Mock.Of<IVariationContextAccessor>(), blockVarianceHandler),
|
new BlockEditorConverter(GetPublishedContentTypeCache(), Mock.Of<ICacheManager>(), publishedModelFactory, Mock.Of<IVariationContextAccessor>(), blockVarianceHandler),
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ public class BlockListPropertyValueConverterTests : BlockPropertyValueConverterT
|
|||||||
private BlockListPropertyValueConverter CreateConverter()
|
private BlockListPropertyValueConverter CreateConverter()
|
||||||
{
|
{
|
||||||
var publishedModelFactory = new NoopPublishedModelFactory();
|
var publishedModelFactory = new NoopPublishedModelFactory();
|
||||||
var blockVarianceHandler = new BlockEditorVarianceHandler(Mock.Of<ILanguageService>());
|
var blockVarianceHandler = new BlockEditorVarianceHandler(Mock.Of<ILanguageService>(), Mock.Of<IContentTypeService>());
|
||||||
var editor = new BlockListPropertyValueConverter(
|
var editor = new BlockListPropertyValueConverter(
|
||||||
Mock.Of<IProfilingLogger>(),
|
Mock.Of<IProfilingLogger>(),
|
||||||
new BlockEditorConverter(GetPublishedContentTypeCache(), Mock.Of<ICacheManager>(), publishedModelFactory, Mock.Of<IVariationContextAccessor>(), blockVarianceHandler),
|
new BlockEditorConverter(GetPublishedContentTypeCache(), Mock.Of<ICacheManager>(), publishedModelFactory, Mock.Of<IVariationContextAccessor>(), blockVarianceHandler),
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ public class DataValueEditorReuseTests
|
|||||||
_propertyEditorCollection = new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty<IDataEditor>));
|
_propertyEditorCollection = new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty<IDataEditor>));
|
||||||
_dataValueReferenceFactories = new DataValueReferenceFactoryCollection(Enumerable.Empty<IDataValueReferenceFactory>);
|
_dataValueReferenceFactories = new DataValueReferenceFactoryCollection(Enumerable.Empty<IDataValueReferenceFactory>);
|
||||||
|
|
||||||
var blockVarianceHandler = new BlockEditorVarianceHandler(Mock.Of<ILanguageService>());
|
var blockVarianceHandler = new BlockEditorVarianceHandler(Mock.Of<ILanguageService>(), Mock.Of<IContentTypeService>());
|
||||||
_dataValueEditorFactoryMock
|
_dataValueEditorFactoryMock
|
||||||
.Setup(m =>
|
.Setup(m =>
|
||||||
m.Create<BlockListPropertyEditorBase.BlockListEditorPropertyValueEditor>(It.IsAny<DataEditorAttribute>(), It.IsAny<BlockEditorDataConverter<BlockListValue, BlockListLayoutItem>>()))
|
m.Create<BlockListPropertyEditorBase.BlockListEditorPropertyValueEditor>(It.IsAny<DataEditorAttribute>(), It.IsAny<BlockEditorDataConverter<BlockListValue, BlockListLayoutItem>>()))
|
||||||
|
|||||||
@@ -16,11 +16,12 @@ public class BlockEditorVarianceHandlerTests
|
|||||||
public async Task Assigns_Default_Culture_When_Culture_Variance_Is_Enabled()
|
public async Task Assigns_Default_Culture_When_Culture_Variance_Is_Enabled()
|
||||||
{
|
{
|
||||||
var propertyValue = new BlockPropertyValue { Culture = null };
|
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(
|
var result = await subject.AlignedPropertyVarianceAsync(
|
||||||
propertyValue,
|
propertyValue,
|
||||||
PublishedPropertyType(ContentVariation.Culture),
|
PublishedPropertyType(ContentVariation.Culture),
|
||||||
PublishedElement(ContentVariation.Culture));
|
owner);
|
||||||
Assert.IsNotNull(result);
|
Assert.IsNotNull(result);
|
||||||
Assert.AreEqual("da-DK", result.Culture);
|
Assert.AreEqual("da-DK", result.Culture);
|
||||||
}
|
}
|
||||||
@@ -29,11 +30,12 @@ public class BlockEditorVarianceHandlerTests
|
|||||||
public async Task Removes_Default_Culture_When_Culture_Variance_Is_Disabled()
|
public async Task Removes_Default_Culture_When_Culture_Variance_Is_Disabled()
|
||||||
{
|
{
|
||||||
var propertyValue = new BlockPropertyValue { Culture = "da-DK" };
|
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(
|
var result = await subject.AlignedPropertyVarianceAsync(
|
||||||
propertyValue,
|
propertyValue,
|
||||||
PublishedPropertyType(ContentVariation.Nothing),
|
PublishedPropertyType(ContentVariation.Nothing),
|
||||||
PublishedElement(ContentVariation.Nothing));
|
owner);
|
||||||
Assert.IsNotNull(result);
|
Assert.IsNotNull(result);
|
||||||
Assert.AreEqual(null, result.Culture);
|
Assert.AreEqual(null, result.Culture);
|
||||||
}
|
}
|
||||||
@@ -42,14 +44,104 @@ public class BlockEditorVarianceHandlerTests
|
|||||||
public async Task Ignores_NonDefault_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 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(
|
var result = await subject.AlignedPropertyVarianceAsync(
|
||||||
propertyValue,
|
propertyValue,
|
||||||
PublishedPropertyType(ContentVariation.Nothing),
|
PublishedPropertyType(ContentVariation.Nothing),
|
||||||
PublishedElement(ContentVariation.Nothing));
|
owner);
|
||||||
Assert.IsNull(result);
|
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)
|
private static IPublishedPropertyType PublishedPropertyType(ContentVariation variation)
|
||||||
{
|
{
|
||||||
var propertyTypeMock = new Mock<IPublishedPropertyType>();
|
var propertyTypeMock = new Mock<IPublishedPropertyType>();
|
||||||
@@ -61,15 +153,21 @@ public class BlockEditorVarianceHandlerTests
|
|||||||
{
|
{
|
||||||
var contentTypeMock = new Mock<IPublishedContentType>();
|
var contentTypeMock = new Mock<IPublishedContentType>();
|
||||||
contentTypeMock.SetupGet(m => m.Variations).Returns(variation);
|
contentTypeMock.SetupGet(m => m.Variations).Returns(variation);
|
||||||
|
contentTypeMock.SetupGet(m => m.Key).Returns(Guid.NewGuid());
|
||||||
var elementMock = new Mock<IPublishedElement>();
|
var elementMock = new Mock<IPublishedElement>();
|
||||||
elementMock.SetupGet(m => m.ContentType).Returns(contentTypeMock.Object);
|
elementMock.SetupGet(m => m.ContentType).Returns(contentTypeMock.Object);
|
||||||
return elementMock.Object;
|
return elementMock.Object;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static BlockEditorVarianceHandler BlockEditorVarianceHandler(string defaultLanguageIsoCode)
|
private static BlockEditorVarianceHandler BlockEditorVarianceHandler(string defaultLanguageIsoCode, IPublishedElement element)
|
||||||
{
|
{
|
||||||
var languageServiceMock = new Mock<ILanguageService>();
|
var languageServiceMock = new Mock<ILanguageService>();
|
||||||
languageServiceMock.Setup(m => m.GetDefaultIsoCodeAsync()).ReturnsAsync(defaultLanguageIsoCode);
|
languageServiceMock.Setup(m => m.GetDefaultIsoCodeAsync()).ReturnsAsync(defaultLanguageIsoCode);
|
||||||
return new BlockEditorVarianceHandler(languageServiceMock.Object);
|
var contentTypeServiceMock = new Mock<IContentTypeService>();
|
||||||
|
var elementType = new Mock<IContentType>();
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user