diff --git a/src/Umbraco.Infrastructure/PropertyEditors/BlockEditorPropertyValueEditor.cs b/src/Umbraco.Infrastructure/PropertyEditors/BlockEditorPropertyValueEditor.cs index 21359ec1c0..10c752088b 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/BlockEditorPropertyValueEditor.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/BlockEditorPropertyValueEditor.cs @@ -2,7 +2,11 @@ // See LICENSE for more details. using System.Diagnostics.CodeAnalysis; +using System.Text.Json; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; using Umbraco.Cms.Core.Cache; +using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Blocks; @@ -21,6 +25,36 @@ public abstract class BlockEditorPropertyValueEditor : BlockVal where TValue : BlockValue, new() where TLayout : class, IBlockLayoutItem, new() { + private readonly ILogger _logger; + + /// + /// Initializes a new instance of the class. + /// + [Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 18.")] + protected BlockEditorPropertyValueEditor( + PropertyEditorCollection propertyEditors, + DataValueReferenceFactoryCollection dataValueReferenceFactories, + IDataTypeConfigurationCache dataTypeConfigurationCache, + IShortStringHelper shortStringHelper, + IJsonSerializer jsonSerializer, + BlockEditorVarianceHandler blockEditorVarianceHandler, + ILanguageService languageService, + IIOHelper ioHelper, + DataEditorAttribute attribute) + : this( + propertyEditors, + dataValueReferenceFactories, + dataTypeConfigurationCache, + shortStringHelper, + jsonSerializer, + blockEditorVarianceHandler, + languageService, + ioHelper, + attribute, + StaticServiceProvider.Instance.GetRequiredService()) + { + } + /// /// Initializes a new instance of the class. /// @@ -33,9 +67,13 @@ public abstract class BlockEditorPropertyValueEditor : BlockVal BlockEditorVarianceHandler blockEditorVarianceHandler, ILanguageService languageService, IIOHelper ioHelper, - DataEditorAttribute attribute) - : base(propertyEditors, dataTypeConfigurationCache, shortStringHelper, jsonSerializer, dataValueReferenceFactories, blockEditorVarianceHandler, languageService, ioHelper, attribute) => + DataEditorAttribute attribute, + ILogger logger) + : base(propertyEditors, dataTypeConfigurationCache, shortStringHelper, jsonSerializer, dataValueReferenceFactories, blockEditorVarianceHandler, languageService, ioHelper, attribute) + { JsonSerializer = jsonSerializer; + _logger = logger; + } /// /// Gets the . @@ -63,7 +101,7 @@ public abstract class BlockEditorPropertyValueEditor : BlockVal private TValue? ParseBlockValue(object? value) { var rawJson = value == null ? string.Empty : value is string str ? str : value.ToString(); - return BlockEditorValues.DeserializeAndClean(rawJson)?.BlockValue; + return SafeParseBlockEditorData(rawJson)?.BlockValue; } /// @@ -71,16 +109,7 @@ public abstract class BlockEditorPropertyValueEditor : BlockVal { var val = property.GetValue(culture, segment); - BlockEditorData? blockEditorData; - try - { - blockEditorData = BlockEditorValues.DeserializeAndClean(val); - } - 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? blockEditorData = SafeParseBlockEditorData(val); if (blockEditorData == null) { @@ -103,8 +132,8 @@ public abstract class BlockEditorPropertyValueEditor : BlockVal // For most of the properties this is fine, but for properties which contain other state it might be critical (e.g. file upload field). // So, we must run MapBlockValueFromEditor even if editorValue is null or string.IsNullOrWhiteSpace(editorValue.Value.ToString()) is true. - BlockEditorData? currentBlockEditorData = GetBlockEditorData(currentValue); - BlockEditorData? blockEditorData = GetBlockEditorData(editorValue.Value); + BlockEditorData? currentBlockEditorData = SafeParseBlockEditorData(currentValue); + BlockEditorData? blockEditorData = SafeParseBlockEditorData(editorValue.Value); // We can skip MapBlockValueFromEditor if both editorValue and currentValue values are empty. if (IsBlockEditorDataEmpty(currentBlockEditorData) && IsBlockEditorDataEmpty(blockEditorData)) @@ -122,19 +151,30 @@ public abstract class BlockEditorPropertyValueEditor : BlockVal return JsonSerializer.Serialize(blockEditorData.BlockValue); } - private BlockEditorData? GetBlockEditorData(object? value) + private static bool IsBlockEditorDataEmpty([NotNullWhen(false)] BlockEditorData? editorData) + => editorData is null || editorData.BlockValue.ContentData.Count == 0; + + // We don't throw on error here because we want to be able to parse what we can, even if some of the data is invalid. In cases where migrating + // from nested content to blocks, we don't want to trigger a fatal error for retrieving references, as this isn't vital to the operation. + // See: https://github.com/umbraco/Umbraco-CMS/issues/19784 and Umbraco support cases. + private BlockEditorData? SafeParseBlockEditorData(object? value) { try { return BlockEditorValues.DeserializeAndClean(value); } + catch (JsonException ex) + { + _logger.LogWarning( + "Could not deserialize the provided property value into a block editor value: {PropertyValue}. Error: {ErrorMessage}.", + value, + ex.Message); + return null; + } catch { // If this occurs it means the data is invalid. It shouldn't happen could if we change the data format. return null; } } - - private static bool IsBlockEditorDataEmpty([NotNullWhen(false)] BlockEditorData? editorData) - => editorData is null || editorData.BlockValue.ContentData.Count == 0; } diff --git a/src/Umbraco.Infrastructure/PropertyEditors/BlockGridPropertyEditorBase.cs b/src/Umbraco.Infrastructure/PropertyEditors/BlockGridPropertyEditorBase.cs index 0f0a48e992..19e518cdcb 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/BlockGridPropertyEditorBase.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/BlockGridPropertyEditorBase.cs @@ -56,7 +56,7 @@ public abstract class BlockGridPropertyEditorBase : DataEditor BlockEditorVarianceHandler blockEditorVarianceHandler, ILanguageService languageService, IIOHelper ioHelper) - : base(propertyEditors, dataValueReferenceFactories, dataTypeConfigurationCache, shortStringHelper, jsonSerializer, blockEditorVarianceHandler, languageService, ioHelper, attribute) + : base(propertyEditors, dataValueReferenceFactories, dataTypeConfigurationCache, shortStringHelper, jsonSerializer, blockEditorVarianceHandler, languageService, ioHelper, attribute, logger) { BlockEditorValues = new BlockEditorValues(new BlockGridEditorDataConverter(jsonSerializer), elementTypeCache, logger); Validators.Add(new BlockEditorValidator(propertyValidationService, BlockEditorValues, elementTypeCache)); diff --git a/src/Umbraco.Infrastructure/PropertyEditors/BlockListPropertyEditorBase.cs b/src/Umbraco.Infrastructure/PropertyEditors/BlockListPropertyEditorBase.cs index 9deb400923..322d2927fb 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/BlockListPropertyEditorBase.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/BlockListPropertyEditorBase.cs @@ -69,7 +69,7 @@ public abstract class BlockListPropertyEditorBase : DataEditor BlockEditorVarianceHandler blockEditorVarianceHandler, ILanguageService languageService, IIOHelper ioHelper) - : base(propertyEditors, dataValueReferenceFactories, dataTypeConfigurationCache, shortStringHelper, jsonSerializer, blockEditorVarianceHandler, languageService, ioHelper, attribute) + : base(propertyEditors, dataValueReferenceFactories, dataTypeConfigurationCache, shortStringHelper, jsonSerializer, blockEditorVarianceHandler, languageService, ioHelper, attribute, logger) { BlockEditorValues = new BlockEditorValues(blockEditorDataConverter, elementTypeCache, logger); Validators.Add(new BlockEditorValidator(propertyValidationService, BlockEditorValues, elementTypeCache));