Skip to content

Fixes #18872 currentValue is not current in Umbraco.BlockList element properties #18976

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 17 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -122,30 +122,35 @@ public override object ToEditor(IProperty property, string? culture = null, stri
/// <returns></returns>
public override object? FromEditor(ContentPropertyData editorValue, object? currentValue)
{
if (editorValue.Value == null || string.IsNullOrWhiteSpace(editorValue.Value.ToString()))
if (editorValue.Value is null || string.IsNullOrWhiteSpace(editorValue.Value.ToString()))
{
return null;
}

BlockEditorData<TValue, TLayout>? blockEditorData;
try
{
blockEditorData = BlockEditorValues.DeserializeAndClean(editorValue.Value);
}
catch
{
// if this occurs it means the data is invalid, shouldn't happen but has happened if we change the data format.
return string.Empty;
}
BlockEditorData<TValue, TLayout>? currentBlockEditorData = GetBlockEditorData(currentValue);
BlockEditorData<TValue, TLayout>? blockEditorData = GetBlockEditorData(editorValue.Value);

if (blockEditorData == null || blockEditorData.BlockValue.ContentData.Count == 0)
if (blockEditorData is null || blockEditorData.BlockValue.ContentData.Count == 0)
{
return string.Empty;
}

MapBlockValueFromEditor(blockEditorData.BlockValue);
MapBlockValueFromEditor(blockEditorData.BlockValue, currentBlockEditorData?.BlockValue, editorValue.ContentKey);

// return json
return JsonSerializer.Serialize(blockEditorData.BlockValue);
}

private BlockEditorData<TValue, TLayout>? GetBlockEditorData(object? value)
{
try
{
return BlockEditorValues.DeserializeAndClean(value);
}
catch
{
// if this occurs it means the data is invalid, shouldn't happen but has happened if we change the data format.
return null;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using Umbraco.Cms.Core.Cache.PropertyEditors;
using Umbraco.Cms.Core.Cache.PropertyEditors;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.Blocks;
using Umbraco.Cms.Core.Models.Validation;
Expand Down Expand Up @@ -88,7 +88,13 @@

foreach (var group in itemDataGroups)
{
var allElementTypes = _elementTypeCache.GetMany(group.Items.Select(x => x.ContentTypeKey).ToArray()).ToDictionary(x => x.Key);
Guid[] elementTypeKeys = group.Items.Select(x => x.ContentTypeKey).ToArray();
if (elementTypeKeys.Length == 0)
{
continue;
}

var allElementTypes = _elementTypeCache.GetMany(elementTypeKeys).ToDictionary(x => x.Key);

Check warning on line 97 in src/Umbraco.Infrastructure/PropertyEditors/BlockEditorValidatorBase.cs

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (contrib)

❌ Getting worse: Complex Method

GetBlockEditorDataValidation increases in cyclomatic complexity from 24 to 26, threshold = 9. This function has many conditional statements (e.g. if, for, while), leading to lower code health. Avoid adding more conditionals and code to it without refactoring.

Check warning on line 97 in src/Umbraco.Infrastructure/PropertyEditors/BlockEditorValidatorBase.cs

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (contrib)

❌ Getting worse: Bumpy Road Ahead

GetBlockEditorDataValidation increases from 4 to 5 logical blocks with deeply nested code, threshold is one single block per function. The Bumpy Road code smell is a function that contains multiple chunks of nested conditional logic. The deeper the nesting and the more bumps, the lower the code health.

for (var i = 0; i < group.Items.Length; i++)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,17 +99,17 @@ protected IEnumerable<UmbracoEntityReference> GetBlockValueReferences(TValue blo
continue;
}

var districtValues = valuesByPropertyEditorAlias.Distinct().ToArray();
var distinctValues = valuesByPropertyEditorAlias.Distinct().ToArray();

if (dataEditor.GetValueEditor() is IDataValueReference reference)
{
foreach (UmbracoEntityReference value in districtValues.SelectMany(reference.GetReferences))
foreach (UmbracoEntityReference value in distinctValues.SelectMany(reference.GetReferences))
{
result.Add(value);
}
}

IEnumerable<UmbracoEntityReference> references = _dataValueReferenceFactoryCollection.GetReferences(dataEditor, districtValues);
IEnumerable<UmbracoEntityReference> references = _dataValueReferenceFactoryCollection.GetReferences(dataEditor, distinctValues);

foreach (UmbracoEntityReference value in references)
{
Expand Down Expand Up @@ -157,10 +157,115 @@ protected IEnumerable<ITag> GetBlockValueTags(TValue blockValue, int? languageId
return result;
}

protected void MapBlockValueFromEditor(TValue blockValue)
[Obsolete("This method is no longer used within Umbraco. Please use the overload taking all parameters. Scheduled for removal in Umbraco 17.")]
protected void MapBlockValueFromEditor(TValue blockValue) => MapBlockValueFromEditor(blockValue, null, Guid.Empty);

protected void MapBlockValueFromEditor(TValue editedBlockValue, TValue? currentBlockValue, Guid contentKey)
{
MapBlockItemDataFromEditor(blockValue.ContentData);
MapBlockItemDataFromEditor(blockValue.SettingsData);
MapBlockItemDataFromEditor(
editedBlockValue?.ContentData ?? [],
currentBlockValue?.ContentData ?? [],
contentKey);

MapBlockItemDataFromEditor(
editedBlockValue?.SettingsData ?? [],
currentBlockValue?.SettingsData ?? [],
contentKey);
}

private void MapBlockItemDataFromEditor(List<BlockItemData> editedItems, List<BlockItemData> currentItems, Guid contentKey)
{
// creating 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)
{
// creating 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)
Expand Down Expand Up @@ -225,40 +330,6 @@ private void MapBlockItemDataToEditor(IProperty property, List<BlockItemData> it
}
}

private void MapBlockItemDataFromEditor(List<BlockItemData> items)
{
foreach (BlockItemData item in items)
{
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 from editor.", nameof(items));
}

// 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 fake content property data object
var propertyData = new ContentPropertyData(blockPropertyValue.Value, configuration);

// Get the property editor to do it's conversion
var newValue = propertyEditor.GetValueEditor().FromEditor(propertyData, blockPropertyValue.Value);

// update the raw value since this is what will get serialized out
blockPropertyValue.Value = newValue;
}
}
}

/// <summary>
/// Updates the invariant data in the source with the invariant data in the value if allowed
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,10 @@ public FileUploadPropertyValueEditor(
// no change?
if (editorModelValue?.TemporaryFileId.HasValue is not true && string.IsNullOrEmpty(editorModelValue?.Src) is false)
{
return currentValue;
// since current value can be json string, we have to parse value
var currentModelValue = ParseFileUploadValue(currentValue);

return currentModelValue?.Src;
}

// the current editor value (if any) is the path to the file
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,8 @@ public override IEnumerable<ITag> GetTags(object? value, object? dataTypeConfigu
return null;
}

TryParseEditorValue(currentValue, out RichTextEditorValue? currentRichTextEditorValue);

Guid userKey = _backOfficeSecurityAccessor.BackOfficeSecurity?.CurrentUser?.Key ??
Constants.Security.SuperUserKey;

Expand All @@ -262,7 +264,7 @@ public override IEnumerable<ITag> GetTags(object? value, object? dataTypeConfigu

richTextEditorValue.Markup = sanitized.NullOrWhiteSpaceAsNull() ?? string.Empty;

RichTextEditorValue cleanedUpRichTextEditorValue = CleanAndMapBlocks(richTextEditorValue, MapBlockValueFromEditor);
RichTextEditorValue cleanedUpRichTextEditorValue = CleanAndMapBlocks(richTextEditorValue, blockValue => MapBlockValueFromEditor(blockValue, currentRichTextEditorValue?.Blocks, editorValue.ContentKey));

// return json
return RichTextPropertyEditorHelper.SerializeRichTextEditorValue(cleanedUpRichTextEditorValue, _jsonSerializer);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { UmbUnsupportedEditorSchemaAliases } from '../../types/index.js';

export const UMB_UNSUPPORTED_EDITOR_SCHEMA_ALIASES: UmbUnsupportedEditorSchemaAliases = {
block: ['Umbraco.ImageCropper', 'Umbraco.UploadField'],
block: ['Umbraco.ImageCropper'],
};
Loading
Loading