Skip to content
Merged
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
5 changes: 5 additions & 0 deletions src/Umbraco.Core/Constants-PropertyEditors.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ public static class Aliases
/// </summary>
public const string BlockList = "Umbraco.BlockList";

/// <summary>
/// Block List.
/// </summary>
public const string SingleBlock = "Umbraco.SingleBlock";

/// <summary>
/// Block Grid.
/// </summary>
Expand Down
17 changes: 17 additions & 0 deletions src/Umbraco.Core/Models/Blocks/SingleBlockEditorDataConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using Umbraco.Cms.Core.Serialization;

namespace Umbraco.Cms.Core.Models.Blocks;

/// <summary>
/// Data converter for the single block property editor
/// </summary>
public class SingleBlockEditorDataConverter : BlockEditorDataConverter<SingleBlockValue, SingleBlockLayoutItem>
{
public SingleBlockEditorDataConverter(IJsonSerializer jsonSerializer)
: base(jsonSerializer)
{
}

protected override IEnumerable<ContentAndSettingsReference> GetBlockReferences(IEnumerable<SingleBlockLayoutItem> layout)
=> layout.Select(x => new ContentAndSettingsReference(x.ContentKey, x.SettingsKey)).ToList();
}
7 changes: 7 additions & 0 deletions src/Umbraco.Core/Models/Blocks/SingleBlockLayoutItem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
using Umbraco.Cms.Core.PropertyEditors;

namespace Umbraco.Cms.Core.Models.Blocks;

public class SingleBlockLayoutItem : BlockLayoutItemBase
{
}
26 changes: 26 additions & 0 deletions src/Umbraco.Core/Models/Blocks/SingleBlockValue.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using System.Text.Json.Serialization;

namespace Umbraco.Cms.Core.Models.Blocks;

/// <summary>
/// Represents a single block value.
/// </summary>
public class SingleBlockValue : BlockValue<SingleBlockLayoutItem>
{
/// <summary>
/// Initializes a new instance of the <see cref="SingleBlockValue" /> class.
/// </summary>
public SingleBlockValue()
{ }

/// <summary>
/// Initializes a new instance of the <see cref="SingleBlockValue" /> class.
/// </summary>
/// <param name="layout">The layout.</param>
public SingleBlockValue(SingleBlockLayoutItem layout)
=> Layout[PropertyEditorAlias] = [layout];

/// <inheritdoc />
[JsonIgnore]
public override string PropertyEditorAlias => Constants.PropertyEditors.Aliases.SingleBlock;
}
1 change: 1 addition & 0 deletions src/Umbraco.Core/PropertyEditors/BlockListConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ public class BlockListConfiguration
public NumberRange ValidationLimit { get; set; } = new();

[ConfigurationField("useSingleBlockMode")]
[Obsolete("Use SingleBlockPropertyEditor and its configuration instead")]
public bool UseSingleBlockMode { get; set; }

public class BlockConfiguration : IBlockConfiguration
Expand Down
13 changes: 13 additions & 0 deletions src/Umbraco.Core/PropertyEditors/SingleBlockConfiguration.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Copyright (c) Umbraco.
// See LICENSE for more details.

namespace Umbraco.Cms.Core.PropertyEditors;

/// <summary>
/// The configuration object for the Single Block editor
/// </summary>
public class SingleBlockConfiguration
{
[ConfigurationField("blocks")]
public BlockListConfiguration.BlockConfiguration[] Blocks { get; set; } = [];
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,16 @@ internal abstract class BlockEditorMinMaxValidatorBase<TValue, TLayout> : IValue
/// <inheritdoc/>
public abstract IEnumerable<ValidationResult> Validate(object? value, string? valueType, object? dataTypeConfiguration, PropertyValidationContext validationContext);

// internal method so we can test for specific error messages being returned without keeping strings in sync
internal static string BuildErrorMessage(
ILocalizedTextService textService,
int? maxNumberOfBlocks,
int numberOfBlocks)
=> textService.Localize(
"validation",
"entriesExceed",
[maxNumberOfBlocks.ToString(), (numberOfBlocks - maxNumberOfBlocks).ToString(),]);

/// <summary>
/// Validates the number of blocks are within the configured minimum and maximum values.
/// </summary>
Expand All @@ -53,10 +63,7 @@ protected IEnumerable<ValidationResult> ValidateNumberOfBlocks(BlockEditorData<T
if (blockEditorData != null && max.HasValue && numberOfBlocks > max)
{
yield return new ValidationResult(
TextService.Localize(
"validation",
"entriesExceed",
[max.ToString(), (numberOfBlocks - max).ToString(),]),
BuildErrorMessage(TextService, max, numberOfBlocks),
["value"]);
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using Umbraco.Cms.Core.IO;
using Umbraco.Cms.Core.PropertyEditors;

namespace Umbraco.Cms.Infrastructure.PropertyEditors;

internal sealed class SingleBlockConfigurationEditor : ConfigurationEditor<SingleBlockConfiguration>
{
public SingleBlockConfigurationEditor(IIOHelper ioHelper)
: base(ioHelper)
{
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
using System.ComponentModel.DataAnnotations;
using Microsoft.Extensions.Logging;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Cache;
using Umbraco.Cms.Core.Cache.PropertyEditors;
using Umbraco.Cms.Core.IO;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.Blocks;
using Umbraco.Cms.Core.Models.Validation;
using Umbraco.Cms.Core.PropertyEditors;
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.Infrastructure.PropertyEditors;

/// <summary>
/// Represents a single block property editor.
/// </summary>
[DataEditor(
Constants.PropertyEditors.Aliases.SingleBlock,
ValueType = ValueTypes.Json,
ValueEditorIsReusable = false)]
public class SingleBlockPropertyEditor : DataEditor
{
private readonly IJsonSerializer _jsonSerializer;
private readonly IIOHelper _ioHelper;
private readonly IBlockValuePropertyIndexValueFactory _blockValuePropertyIndexValueFactory;

public SingleBlockPropertyEditor(
IDataValueEditorFactory dataValueEditorFactory,
IJsonSerializer jsonSerializer,
IIOHelper ioHelper,
IBlockValuePropertyIndexValueFactory blockValuePropertyIndexValueFactory)
: base(dataValueEditorFactory)
{
_jsonSerializer = jsonSerializer;
_ioHelper = ioHelper;
_blockValuePropertyIndexValueFactory = blockValuePropertyIndexValueFactory;
}

public override IPropertyIndexValueFactory PropertyIndexValueFactory => _blockValuePropertyIndexValueFactory;

/// <inheritdoc/>
public override bool SupportsConfigurableElements => true;

/// <summary>
/// Instantiates a new <see cref="BlockEditorDataConverter{SingleBlockValue, SingleBlockLayoutItem}"/> for use with the single block editor property value editor.
/// </summary>
/// <returns>A new instance of <see cref="SingleBlockEditorDataConverter"/>.</returns>
protected virtual BlockEditorDataConverter<SingleBlockValue, SingleBlockLayoutItem> CreateBlockEditorDataConverter()
=> new SingleBlockEditorDataConverter(_jsonSerializer);

/// <inheritdoc/>
protected override IDataValueEditor CreateValueEditor() =>
DataValueEditorFactory.Create<SingleBlockEditorPropertyValueEditor>(Attribute!, CreateBlockEditorDataConverter());

/// <inheritdoc />
public override bool CanMergePartialPropertyValues(IPropertyType propertyType) => propertyType.VariesByCulture() is false;

/// <inheritdoc />
public override object? MergePartialPropertyValueForCulture(object? sourceValue, object? targetValue, string? culture)
{
var valueEditor = (SingleBlockEditorPropertyValueEditor)GetValueEditor();
return valueEditor.MergePartialPropertyValueForCulture(sourceValue, targetValue, culture);
}

/// <inheritdoc/>
protected override IConfigurationEditor CreateConfigurationEditor() =>
new SingleBlockConfigurationEditor(_ioHelper);

/// <inheritdoc/>
public override object? MergeVariantInvariantPropertyValue(
object? sourceValue,
object? targetValue,
bool canUpdateInvariantData,
HashSet<string> allowedCultures)
{
var valueEditor = (SingleBlockEditorPropertyValueEditor)GetValueEditor();
return valueEditor.MergeVariantInvariantPropertyValue(sourceValue, targetValue, canUpdateInvariantData, allowedCultures);
}

internal sealed class SingleBlockEditorPropertyValueEditor : BlockEditorPropertyValueEditor<SingleBlockValue, SingleBlockLayoutItem>
{
public SingleBlockEditorPropertyValueEditor(
DataEditorAttribute attribute,
BlockEditorDataConverter<SingleBlockValue, SingleBlockLayoutItem> blockEditorDataConverter,
PropertyEditorCollection propertyEditors,
DataValueReferenceFactoryCollection dataValueReferenceFactories,
IDataTypeConfigurationCache dataTypeConfigurationCache,
IShortStringHelper shortStringHelper,
IJsonSerializer jsonSerializer,
BlockEditorVarianceHandler blockEditorVarianceHandler,
ILanguageService languageService,
IIOHelper ioHelper,
IBlockEditorElementTypeCache elementTypeCache,
ILogger<SingleBlockEditorPropertyValueEditor> logger,
ILocalizedTextService textService,
IPropertyValidationService propertyValidationService)
: base(propertyEditors, dataValueReferenceFactories, dataTypeConfigurationCache, shortStringHelper, jsonSerializer, blockEditorVarianceHandler, languageService, ioHelper, attribute)
{
BlockEditorValues = new BlockEditorValues<SingleBlockValue, SingleBlockLayoutItem>(blockEditorDataConverter, elementTypeCache, logger);
Validators.Add(new BlockEditorValidator<SingleBlockValue, SingleBlockLayoutItem>(propertyValidationService, BlockEditorValues, elementTypeCache));
Validators.Add(new SingleBlockValidator(BlockEditorValues, textService));
}

Check warning on line 107 in src/Umbraco.Infrastructure/PropertyEditors/SingleBlockPropertyEditor.cs

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (v17/dev)

❌ New issue: Constructor Over-Injection

SingleBlockEditorPropertyValueEditor has 14 arguments, threshold = 5. This constructor has too many arguments, indicating an object with low cohesion or missing function argument abstraction. Avoid adding more arguments.

protected override SingleBlockValue CreateWithLayout(IEnumerable<SingleBlockLayoutItem> layout) =>
new(layout.Single());

/// <inheritdoc/>
public override IEnumerable<Guid> ConfiguredElementTypeKeys()
{
var configuration = ConfigurationObject as SingleBlockConfiguration;
return configuration?.Blocks.SelectMany(ConfiguredElementTypeKeys) ?? Enumerable.Empty<Guid>();
}

/// <summary>
/// Validates whether the block editor holds a single value
/// </summary>
internal sealed class SingleBlockValidator : BlockEditorMinMaxValidatorBase<SingleBlockValue, SingleBlockLayoutItem>
{
private readonly BlockEditorValues<SingleBlockValue, SingleBlockLayoutItem> _blockEditorValues;

public SingleBlockValidator(BlockEditorValues<SingleBlockValue, SingleBlockLayoutItem> blockEditorValues, ILocalizedTextService textService)
: base(textService) =>
_blockEditorValues = blockEditorValues;

public override IEnumerable<ValidationResult> Validate(object? value, string? valueType, object? dataTypeConfiguration, PropertyValidationContext validationContext)
{
BlockEditorData<SingleBlockValue, SingleBlockLayoutItem>? blockEditorData = _blockEditorValues.DeserializeAndClean(value);

return ValidateNumberOfBlocks(blockEditorData, 0, 1);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
// Copyright (c) Umbraco.
// See LICENSE for more details.

using Umbraco.Cms.Core;
using Umbraco.Cms.Core.DeliveryApi;
using Umbraco.Cms.Core.Logging;
using Umbraco.Cms.Core.Models.Blocks;
using Umbraco.Cms.Core.Models.DeliveryApi;
using Umbraco.Cms.Core.Models.PublishedContent;
using Umbraco.Cms.Core.PropertyEditors;
using Umbraco.Cms.Core.PropertyEditors.DeliveryApi;
using Umbraco.Cms.Core.PropertyEditors.ValueConverters;
using Umbraco.Cms.Core.Serialization;
using Umbraco.Cms.Infrastructure.Extensions;
using Umbraco.Extensions;

namespace Umbraco.Cms.Infrastructure.PropertyEditors.ValueConverters;

[DefaultPropertyValueConverter(typeof(JsonValueConverter))]
public class SingleBlockPropertyValueConverter : PropertyValueConverterBase, IDeliveryApiPropertyValueConverter
{
private readonly IProfilingLogger _proflog;
private readonly BlockEditorConverter _blockConverter;
private readonly IApiElementBuilder _apiElementBuilder;
private readonly IJsonSerializer _jsonSerializer;
private readonly BlockListPropertyValueConstructorCache _constructorCache;
private readonly IVariationContextAccessor _variationContextAccessor;
private readonly BlockEditorVarianceHandler _blockEditorVarianceHandler;

public SingleBlockPropertyValueConverter(
IProfilingLogger proflog,
BlockEditorConverter blockConverter,
IApiElementBuilder apiElementBuilder,
IJsonSerializer jsonSerializer,
BlockListPropertyValueConstructorCache constructorCache,
IVariationContextAccessor variationContextAccessor,
BlockEditorVarianceHandler blockEditorVarianceHandler)
{
_proflog = proflog;
_blockConverter = blockConverter;
_apiElementBuilder = apiElementBuilder;
_jsonSerializer = jsonSerializer;
_constructorCache = constructorCache;
_variationContextAccessor = variationContextAccessor;
_blockEditorVarianceHandler = blockEditorVarianceHandler;
}

Check warning on line 46 in src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/SingleBlockPropertyValueConverter.cs

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (v17/dev)

❌ New issue: Constructor Over-Injection

SingleBlockPropertyValueConverter has 7 arguments, threshold = 5. This constructor has too many arguments, indicating an object with low cohesion or missing function argument abstraction. Avoid adding more arguments.

/// <inheritdoc />
public override bool IsConverter(IPublishedPropertyType propertyType)
=> propertyType.EditorAlias.InvariantEquals(Constants.PropertyEditors.Aliases.SingleBlock);

/// <inheritdoc />
public override Type GetPropertyValueType(IPublishedPropertyType propertyType) => typeof( BlockListItem);

/// <inheritdoc />
public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType)
=> PropertyCacheLevel.Element;

/// <inheritdoc />
public override object? ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object? source, bool preview)
=> source?.ToString();

/// <inheritdoc />
public override object? ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object? inter, bool preview)
{
// NOTE: The intermediate object is just a JSON string, we don't actually convert from source -> intermediate since source is always just a JSON string
using (!_proflog.IsEnabled(Core.Logging.LogLevel.Debug) ? null : _proflog.DebugDuration<BlockListPropertyValueConverter>(
$"ConvertPropertyToBlockList ({propertyType.DataType.Id})"))
{
return ConvertIntermediateToBlockListItem(owner, propertyType, referenceCacheLevel, inter, preview);
}
}

Check warning on line 72 in src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/SingleBlockPropertyValueConverter.cs

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (v17/dev)

❌ New issue: Excess Number of Function Arguments

ConvertIntermediateToObject has 5 arguments, threshold = 4. This function has too many arguments, indicating a lack of encapsulation. Avoid adding more arguments.

/// <inheritdoc />
public PropertyCacheLevel GetDeliveryApiPropertyCacheLevel(IPublishedPropertyType propertyType) => GetPropertyCacheLevel(propertyType);

/// <inheritdoc />
public PropertyCacheLevel GetDeliveryApiPropertyCacheLevelForExpansion(IPublishedPropertyType propertyType) => PropertyCacheLevel.Snapshot;

/// <inheritdoc />
public Type GetDeliveryApiPropertyValueType(IPublishedPropertyType propertyType)
=> typeof(ApiBlockItem);

/// <inheritdoc />
public object? ConvertIntermediateToDeliveryApiObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object? inter, bool preview, bool expanding)
{
BlockListItem? model = ConvertIntermediateToBlockListItem(owner, propertyType, referenceCacheLevel, inter, preview);

return
model?.CreateApiBlockItem(_apiElementBuilder);
}

Check warning on line 91 in src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/SingleBlockPropertyValueConverter.cs

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (v17/dev)

❌ New issue: Excess Number of Function Arguments

ConvertIntermediateToDeliveryApiObject has 6 arguments, threshold = 4. This function has too many arguments, indicating a lack of encapsulation. Avoid adding more arguments.

private BlockListItem? ConvertIntermediateToBlockListItem(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object? inter, bool preview)
{
using (!_proflog.IsEnabled(LogLevel.Debug) ? null : _proflog.DebugDuration<SingleBlockPropertyValueConverter>(
$"ConvertPropertyToSingleBlock ({propertyType.DataType.Id})"))
{
// NOTE: The intermediate object is just a JSON string, we don't actually convert from source -> intermediate since source is always just a JSON string
if (inter is not string intermediateBlockModelValue)
{
return null;
}

// Get configuration
SingleBlockConfiguration? configuration = propertyType.DataType.ConfigurationAs<SingleBlockConfiguration>();
if (configuration is null)
{
return null;
}


var creator = new SingleBlockPropertyValueCreator(_blockConverter, _variationContextAccessor, _blockEditorVarianceHandler, _jsonSerializer, _constructorCache);
return creator.CreateBlockModel(owner, referenceCacheLevel, intermediateBlockModelValue, preview, configuration.Blocks);
}
}

Check warning on line 115 in src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/SingleBlockPropertyValueConverter.cs

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (v17/dev)

❌ New issue: Excess Number of Function Arguments

ConvertIntermediateToBlockListItem has 5 arguments, threshold = 4. This function has too many arguments, indicating a lack of encapsulation. Avoid adding more arguments.
}
Loading