diff --git a/src/Umbraco.Core/PropertyEditors/IBlockValuePropertyIndexValueFactory.cs b/src/Umbraco.Core/PropertyEditors/IBlockValuePropertyIndexValueFactory.cs new file mode 100644 index 0000000000..8556b993f0 --- /dev/null +++ b/src/Umbraco.Core/PropertyEditors/IBlockValuePropertyIndexValueFactory.cs @@ -0,0 +1,5 @@ +namespace Umbraco.Cms.Core.PropertyEditors; + +public interface IBlockValuePropertyIndexValueFactory : IPropertyIndexValueFactory +{ +} diff --git a/src/Umbraco.Core/PropertyEditors/INestedContentPropertyIndexValueFactory.cs b/src/Umbraco.Core/PropertyEditors/INestedContentPropertyIndexValueFactory.cs new file mode 100644 index 0000000000..eb87a390d2 --- /dev/null +++ b/src/Umbraco.Core/PropertyEditors/INestedContentPropertyIndexValueFactory.cs @@ -0,0 +1,5 @@ +namespace Umbraco.Cms.Core.PropertyEditors; + +public interface INestedContentPropertyIndexValueFactory : IPropertyIndexValueFactory +{ +} diff --git a/src/Umbraco.Core/PropertyEditors/ITagPropertyIndexValueFactory.cs b/src/Umbraco.Core/PropertyEditors/ITagPropertyIndexValueFactory.cs new file mode 100644 index 0000000000..33eabb314c --- /dev/null +++ b/src/Umbraco.Core/PropertyEditors/ITagPropertyIndexValueFactory.cs @@ -0,0 +1,5 @@ +namespace Umbraco.Cms.Core.PropertyEditors; + +public interface ITagPropertyIndexValueFactory : IPropertyIndexValueFactory +{ +} diff --git a/src/Umbraco.Core/PropertyEditors/JsonPropertyIndexValueFactoryBase.cs b/src/Umbraco.Core/PropertyEditors/JsonPropertyIndexValueFactoryBase.cs new file mode 100644 index 0000000000..e639ff7ca8 --- /dev/null +++ b/src/Umbraco.Core/PropertyEditors/JsonPropertyIndexValueFactoryBase.cs @@ -0,0 +1,84 @@ +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Serialization; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Core.PropertyEditors; + +/// +/// Abstract base for property index value factories where the value is json. +/// +/// The type to deserialize the json to. +public abstract class JsonPropertyIndexValueFactoryBase : IPropertyIndexValueFactory +{ + private readonly IJsonSerializer _jsonSerializer; + + /// + /// Constructor for the JsonPropertyIndexValueFactoryBase. + /// + protected JsonPropertyIndexValueFactoryBase(IJsonSerializer jsonSerializer) + { + _jsonSerializer = jsonSerializer; + } + + /// + public IEnumerable>> GetIndexValues( + IProperty property, + string? culture, + string? segment, + bool published) + { + var result = new List>>(); + + var propertyValue = property.GetValue(culture, segment, published); + + // If there is a value, it's a string and it's detected as json. + if (propertyValue is string rawValue && rawValue.DetectIsJson()) + { + try + { + TSerialized? deserializedPropertyValue = _jsonSerializer.Deserialize(rawValue); + + if (deserializedPropertyValue is null) + { + return result; + } + + result.AddRange(Handle(deserializedPropertyValue, property, culture, segment, published)); + } + catch (InvalidCastException) + { + // Swallow...on purpose, there's a chance that this isn't the json format we are looking for + // and we don't want that to affect the website. + } + catch (ArgumentException) + { + // Swallow on purpose to prevent this error: + // Can not add Newtonsoft.Json.Linq.JValue to Newtonsoft.Json.Linq.JObject. + } + } + + result.AddRange(HandleResume(result, property, culture, segment, published)); + + return result; + } + + /// + /// Method to return a list of resume of the content. By default this returns an empty list + /// + protected virtual IEnumerable>> HandleResume( + List>> result, + IProperty property, + string? culture, + string? segment, + bool published) => Array.Empty>>(); + + /// + /// Method that handle the deserialized object. + /// + protected abstract IEnumerable>> Handle( + TSerialized deserializedPropertyValue, + IProperty property, + string? culture, + string? segment, + bool published); +} diff --git a/src/Umbraco.Core/PropertyEditors/NoopPropertyIndexValueFactory.cs b/src/Umbraco.Core/PropertyEditors/NoopPropertyIndexValueFactory.cs new file mode 100644 index 0000000000..7e64b368c4 --- /dev/null +++ b/src/Umbraco.Core/PropertyEditors/NoopPropertyIndexValueFactory.cs @@ -0,0 +1,12 @@ +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Core.PropertyEditors; + +/// +/// Property Index Valye Factory that do not index anything. +/// +public class NoopPropertyIndexValueFactory : IPropertyIndexValueFactory +{ + /// + public IEnumerable>> GetIndexValues(IProperty property, string? culture, string? segment, bool published) => Array.Empty>>(); +} diff --git a/src/Umbraco.Core/PropertyEditors/TagPropertyIndexValueFactory.cs b/src/Umbraco.Core/PropertyEditors/TagPropertyIndexValueFactory.cs new file mode 100644 index 0000000000..83a327e0ef --- /dev/null +++ b/src/Umbraco.Core/PropertyEditors/TagPropertyIndexValueFactory.cs @@ -0,0 +1,21 @@ +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Serialization; + +namespace Umbraco.Cms.Core.PropertyEditors; + +public class TagPropertyIndexValueFactory : JsonPropertyIndexValueFactoryBase, ITagPropertyIndexValueFactory +{ + public TagPropertyIndexValueFactory(IJsonSerializer jsonSerializer) : base(jsonSerializer) + { + } + + protected override IEnumerable>> Handle( + string[] deserializedPropertyValue, + IProperty property, + string? culture, + string? segment, + bool published) + { + yield return new KeyValuePair>(property.Alias, deserializedPropertyValue); + } +} diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs index e529902487..c65e50024c 100644 --- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs +++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs @@ -222,6 +222,19 @@ public static partial class UmbracoBuilderExtensions builder.Services.AddSingleton(); builder.Services.AddTransient(); + + + builder.AddPropertyIndexValueFactories(); + + return builder; + } + + public static IUmbracoBuilder AddPropertyIndexValueFactories(this IUmbracoBuilder builder) + { + builder.Services.AddSingleton(); + builder.Services.AddSingleton(); + builder.Services.AddSingleton(); + return builder; } diff --git a/src/Umbraco.Infrastructure/PropertyEditors/BlockEditorPropertyEditor.cs b/src/Umbraco.Infrastructure/PropertyEditors/BlockEditorPropertyEditor.cs index b939a8c06e..acfe6659c5 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/BlockEditorPropertyEditor.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/BlockEditorPropertyEditor.cs @@ -1,6 +1,9 @@ // Copyright (c) Umbraco. // See LICENSE for more details. +using Microsoft.Extensions.DependencyInjection; +using Umbraco.Cms.Web.Common.DependencyInjection; + namespace Umbraco.Cms.Core.PropertyEditors; // Scheduled for removal in v12 @@ -11,11 +14,26 @@ public abstract class BlockEditorPropertyEditor : BlockListPropertyEditorBase public const string ContentTypeKeyPropertyKey = "contentTypeKey"; public const string UdiPropertyKey = "udi"; + [Obsolete("Use non-obsoleted ctor. This will be removed in Umbraco 13.")] protected BlockEditorPropertyEditor( IDataValueEditorFactory dataValueEditorFactory, PropertyEditorCollection propertyEditors) - : base(dataValueEditorFactory) => + : this( + dataValueEditorFactory, + propertyEditors, + StaticServiceProvider.Instance.GetRequiredService()) + { + + } + + protected BlockEditorPropertyEditor( + IDataValueEditorFactory dataValueEditorFactory, + PropertyEditorCollection propertyEditors, + IBlockValuePropertyIndexValueFactory blockValuePropertyIndexValueFactory) + : base(dataValueEditorFactory, blockValuePropertyIndexValueFactory) + { PropertyEditors = propertyEditors; + } private PropertyEditorCollection PropertyEditors { get; } } diff --git a/src/Umbraco.Infrastructure/PropertyEditors/BlockGridPropertyEditor.cs b/src/Umbraco.Infrastructure/PropertyEditors/BlockGridPropertyEditor.cs index 8881ce82a9..361b8c86f0 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/BlockGridPropertyEditor.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/BlockGridPropertyEditor.cs @@ -1,7 +1,9 @@ // Copyright (c) Umbraco. // See LICENSE for more details. +using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Web.Common.DependencyInjection; namespace Umbraco.Cms.Core.PropertyEditors; @@ -19,11 +21,25 @@ public class BlockGridPropertyEditor : BlockGridPropertyEditorBase { private readonly IIOHelper _ioHelper; + [Obsolete("Use non-obsoleted ctor. This will be removed in Umbraco 13.")] public BlockGridPropertyEditor( IDataValueEditorFactory dataValueEditorFactory, IIOHelper ioHelper) - : base(dataValueEditorFactory) => + : this(dataValueEditorFactory, ioHelper, StaticServiceProvider.Instance.GetRequiredService()) + { + + } + + + public BlockGridPropertyEditor( + IDataValueEditorFactory dataValueEditorFactory, + IIOHelper ioHelper, + IBlockValuePropertyIndexValueFactory blockValuePropertyIndexValueFactory) + : base(dataValueEditorFactory, blockValuePropertyIndexValueFactory) + { _ioHelper = ioHelper; + } + #region Pre Value Editor diff --git a/src/Umbraco.Infrastructure/PropertyEditors/BlockGridPropertyEditorBase.cs b/src/Umbraco.Infrastructure/PropertyEditors/BlockGridPropertyEditorBase.cs index 42a5931a2b..73b767bd4f 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/BlockGridPropertyEditorBase.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/BlockGridPropertyEditorBase.cs @@ -2,6 +2,7 @@ // See LICENSE for more details. using System.ComponentModel.DataAnnotations; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Models; @@ -9,6 +10,7 @@ using Umbraco.Cms.Core.Models.Blocks; using Umbraco.Cms.Core.Serialization; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Strings; +using Umbraco.Cms.Web.Common.DependencyInjection; using Umbraco.Extensions; using BlockGridAreaConfiguration = Umbraco.Cms.Core.PropertyEditors.BlockGridConfiguration.BlockGridAreaConfiguration; @@ -19,9 +21,24 @@ namespace Umbraco.Cms.Core.PropertyEditors; /// public abstract class BlockGridPropertyEditorBase : DataEditor { + private readonly IBlockValuePropertyIndexValueFactory _blockValuePropertyIndexValueFactory; + + [Obsolete("Use non-obsoleted ctor. This will be removed in Umbraco 13.")] protected BlockGridPropertyEditorBase(IDataValueEditorFactory dataValueEditorFactory) - : base(dataValueEditorFactory) => + : this(dataValueEditorFactory, StaticServiceProvider.Instance.GetRequiredService()) + { + + } + + protected BlockGridPropertyEditorBase(IDataValueEditorFactory dataValueEditorFactory, IBlockValuePropertyIndexValueFactory blockValuePropertyIndexValueFactory) + : base(dataValueEditorFactory) + { + _blockValuePropertyIndexValueFactory = blockValuePropertyIndexValueFactory; SupportsReadOnly = true; + } + + public override IPropertyIndexValueFactory PropertyIndexValueFactory => _blockValuePropertyIndexValueFactory; + #region Value Editor diff --git a/src/Umbraco.Infrastructure/PropertyEditors/BlockListPropertyEditor.cs b/src/Umbraco.Infrastructure/PropertyEditors/BlockListPropertyEditor.cs index f36d7b67ff..2f5147cf1a 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/BlockListPropertyEditor.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/BlockListPropertyEditor.cs @@ -25,7 +25,7 @@ public class BlockListPropertyEditor : BlockEditorPropertyEditor private readonly IIOHelper _ioHelper; // Scheduled for removal in v12 - [Obsolete("Please use constructor that takes an IEditorConfigurationParser instead")] + [Obsolete("Use non-obsoleted ctor. This will be removed in Umbraco 13.")] public BlockListPropertyEditor( IDataValueEditorFactory dataValueEditorFactory, PropertyEditorCollection propertyEditors, @@ -34,12 +34,29 @@ public class BlockListPropertyEditor : BlockEditorPropertyEditor { } + [Obsolete("Use non-obsoleted ctor. This will be removed in Umbraco 13.")] public BlockListPropertyEditor( IDataValueEditorFactory dataValueEditorFactory, PropertyEditorCollection propertyEditors, IIOHelper ioHelper, IEditorConfigurationParser editorConfigurationParser) - : base(dataValueEditorFactory, propertyEditors) + : this( + dataValueEditorFactory, + propertyEditors, + ioHelper, + editorConfigurationParser, + StaticServiceProvider.Instance.GetRequiredService()) + { + + } + + public BlockListPropertyEditor( + IDataValueEditorFactory dataValueEditorFactory, + PropertyEditorCollection propertyEditors, + IIOHelper ioHelper, + IEditorConfigurationParser editorConfigurationParser, + IBlockValuePropertyIndexValueFactory blockValuePropertyIndexValueFactory) + : base(dataValueEditorFactory, propertyEditors, blockValuePropertyIndexValueFactory) { _ioHelper = ioHelper; _editorConfigurationParser = editorConfigurationParser; diff --git a/src/Umbraco.Infrastructure/PropertyEditors/BlockListPropertyEditorBase.cs b/src/Umbraco.Infrastructure/PropertyEditors/BlockListPropertyEditorBase.cs index eb09f405a0..194383560e 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/BlockListPropertyEditorBase.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/BlockListPropertyEditorBase.cs @@ -1,4 +1,5 @@ using System.ComponentModel.DataAnnotations; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Models; @@ -6,6 +7,7 @@ using Umbraco.Cms.Core.Models.Blocks; using Umbraco.Cms.Core.Serialization; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Strings; +using Umbraco.Cms.Web.Common.DependencyInjection; namespace Umbraco.Cms.Core.PropertyEditors; @@ -14,9 +16,25 @@ namespace Umbraco.Cms.Core.PropertyEditors; /// public abstract class BlockListPropertyEditorBase : DataEditor { + + private readonly IBlockValuePropertyIndexValueFactory _blockValuePropertyIndexValueFactory; + + [Obsolete("Use non-obsoleted ctor. This will be removed in Umbraco 13.")] protected BlockListPropertyEditorBase(IDataValueEditorFactory dataValueEditorFactory) - : base(dataValueEditorFactory) => + : this(dataValueEditorFactory, StaticServiceProvider.Instance.GetRequiredService()) + { + + } + + protected BlockListPropertyEditorBase(IDataValueEditorFactory dataValueEditorFactory, IBlockValuePropertyIndexValueFactory blockValuePropertyIndexValueFactory) + : base(dataValueEditorFactory) + { + _blockValuePropertyIndexValueFactory = blockValuePropertyIndexValueFactory; SupportsReadOnly = true; + } + + public override IPropertyIndexValueFactory PropertyIndexValueFactory => _blockValuePropertyIndexValueFactory; + #region Value Editor diff --git a/src/Umbraco.Infrastructure/PropertyEditors/BlockValuePropertyIndexValueFactory.cs b/src/Umbraco.Infrastructure/PropertyEditors/BlockValuePropertyIndexValueFactory.cs new file mode 100644 index 0000000000..dfedeedc3f --- /dev/null +++ b/src/Umbraco.Infrastructure/PropertyEditors/BlockValuePropertyIndexValueFactory.cs @@ -0,0 +1,35 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.Blocks; +using Umbraco.Cms.Core.Serialization; +using Umbraco.Cms.Core.Services; + +namespace Umbraco.Cms.Core.PropertyEditors; + +internal sealed class BlockValuePropertyIndexValueFactory : + NestedPropertyIndexValueFactoryBase, + IBlockValuePropertyIndexValueFactory +{ + private readonly IContentTypeService _contentTypeService; + + + public BlockValuePropertyIndexValueFactory( + PropertyEditorCollection propertyEditorCollection, + IContentTypeService contentTypeService, + IJsonSerializer jsonSerializer) + : base(propertyEditorCollection, jsonSerializer) + { + _contentTypeService = contentTypeService; + } + + + protected override IContentType? GetContentTypeOfNestedItem(BlockItemData input) => + _contentTypeService.Get(input.ContentTypeKey); + + protected override IDictionary GetRawProperty(BlockItemData blockItemData) => + blockItemData.RawPropertyValues; + + protected override IEnumerable GetDataItems(BlockValue input) => input.ContentData; +} diff --git a/src/Umbraco.Infrastructure/PropertyEditors/ColorPickerPropertyEditor.cs b/src/Umbraco.Infrastructure/PropertyEditors/ColorPickerPropertyEditor.cs index 1ce8ae4930..00d432070f 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/ColorPickerPropertyEditor.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/ColorPickerPropertyEditor.cs @@ -45,6 +45,9 @@ public class ColorPickerPropertyEditor : DataEditor SupportsReadOnly = true; } + public override IPropertyIndexValueFactory PropertyIndexValueFactory { get; } = new NoopPropertyIndexValueFactory(); + + /// protected override IConfigurationEditor CreateConfigurationEditor() => new ColorPickerConfigurationEditor(_ioHelper, _jsonSerializer, _editorConfigurationParser); diff --git a/src/Umbraco.Infrastructure/PropertyEditors/ImageCropperPropertyEditor.cs b/src/Umbraco.Infrastructure/PropertyEditors/ImageCropperPropertyEditor.cs index c3390b3fc5..ab1e3f976e 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/ImageCropperPropertyEditor.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/ImageCropperPropertyEditor.cs @@ -97,6 +97,8 @@ public class ImageCropperPropertyEditor : DataEditor, IMediaUrlGenerator, SupportsReadOnly = true; } + public override IPropertyIndexValueFactory PropertyIndexValueFactory { get; } = new NoopPropertyIndexValueFactory(); + public bool TryGetMediaPath(string? propertyEditorAlias, object? value, out string? mediaPath) { if (propertyEditorAlias == Alias && diff --git a/src/Umbraco.Infrastructure/PropertyEditors/MediaPicker3PropertyEditor.cs b/src/Umbraco.Infrastructure/PropertyEditors/MediaPicker3PropertyEditor.cs index 09eb6a1f47..f67273a041 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/MediaPicker3PropertyEditor.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/MediaPicker3PropertyEditor.cs @@ -59,6 +59,8 @@ public class MediaPicker3PropertyEditor : DataEditor SupportsReadOnly = true; } + public override IPropertyIndexValueFactory PropertyIndexValueFactory { get; } = new NoopPropertyIndexValueFactory(); + /// protected override IConfigurationEditor CreateConfigurationEditor() => new MediaPicker3ConfigurationEditor(_ioHelper, _editorConfigurationParser); @@ -67,6 +69,8 @@ public class MediaPicker3PropertyEditor : DataEditor protected override IDataValueEditor CreateValueEditor() => DataValueEditorFactory.Create(Attribute!); + + internal class MediaPicker3PropertyValueEditor : DataValueEditor, IDataValueReference { private readonly IDataTypeService _dataTypeService; diff --git a/src/Umbraco.Infrastructure/PropertyEditors/NestedContentPropertyEditor.cs b/src/Umbraco.Infrastructure/PropertyEditors/NestedContentPropertyEditor.cs index f38c88c4bc..d64df34aa4 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/NestedContentPropertyEditor.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/NestedContentPropertyEditor.cs @@ -32,9 +32,9 @@ public class NestedContentPropertyEditor : DataEditor public const string ContentTypeAliasPropertyKey = "ncContentTypeAlias"; private readonly IEditorConfigurationParser _editorConfigurationParser; private readonly IIOHelper _ioHelper; + private readonly INestedContentPropertyIndexValueFactory _nestedContentPropertyIndexValueFactory; - // Scheduled for removal in v12 - [Obsolete("Please use constructor that takes an IEditorConfigurationParser instead")] + [Obsolete("Use non-obsoleted ctor. This will be removed in Umbraco 12.")] public NestedContentPropertyEditor( IDataValueEditorFactory dataValueEditorFactory, IIOHelper ioHelper) @@ -42,17 +42,35 @@ public class NestedContentPropertyEditor : DataEditor { } + [Obsolete("Use non-obsoleted ctor. This will be removed in Umbraco 13.")] public NestedContentPropertyEditor( IDataValueEditorFactory dataValueEditorFactory, IIOHelper ioHelper, IEditorConfigurationParser editorConfigurationParser) + : this( + dataValueEditorFactory, + ioHelper, + editorConfigurationParser, + StaticServiceProvider.Instance.GetRequiredService()) + { + + } + + public NestedContentPropertyEditor( + IDataValueEditorFactory dataValueEditorFactory, + IIOHelper ioHelper, + IEditorConfigurationParser editorConfigurationParser, + INestedContentPropertyIndexValueFactory nestedContentPropertyIndexValueFactory) : base(dataValueEditorFactory) { _ioHelper = ioHelper; _editorConfigurationParser = editorConfigurationParser; + _nestedContentPropertyIndexValueFactory = nestedContentPropertyIndexValueFactory; SupportsReadOnly = true; } + public override IPropertyIndexValueFactory PropertyIndexValueFactory => _nestedContentPropertyIndexValueFactory; + #region Pre Value Editor protected override IConfigurationEditor CreateConfigurationEditor() => diff --git a/src/Umbraco.Infrastructure/PropertyEditors/NestedContentPropertyIndexValueFactory.cs b/src/Umbraco.Infrastructure/PropertyEditors/NestedContentPropertyIndexValueFactory.cs new file mode 100644 index 0000000000..445e1cc361 --- /dev/null +++ b/src/Umbraco.Infrastructure/PropertyEditors/NestedContentPropertyIndexValueFactory.cs @@ -0,0 +1,37 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Serialization; +using Umbraco.Cms.Core.Services; + +namespace Umbraco.Cms.Core.PropertyEditors; + +internal sealed class NestedContentPropertyIndexValueFactory + : NestedPropertyIndexValueFactoryBase< + NestedContentPropertyEditor.NestedContentValues.NestedContentRowValue[], + NestedContentPropertyEditor.NestedContentValues.NestedContentRowValue>, + INestedContentPropertyIndexValueFactory +{ + private readonly IContentTypeService _contentTypeService; + + + public NestedContentPropertyIndexValueFactory( + PropertyEditorCollection propertyEditorCollection, + IContentTypeService contentTypeService, + IJsonSerializer jsonSerializer) : base(propertyEditorCollection, jsonSerializer) + { + _contentTypeService = contentTypeService; + } + + protected override IContentType? GetContentTypeOfNestedItem( + NestedContentPropertyEditor.NestedContentValues.NestedContentRowValue input) + => _contentTypeService.Get(input.ContentTypeAlias); + + protected override IDictionary GetRawProperty( + NestedContentPropertyEditor.NestedContentValues.NestedContentRowValue nestedContentRowValue) => + nestedContentRowValue.RawPropertyValues; + + protected override IEnumerable GetDataItems( + NestedContentPropertyEditor.NestedContentValues.NestedContentRowValue[] input) => input; +} diff --git a/src/Umbraco.Infrastructure/PropertyEditors/NestedPropertyIndexValueFactoryBase.cs b/src/Umbraco.Infrastructure/PropertyEditors/NestedPropertyIndexValueFactoryBase.cs new file mode 100644 index 0000000000..cc2f8143b8 --- /dev/null +++ b/src/Umbraco.Infrastructure/PropertyEditors/NestedPropertyIndexValueFactoryBase.cs @@ -0,0 +1,181 @@ +using System.Text; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Serialization; +using Umbraco.Cms.Infrastructure.Examine; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Core.PropertyEditors; + +internal abstract class NestedPropertyIndexValueFactoryBase : JsonPropertyIndexValueFactoryBase +{ + private readonly PropertyEditorCollection _propertyEditorCollection; + + protected NestedPropertyIndexValueFactoryBase( + PropertyEditorCollection propertyEditorCollection, + IJsonSerializer jsonSerializer) + : base(jsonSerializer) + { + _propertyEditorCollection = propertyEditorCollection; + } + + protected override IEnumerable>> Handle( + TSerialized deserializedPropertyValue, + IProperty property, + string? culture, + string? segment, + bool published) + { + var result = new List>>(); + + foreach (TItem nestedContentRowValue in GetDataItems(deserializedPropertyValue)) + { + IContentType? contentType = GetContentTypeOfNestedItem(nestedContentRowValue); + + if (contentType is null) + { + continue; + } + + var propertyTypeDictionary = + contentType + .PropertyGroups + .SelectMany(x => x.PropertyTypes!) + .ToDictionary(x => x.Alias); + + result.AddRange(GetNestedResults( + property.Alias, + culture, + segment, + published, + propertyTypeDictionary, + nestedContentRowValue)); + } + + return RenameKeysToEnsureRawSegmentsIsAPrefix(result); + } + + /// + /// Rename keys that count the RAW-constant, to ensure the RAW-constant is a prefix. + /// + private IEnumerable>> RenameKeysToEnsureRawSegmentsIsAPrefix( + List>> indexContent) + { + foreach (KeyValuePair> indexedKeyValuePair in indexContent) + { + // Tests if key includes the RawFieldPrefix and it is not in the start + if (indexedKeyValuePair.Key.Substring(1).Contains(UmbracoExamineFieldNames.RawFieldPrefix)) + { + var newKey = UmbracoExamineFieldNames.RawFieldPrefix + + indexedKeyValuePair.Key.Replace(UmbracoExamineFieldNames.RawFieldPrefix, string.Empty); + yield return new KeyValuePair>(newKey, indexedKeyValuePair.Value); + } + else + { + yield return indexedKeyValuePair; + } + } + } + + /// + /// Gets the content type using the nested item. + /// + protected abstract IContentType? GetContentTypeOfNestedItem(TItem nestedItem); + + /// + /// Gets the raw data from a nested item. + /// + protected abstract IDictionary GetRawProperty(TItem nestedItem); + + /// + /// Get the data times of a parent item. E.g. block list have contentData. + /// + protected abstract IEnumerable GetDataItems(TSerialized input); + + /// + /// Index a key with the name of the property, using the relevant content of all the children. + /// + protected override IEnumerable>> HandleResume( + List>> indexedContent, + IProperty property, + string? culture, + string? segment, + bool published) + { + yield return new KeyValuePair>( + property.Alias, + GetResumeFromAllContent(indexedContent).Yield()); + } + + /// + /// Gets a resume as string of all the content in this nested type. + /// + /// All the indexed content for this property. + /// the string with all relevant content from + private static string GetResumeFromAllContent(List>> indexedContent) + { + var stringBuilder = new StringBuilder(); + foreach ((var indexKey, IEnumerable? indexedValue) in indexedContent) + { + // Ignore Raw fields + if (indexKey.Contains(UmbracoExamineFieldNames.RawFieldPrefix)) + { + continue; + } + + foreach (var value in indexedValue) + { + if (value is not null) + { + stringBuilder.AppendLine(value.ToString()); + } + } + } + + return stringBuilder.ToString(); + } + + /// + /// Gets the content to index for the nested type. E.g. Block list, Nested Content, etc.. + /// + private IEnumerable>> GetNestedResults( + string keyPrefix, + string? culture, + string? segment, + bool published, + IDictionary propertyTypeDictionary, + TItem nestedContentRowValue) + { + var blockIndex = 0; + + foreach ((var propertyAlias, var propertyValue) in GetRawProperty(nestedContentRowValue)) + { + if (propertyTypeDictionary.TryGetValue(propertyAlias, out IPropertyType? propertyType)) + { + IProperty subProperty = new Property(propertyType); + subProperty.SetValue(propertyValue, culture, segment); + + if (published) + { + subProperty.PublishValues(culture, segment ?? "*"); + } + + IDataEditor? editor = _propertyEditorCollection[propertyType.PropertyEditorAlias]; + if (editor is null) + { + continue; + } + + IEnumerable>> indexValues = + editor.PropertyIndexValueFactory.GetIndexValues(subProperty, culture, segment, published); + + foreach ((var nestedAlias, IEnumerable nestedValue) in indexValues) + { + yield return new KeyValuePair>( + $"{keyPrefix}.items[{blockIndex}].{nestedAlias}", nestedValue!); + } + } + + blockIndex++; + } + } +} diff --git a/src/Umbraco.Infrastructure/PropertyEditors/TagsPropertyEditor.cs b/src/Umbraco.Infrastructure/PropertyEditors/TagsPropertyEditor.cs index ff646a039d..6f82c8ab3c 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/TagsPropertyEditor.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/TagsPropertyEditor.cs @@ -29,12 +29,13 @@ namespace Umbraco.Cms.Core.PropertyEditors; public class TagsPropertyEditor : DataEditor { private readonly IEditorConfigurationParser _editorConfigurationParser; + private readonly ITagPropertyIndexValueFactory _tagPropertyIndexValueFactory; private readonly IIOHelper _ioHelper; private readonly ILocalizedTextService _localizedTextService; private readonly ManifestValueValidatorCollection _validators; // Scheduled for removal in v12 - [Obsolete("Please use constructor that takes an IEditorConfigurationParser instead")] + [Obsolete("Use non-obsoleted ctor. This will be removed in Umbraco 13.")] public TagsPropertyEditor( IDataValueEditorFactory dataValueEditorFactory, ManifestValueValidatorCollection validators, @@ -45,24 +46,48 @@ public class TagsPropertyEditor : DataEditor validators, ioHelper, localizedTextService, - StaticServiceProvider.Instance.GetRequiredService()) + StaticServiceProvider.Instance.GetRequiredService(), + StaticServiceProvider.Instance.GetRequiredService()) { } + [Obsolete("Use non-obsoleted ctor. This will be removed in Umbraco 13.")] + public TagsPropertyEditor( + IDataValueEditorFactory dataValueEditorFactory, + ManifestValueValidatorCollection validators, + IIOHelper ioHelper, + ILocalizedTextService localizedTextService, + IEditorConfigurationParser editorConfigurationParser) + : this( + dataValueEditorFactory, + validators, + ioHelper, + localizedTextService, + editorConfigurationParser, + StaticServiceProvider.Instance.GetRequiredService()) + { + + } + public TagsPropertyEditor( IDataValueEditorFactory dataValueEditorFactory, ManifestValueValidatorCollection validators, IIOHelper ioHelper, ILocalizedTextService localizedTextService, - IEditorConfigurationParser editorConfigurationParser) + IEditorConfigurationParser editorConfigurationParser, + ITagPropertyIndexValueFactory tagPropertyIndexValueFactory) : base(dataValueEditorFactory) { _validators = validators; _ioHelper = ioHelper; _localizedTextService = localizedTextService; _editorConfigurationParser = editorConfigurationParser; + _tagPropertyIndexValueFactory = tagPropertyIndexValueFactory; } + public override IPropertyIndexValueFactory PropertyIndexValueFactory => _tagPropertyIndexValueFactory; + + protected override IDataValueEditor CreateValueEditor() => DataValueEditorFactory.Create(Attribute!); diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/DataValueEditorReuseTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/DataValueEditorReuseTests.cs index 947c3beecd..d88a9689ab 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/DataValueEditorReuseTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/DataValueEditorReuseTests.cs @@ -95,7 +95,8 @@ public class DataValueEditorReuseTests _dataValueEditorFactoryMock.Object, new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty)), Mock.Of(), - Mock.Of()); + Mock.Of(), + Mock.Of()); // block list is *not* set to reuse its data value editor var dataValueEditor1 = blockListPropertyEditor.GetValueEditor(); @@ -115,7 +116,8 @@ public class DataValueEditorReuseTests _dataValueEditorFactoryMock.Object, new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty)), Mock.Of(), - Mock.Of()); + Mock.Of(), + Mock.Of()); // no matter what, a property editor should never reuse its data value editor when created *with* configuration var dataValueEditor1 = blockListPropertyEditor.GetValueEditor("config"); diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Published/NestedContentTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Published/NestedContentTests.cs index 7109b9cbcc..1fa01615ed 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Published/NestedContentTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Published/NestedContentTests.cs @@ -34,7 +34,11 @@ public class NestedContentTests var localizationService = Mock.Of(); PropertyEditorCollection editors = null; - var editor = new NestedContentPropertyEditor(Mock.Of(), Mock.Of(), Mock.Of()); + var editor = new NestedContentPropertyEditor( + Mock.Of(), + Mock.Of(), + Mock.Of(), + Mock.Of()); editors = new PropertyEditorCollection(new DataEditorCollection(() => new DataEditor[] { editor })); var serializer = new ConfigurationEditorJsonSerializer();