Fix variant invariancy with limited language (#17707)

* Add a way to configure IUmbracobuilder on a per testcase basis

* New logic for invariantVariantMerging

* bugfix

* Undo formatting changes

* Undo more automatic formatting

* Last automatic formatting correction

* Cleanup ConfigureBuilderAttribute

* Made propertyEditor tests internal
This commit is contained in:
Sven Geusens
2025-01-06 14:58:00 +01:00
committed by GitHub
parent 99f572837a
commit 1cd9e3e83f
25 changed files with 720 additions and 79 deletions

View File

@@ -10,6 +10,7 @@ using Umbraco.Cms.Core.PropertyEditors.ValueConverters;
using Umbraco.Cms.Core.Serialization;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Strings;
using Umbraco.Extensions;
namespace Umbraco.Cms.Core.PropertyEditors;
@@ -259,6 +260,178 @@ public abstract class BlockValuePropertyValueEditorBase<TValue, TLayout> : DataV
}
}
/// <summary>
/// Updates the invariant data in the source with the invariant data in the value if allowed
/// </summary>
/// <param name="source"></param>
/// <param name="target"></param>
/// <param name="canUpdateInvariantData"></param>
/// <returns></returns>
internal virtual BlockEditorData<TValue, TLayout>? UpdateSourceInvariantData(BlockEditorData<TValue, TLayout>? source, BlockEditorData<TValue, TLayout>? target, bool canUpdateInvariantData)
{
if (source is null && target is null)
{
return null;
}
if (source is null)
{
return MergeNewInvariant(target!, canUpdateInvariantData);
}
if (target is null)
{
return MergeRemovalInvariant(source, canUpdateInvariantData);
}
return MergeInvariant(source, target, canUpdateInvariantData);
}
internal virtual object? MergeVariantInvariantPropertyValue(
object? sourceValue,
object? targetValue,
bool canUpdateInvariantData,
HashSet<string> allowedCultures)
{
BlockEditorData<TValue, TLayout>? source = BlockEditorValues.DeserializeAndClean(sourceValue);
BlockEditorData<TValue, TLayout>? target = BlockEditorValues.DeserializeAndClean(targetValue);
source = UpdateSourceInvariantData(source, target, canUpdateInvariantData);
if (source is null && target is null)
{
return null;
}
if (source is null && target?.Layout is not null)
{
source = new BlockEditorData<TValue, TLayout>([], CreateWithLayout(target.Layout));
}
else if (target is null && source?.Layout is not null)
{
target = new BlockEditorData<TValue, TLayout>([], CreateWithLayout(source.Layout));
}
// at this point the layout should have been merged or fallback created
if (source is null || target is null)
{
throw new ArgumentException("invalid sourceValue or targetValue");
}
// remove all the blocks that are no longer part of the layout
target.BlockValue.ContentData.RemoveAll(contentBlock =>
target.Layout!.Any(layoutItem => layoutItem.ContentKey == contentBlock.Key) is false);
target.BlockValue.SettingsData.RemoveAll(settingsBlock =>
target.Layout!.Any(layoutItem => layoutItem.SettingsKey == settingsBlock.Key) is false);
CleanupVariantValues(source.BlockValue.ContentData, target.BlockValue.ContentData, canUpdateInvariantData, allowedCultures);
CleanupVariantValues(source.BlockValue.SettingsData, target.BlockValue.SettingsData, canUpdateInvariantData, allowedCultures);
return _jsonSerializer.Serialize(target.BlockValue);
}
private void CleanupVariantValues(
List<BlockItemData> sourceBlockItems,
List<BlockItemData> targetBlockItems,
bool canUpdateInvariantData,
HashSet<string> allowedCultures)
{
// merge the source values into the target values for culture
foreach (BlockItemData targetBlockItem in targetBlockItems)
{
BlockItemData? sourceBlockItem = sourceBlockItems.FirstOrDefault(i => i.Key == targetBlockItem.Key);
var valuesToRemove = new List<BlockPropertyValue>();
foreach (BlockPropertyValue targetBlockPropertyValue in targetBlockItem.Values)
{
BlockPropertyValue? sourceBlockPropertyValue =
sourceBlockItem?.Values.FirstOrDefault(v => v.Culture == targetBlockPropertyValue.Culture);
// todo double check if this path can have an invariant value, but it shouldn't right???
// => it can be a null culture, but we shouldn't do anything? as the invariant section should have done it already
if ((targetBlockPropertyValue.Culture is null && canUpdateInvariantData == false)
|| (targetBlockPropertyValue.Culture is not null && allowedCultures.Contains(targetBlockPropertyValue.Culture) is false))
{
// not allowed to update this culture, set the value back to the source
if (sourceBlockPropertyValue is null)
{
valuesToRemove.Add(targetBlockPropertyValue);
}
else
{
targetBlockPropertyValue.Value = sourceBlockPropertyValue.Value;
}
continue;
}
// is this another editor that supports partial merging? i.e. blocks within blocks.
IDataEditor? mergingDataEditor = null;
var shouldPerformPartialMerge = targetBlockPropertyValue.PropertyType is not null
&& _propertyEditors.TryGet(targetBlockPropertyValue.PropertyType.PropertyEditorAlias, out mergingDataEditor)
&& mergingDataEditor.CanMergePartialPropertyValues(targetBlockPropertyValue.PropertyType);
if (shouldPerformPartialMerge is false)
{
continue;
}
// marge subdata
targetBlockPropertyValue.Value = mergingDataEditor!.MergeVariantInvariantPropertyValue(
sourceBlockPropertyValue?.Value,
targetBlockPropertyValue.Value,
canUpdateInvariantData,
allowedCultures);
}
foreach (BlockPropertyValue value in valuesToRemove)
{
targetBlockItem.Values.Remove(value);
}
}
}
private BlockEditorData<TValue, TLayout>? MergeNewInvariant(BlockEditorData<TValue, TLayout> target, bool canUpdateInvariantData)
{
if (canUpdateInvariantData is false)
{
// source value was null and not allowed to update the structure which is invariant => nothing remains
return null;
}
// create a new source object based on the target value that only has the invariant data (structure)
return target.Layout is not null
? new BlockEditorData<TValue, TLayout>([], CreateWithLayout(target.Layout))
: null;
}
private BlockEditorData<TValue, TLayout>? MergeRemovalInvariant(BlockEditorData<TValue, TLayout> source, bool canUpdateInvariantData)
{
if (canUpdateInvariantData)
{
// if the structure is removed, everything is gone anyway
return null;
}
// create a new target object based on the source value that only has the invariant data (structure)
return source.Layout is not null
? new BlockEditorData<TValue, TLayout>([], CreateWithLayout(source.Layout))
: null;
}
private BlockEditorData<TValue, TLayout> MergeInvariant(BlockEditorData<TValue, TLayout> source, BlockEditorData<TValue, TLayout> target, bool canUpdateInvariantData)
{
if (canUpdateInvariantData)
{
source.BlockValue.Layout = target.BlockValue.Layout;
source.BlockValue.Expose = target.BlockValue.Expose;
}
return source;
}
internal virtual object? MergePartialPropertyValueForCulture(object? sourceValue, object? targetValue, string? culture)
{
if (sourceValue is null)