Files
Umbraco-CMS/src/Umbraco.Infrastructure/PropertyEditors/BlockValuePropertyValueEditorBase.cs
Nicklas Kramer 50ae48b0a2 V17 - Removed obsoleted code from Umbraco.Infrastructure (#19977)
* Removing obsoleted code from MigrationPlanExecutor.cs & Interface

* Removing obsoleted code from EmailAddressPropertyEditor.cs

* Removing obsoleted class CacheRebuilder.cs

* Removing obsoleted code from TextBuilder.cs

* Removing obsoleted class ICacheRebuilder.cs

* Removing obsoleted code from SerilogLogger.cs

* Removing the use of Infrastructure IBackgroundTaskQueue.cs and replacing usage with the Core replacement

* Removing obsoleted code from the FileUploadPropertyEditor.cs

* Removing obsoleted code from BlockValuePropertyValueEditorBase.cs

* Removing obsoleted constructors and methods from MultiNodeTreePickerPropertyEditor.cs and TextHeaderWriter.cs

* Removing obsoleted code from CacheInstructionService.cs

* Bumping obsoleted code from MigrationBase.cs to V18

* Removing obsoleted code from EmailSender.cs

* Removing obsoleted code from BlockEditorVarianceHandler.cs

* Removing obsoleted code from IBackOfficeApplicationManager.cs

* Removing obsoleted code from RedirectTracker.cs & RichTextEditorPastedImages.cs
2025-08-22 14:38:27 +02:00

652 lines
29 KiB
C#

using Umbraco.Cms.Core.Cache;
using Umbraco.Cms.Core.IO;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.Blocks;
using Umbraco.Cms.Core.Models.Editors;
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;
public abstract class BlockValuePropertyValueEditorBase<TValue, TLayout> : DataValueEditor, IDataValueReference, IDataValueTags
where TValue : BlockValue<TLayout>, new()
where TLayout : class, IBlockLayoutItem, new()
{
private readonly IDataTypeConfigurationCache _dataTypeConfigurationCache;
private readonly PropertyEditorCollection _propertyEditors;
private readonly IJsonSerializer _jsonSerializer;
private readonly DataValueReferenceFactoryCollection _dataValueReferenceFactoryCollection;
private readonly BlockEditorVarianceHandler _blockEditorVarianceHandler;
private BlockEditorValues<TValue, TLayout>? _blockEditorValues;
private readonly ILanguageService _languageService;
protected BlockValuePropertyValueEditorBase(
PropertyEditorCollection propertyEditors,
IDataTypeConfigurationCache dataTypeConfigurationCache,
IShortStringHelper shortStringHelper,
IJsonSerializer jsonSerializer,
DataValueReferenceFactoryCollection dataValueReferenceFactoryCollection,
BlockEditorVarianceHandler blockEditorVarianceHandler,
ILanguageService languageService,
IIOHelper ioHelper,
DataEditorAttribute attribute)
: base(shortStringHelper, jsonSerializer, ioHelper, attribute)
{
_propertyEditors = propertyEditors;
_dataTypeConfigurationCache = dataTypeConfigurationCache;
_jsonSerializer = jsonSerializer;
_dataValueReferenceFactoryCollection = dataValueReferenceFactoryCollection;
_blockEditorVarianceHandler = blockEditorVarianceHandler;
_languageService = languageService;
}
/// <inheritdoc />
public abstract IEnumerable<UmbracoEntityReference> GetReferences(object? value);
protected abstract TValue CreateWithLayout(IEnumerable<TLayout> layout);
protected BlockEditorValues<TValue, TLayout> BlockEditorValues
{
get => _blockEditorValues ?? throw new NullReferenceException($"The property {nameof(BlockEditorValues)} must be initialized at value editor construction");
set => _blockEditorValues = value;
}
protected IEnumerable<UmbracoEntityReference> GetBlockValueReferences(TValue blockValue)
{
var result = new HashSet<UmbracoEntityReference>();
BlockPropertyValue[] blockPropertyValues = blockValue.ContentData.Concat(blockValue.SettingsData)
.SelectMany(x => x.Values).ToArray();
if (blockPropertyValues.Any(p => p.PropertyType is null))
{
throw new ArgumentException("One or more block properties did not have a resolved property type. Block editor values must be resolved before attempting to find references within them.", nameof(blockValue));
}
foreach (IGrouping<string, object?> valuesByPropertyEditorAlias in blockPropertyValues.GroupBy(x => x.PropertyType!.PropertyEditorAlias, x => x.Value))
{
if (!_propertyEditors.TryGet(valuesByPropertyEditorAlias.Key, out IDataEditor? dataEditor))
{
continue;
}
var distinctValues = valuesByPropertyEditorAlias.Distinct().ToArray();
if (dataEditor.GetValueEditor() is IDataValueReference reference)
{
foreach (UmbracoEntityReference value in distinctValues.SelectMany(reference.GetReferences))
{
result.Add(value);
}
}
IEnumerable<UmbracoEntityReference> references = _dataValueReferenceFactoryCollection.GetReferences(dataEditor, distinctValues);
foreach (UmbracoEntityReference value in references)
{
result.Add(value);
}
}
return result;
}
/// <inheritdoc />
public abstract IEnumerable<ITag> GetTags(object? value, object? dataTypeConfiguration, int? languageId);
protected IEnumerable<ITag> GetBlockValueTags(TValue blockValue, int? languageId)
{
var result = new List<ITag>();
// loop through all content and settings data
foreach (BlockItemData row in blockValue.ContentData.Concat(blockValue.SettingsData))
{
foreach (BlockPropertyValue blockPropertyValue in row.Values)
{
if (blockPropertyValue.PropertyType is null)
{
throw new ArgumentException("One or more block properties did not have a resolved property type. Block editor values must be resolved before attempting to find tags within them.", nameof(blockValue));
}
IDataEditor? propEditor = _propertyEditors[blockPropertyValue.PropertyType.PropertyEditorAlias];
IDataValueEditor? valueEditor = propEditor?.GetValueEditor();
if (valueEditor is not IDataValueTags tagsProvider)
{
continue;
}
object? configuration = _dataTypeConfigurationCache.GetConfiguration(blockPropertyValue.PropertyType.DataTypeKey);
var tagLanguageId = blockPropertyValue.Culture is not null
? _languageService.GetAsync(blockPropertyValue.Culture).GetAwaiter().GetResult()?.Id
: languageId;
result.AddRange(tagsProvider.GetTags(blockPropertyValue.Value, configuration, tagLanguageId));
}
}
return result;
}
protected void MapBlockValueFromEditor(TValue? editedBlockValue, TValue? currentBlockValue, Guid contentKey)
{
MapBlockItemDataFromEditor(
editedBlockValue?.ContentData ?? [],
currentBlockValue?.ContentData ?? [],
contentKey);
MapBlockItemDataFromEditor(
editedBlockValue?.SettingsData ?? [],
currentBlockValue?.SettingsData ?? [],
contentKey);
}
private void MapBlockItemDataFromEditor(List<BlockItemData> editedItems, List<BlockItemData> currentItems, Guid contentKey)
{
// Create mapping between edited and current block items.
IEnumerable<BlockStateMapping<BlockItemData>> itemsMapping = GetBlockStatesMapping(editedItems, currentItems, (mapping, current) => mapping.Edited?.Key == current.Key);
foreach (BlockStateMapping<BlockItemData> itemMapping in itemsMapping)
{
// Create mapping between edited and current block item values.
IEnumerable<BlockStateMapping<BlockPropertyValue>> valuesMapping = GetBlockStatesMapping(itemMapping.Edited?.Values, itemMapping.Current?.Values, (mapping, current) => mapping.Edited?.Alias == current.Alias);
foreach (BlockStateMapping<BlockPropertyValue> valueMapping in valuesMapping)
{
BlockPropertyValue? editedValue = valueMapping.Edited;
BlockPropertyValue? currentValue = valueMapping.Current;
IPropertyType propertyType = editedValue?.PropertyType
?? currentValue?.PropertyType
?? throw new ArgumentException("One or more block properties did not have a resolved property type. Block editor values must be resolved before attempting to map them from editor.", nameof(editedItems));
// Lookup the property editor.
IDataEditor? propertyEditor = _propertyEditors[propertyType.PropertyEditorAlias];
if (propertyEditor is null)
{
continue;
}
// Fetch the property types prevalue.
var configuration = _dataTypeConfigurationCache.GetConfiguration(propertyType.DataTypeKey);
// Create a real content property data object.
var propertyData = new ContentPropertyData(editedValue?.Value, configuration)
{
ContentKey = contentKey,
PropertyTypeKey = propertyType.Key,
};
// Get the property editor to do it's conversion.
IDataValueEditor valueEditor = propertyEditor.GetValueEditor();
var newValue = valueEditor.FromEditor(propertyData, currentValue?.Value);
// Update the raw value since this is what will get serialized out.
if (editedValue != null)
{
editedValue.Value = newValue;
}
}
}
}
private sealed class BlockStateMapping<T>
{
public T? Edited { get; set; }
public T? Current { get; set; }
}
private static IEnumerable<BlockStateMapping<T>> GetBlockStatesMapping<T>(IList<T>? editedItems, IList<T>? currentItems, Func<BlockStateMapping<T>, T, bool> condition)
{
// filling with edited items first
List<BlockStateMapping<T>> mapping = editedItems?
.Select(editedItem => new BlockStateMapping<T>
{
Current = default,
Edited = editedItem,
})
.ToList()
?? [];
if (currentItems is null)
{
return mapping;
}
// then adding current items
foreach (T currentItem in currentItems)
{
BlockStateMapping<T>? mappingItem = mapping.FirstOrDefault(x => condition(x, currentItem));
if (mappingItem == null) // if there is no edited item, then adding just current
{
mapping.Add(new BlockStateMapping<T>
{
Current = currentItem,
Edited = default,
});
}
else
{
mappingItem.Current = currentItem;
}
}
return mapping;
}
protected void MapBlockValueToEditor(IProperty property, TValue blockValue, string? culture, string? segment)
{
MapBlockItemDataToEditor(property, blockValue.ContentData, culture, segment);
MapBlockItemDataToEditor(property, blockValue.SettingsData, culture, segment);
_blockEditorVarianceHandler.AlignExposeVariance(blockValue);
}
protected IEnumerable<Guid> ConfiguredElementTypeKeys(IBlockConfiguration configuration)
{
yield return configuration.ContentElementTypeKey;
if (configuration.SettingsElementTypeKey is not null)
{
yield return configuration.SettingsElementTypeKey.Value;
}
}
private void MapBlockItemDataToEditor(IProperty property, List<BlockItemData> items, string? culture, string? segment)
{
var valueEditorsByKey = new Dictionary<Guid, IDataValueEditor>();
foreach (BlockItemData item in items)
{
// if changes were made to the element type variations, we need those changes reflected in the block property values.
// for regular content this happens when a content type is saved (copies of property values are created in the DB),
// but for local block level properties we don't have that kind of handling, so we to do it manually.
// to be friendly we'll map "formerly invariant properties" to the default language ISO code instead of performing a
// hard reset of the property values (which would likely be the most correct thing to do from a data point of view).
item.Values = _blockEditorVarianceHandler.AlignPropertyVarianceAsync(item.Values, culture).GetAwaiter().GetResult();
foreach (BlockPropertyValue blockPropertyValue in item.Values)
{
IPropertyType? propertyType = blockPropertyValue.PropertyType;
if (propertyType is null)
{
throw new ArgumentException("One or more block properties did not have a resolved property type. Block editor values must be resolved before attempting to map them to editor.", nameof(items));
}
IDataEditor? propertyEditor = _propertyEditors[propertyType.PropertyEditorAlias];
if (propertyEditor is null)
{
// leave the current block property value as-is - will be used to render a fallback output in the client
continue;
}
if (!valueEditorsByKey.TryGetValue(propertyType.DataTypeKey, out IDataValueEditor? valueEditor))
{
var configuration = _dataTypeConfigurationCache.GetConfiguration(propertyType.DataTypeKey);
valueEditor = propertyEditor.GetValueEditor(configuration);
valueEditorsByKey.Add(propertyType.DataTypeKey, valueEditor);
}
var tempProp = new Property(propertyType);
tempProp.SetValue(blockPropertyValue.Value, blockPropertyValue.Culture, blockPropertyValue.Segment);
var editorValue = valueEditor.ToEditor(tempProp, blockPropertyValue.Culture, blockPropertyValue.Segment);
// update the raw value since this is what will get serialized out
blockPropertyValue.Value = editorValue;
}
}
}
/// <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)
{
if (canUpdateInvariantData is false && targetValue is null)
{
return sourceValue;
}
BlockEditorData<TValue, TLayout>? source = BlockEditorValues.DeserializeAndClean(sourceValue);
BlockEditorData<TValue, TLayout>? target = BlockEditorValues.DeserializeAndClean(targetValue);
TValue? mergedBlockValue =
MergeVariantInvariantPropertyValueTyped(source, target, canUpdateInvariantData, allowedCultures);
if (mergedBlockValue is null)
{
return null;
}
return _jsonSerializer.Serialize(mergedBlockValue);
}
internal virtual TValue? MergeVariantInvariantPropertyValueTyped(
BlockEditorData<TValue, TLayout>? source,
BlockEditorData<TValue, TLayout>? target,
bool canUpdateInvariantData,
HashSet<string> allowedCultures)
{
var mergedInvariant = UpdateSourceInvariantData(source, target, canUpdateInvariantData);
// if the structure (invariant) is not defined after merger, the target content does not matter
if (mergedInvariant?.Layout is null)
{
return null;
}
// since we merged the invariant data (layout) before we get to this point
// we just need an empty valid object to run comparisons at this point
if (source is null)
{
source = new BlockEditorData<TValue, TLayout>([], new TValue());
}
// update the target with the merged invariant
target!.BlockValue.Layout = mergedInvariant.BlockValue.Layout;
// remove all the blocks that are no longer part of the layout
target.BlockValue.ContentData.RemoveAll(contentBlock =>
target.Layout!.Any(layoutItem => layoutItem.ReferencesContent(contentBlock.Key)) is false);
// remove any exposes that no longer have content assigned to them
target.BlockValue.Expose.RemoveAll(expose => target.BlockValue.ContentData.Any(data => data.Key == expose.ContentKey) is false);
target.BlockValue.SettingsData.RemoveAll(settingsBlock =>
target.Layout!.Any(layoutItem => layoutItem.ReferencesSetting(settingsBlock.Key)) is false);
CleanupVariantValues(source.BlockValue.ContentData, target.BlockValue.ContentData, canUpdateInvariantData, allowedCultures);
CleanupVariantValues(source.BlockValue.SettingsData, target.BlockValue.SettingsData, canUpdateInvariantData, allowedCultures);
// every source block value for a culture that is not allowed to be edited should be present on the target
RestoreMissingValues(
source.BlockValue.ContentData,
target.BlockValue.ContentData,
mergedInvariant.Layout,
(layoutItem, itemData) => layoutItem.ContentKey == itemData.Key,
canUpdateInvariantData,
allowedCultures);
RestoreMissingValues(
source.BlockValue.SettingsData,
target.BlockValue.SettingsData,
mergedInvariant.Layout,
(layoutItem, itemData) => layoutItem.SettingsKey == itemData.Key,
canUpdateInvariantData,
allowedCultures);
// update the expose list from source for any blocks that were restored
var missingSourceExposes =
source.BlockValue.Expose.Where(sourceExpose =>
target.BlockValue.Expose.Any(targetExpose => targetExpose.ContentKey == sourceExpose.ContentKey) is false
&& target.BlockValue.ContentData.Any(data => data.Key == sourceExpose.ContentKey)).ToList();
foreach (BlockItemVariation missingSourceExpose in missingSourceExposes)
{
target.BlockValue.Expose.Add(missingSourceExpose);
}
return target.BlockValue;
}
private void RestoreMissingValues(
List<BlockItemData> sourceBlockItemData,
List<BlockItemData> targetBlockItemData,
IEnumerable<TLayout> mergedLayout,
Func<TLayout, BlockItemData, bool> relevantBlockItemMatcher,
bool canUpdateInvariantData,
HashSet<string> allowedCultures)
{
IEnumerable<BlockItemData> blockItemsToCheck = sourceBlockItemData.Where(itemData =>
mergedLayout.Any(layoutItem => relevantBlockItemMatcher(layoutItem, itemData)));
foreach (BlockItemData blockItemData in blockItemsToCheck)
{
var relevantValues = blockItemData.Values.Where(value =>
(value.Culture is null && canUpdateInvariantData is false)
|| (value.Culture is not null && allowedCultures.Contains(value.Culture) is false)).ToList();
if (relevantValues.Count < 1)
{
continue;
}
BlockItemData targetBlockData =
targetBlockItemData.FirstOrDefault(itemData => itemData.Key == blockItemData.Key)
?? new BlockItemData(blockItemData.Key, blockItemData.ContentTypeKey, blockItemData.ContentTypeAlias);
foreach (BlockPropertyValue missingValue in relevantValues.Where(value => targetBlockData.Values.Any(targetValue =>
targetValue.Alias == value.Alias
&& targetValue.Culture == value.Culture
&& targetValue.Segment == value.Segment) is false))
{
targetBlockData.Values.Add(missingValue);
}
if (targetBlockItemData.Any(existingBlockItemData => existingBlockItemData.Key == targetBlockData.Key) is false)
{
targetBlockItemData.Add(blockItemData);
}
}
}
private void CleanupVariantValues(
List<BlockItemData> sourceBlockItems,
List<BlockItemData> targetBlockItems,
bool canUpdateInvariantData,
HashSet<string> allowedCultures)
{
// merge the source values into the target values per 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.Alias == targetBlockPropertyValue.Alias && 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)
{
return null;
}
// parse the source value as block editor data
BlockEditorData<TValue, TLayout>? sourceBlockEditorValues = BlockEditorValues.DeserializeAndClean(sourceValue);
if (sourceBlockEditorValues?.Layout is null)
{
return null;
}
// parse the target value as block editor data (fallback to an empty set of block editor data)
BlockEditorData<TValue, TLayout> targetBlockEditorValues =
(targetValue is not null ? BlockEditorValues.DeserializeAndClean(targetValue) : null)
?? new BlockEditorData<TValue, TLayout>([], CreateWithLayout(sourceBlockEditorValues.Layout));
TValue mergeResult = MergeBlockEditorDataForCulture(sourceBlockEditorValues.BlockValue, targetBlockEditorValues.BlockValue, culture);
return _jsonSerializer.Serialize(mergeResult);
}
protected TValue MergeBlockEditorDataForCulture(TValue sourceBlockValue, TValue targetBlockValue, string? culture)
{
// structure is global, layout and expose follows structure
targetBlockValue.Layout = sourceBlockValue.Layout;
targetBlockValue.Expose = sourceBlockValue.Expose;
MergePartialPropertyValueForCulture(sourceBlockValue.ContentData, targetBlockValue.ContentData, culture);
MergePartialPropertyValueForCulture(sourceBlockValue.SettingsData, targetBlockValue.SettingsData, culture);
return targetBlockValue;
}
private void MergePartialPropertyValueForCulture(List<BlockItemData> sourceBlockItems, List<BlockItemData> targetBlockItems, string? culture)
{
// remove all target blocks that are not part of the source blocks (structure is global)
targetBlockItems.RemoveAll(pb => sourceBlockItems.Any(eb => eb.Key == pb.Key) is false);
// merge the source values into the target values for culture
foreach (BlockItemData sourceBlockItem in sourceBlockItems)
{
BlockItemData? targetBlockItem = targetBlockItems.FirstOrDefault(i => i.Key == sourceBlockItem.Key);
if (targetBlockItem is null)
{
targetBlockItem = new BlockItemData(
sourceBlockItem.Key,
sourceBlockItem.ContentTypeKey,
sourceBlockItem.ContentTypeAlias);
// NOTE: this only works because targetBlockItem is by ref!
targetBlockItems.Add(targetBlockItem);
}
foreach (BlockPropertyValue sourceBlockPropertyValue in sourceBlockItem.Values)
{
// is this another editor that supports partial merging? i.e. blocks within blocks.
IDataEditor? mergingDataEditor = null;
var shouldPerformPartialMerge = sourceBlockPropertyValue.PropertyType is not null
&& _propertyEditors.TryGet(sourceBlockPropertyValue.PropertyType.PropertyEditorAlias, out mergingDataEditor)
&& mergingDataEditor.CanMergePartialPropertyValues(sourceBlockPropertyValue.PropertyType);
if (shouldPerformPartialMerge is false && sourceBlockPropertyValue.Culture != culture)
{
// skip for now (irrelevant for the current culture, but might be included in the next pass)
continue;
}
BlockPropertyValue? targetBlockPropertyValue = targetBlockItem
.Values
.FirstOrDefault(v =>
v.Alias == sourceBlockPropertyValue.Alias &&
v.Culture == sourceBlockPropertyValue.Culture &&
v.Segment == sourceBlockPropertyValue.Segment);
if (targetBlockPropertyValue is null)
{
targetBlockPropertyValue = new BlockPropertyValue
{
Alias = sourceBlockPropertyValue.Alias,
Culture = sourceBlockPropertyValue.Culture,
Segment = sourceBlockPropertyValue.Segment
};
targetBlockItem.Values.Add(targetBlockPropertyValue);
}
// assign source value to target value (or perform partial merge, depending on context)
targetBlockPropertyValue.Value = shouldPerformPartialMerge is false
? sourceBlockPropertyValue.Value
: mergingDataEditor!.MergePartialPropertyValueForCulture(sourceBlockPropertyValue.Value, targetBlockPropertyValue.Value, culture);
}
}
}
}