2024-10-20 15:42:13 +02:00
|
|
|
using System.Text;
|
|
|
|
|
using Microsoft.Extensions.Options;
|
|
|
|
|
using Umbraco.Cms.Core.Configuration.Models;
|
|
|
|
|
using Umbraco.Cms.Core.Models;
|
|
|
|
|
using Umbraco.Cms.Core.Models.Blocks;
|
|
|
|
|
using Umbraco.Cms.Core.Serialization;
|
|
|
|
|
using Umbraco.Cms.Infrastructure.Examine;
|
|
|
|
|
using Umbraco.Extensions;
|
|
|
|
|
|
|
|
|
|
namespace Umbraco.Cms.Core.PropertyEditors;
|
|
|
|
|
|
|
|
|
|
internal abstract class BlockValuePropertyIndexValueFactoryBase<TSerialized> : JsonPropertyIndexValueFactoryBase<TSerialized>
|
|
|
|
|
{
|
|
|
|
|
private readonly PropertyEditorCollection _propertyEditorCollection;
|
|
|
|
|
|
|
|
|
|
protected BlockValuePropertyIndexValueFactoryBase(
|
|
|
|
|
PropertyEditorCollection propertyEditorCollection,
|
|
|
|
|
IJsonSerializer jsonSerializer,
|
|
|
|
|
IOptionsMonitor<IndexingSettings> indexingSettings)
|
|
|
|
|
: base(jsonSerializer, indexingSettings)
|
|
|
|
|
{
|
|
|
|
|
_propertyEditorCollection = propertyEditorCollection;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected override IEnumerable<IndexValue> Handle(
|
|
|
|
|
TSerialized deserializedPropertyValue,
|
|
|
|
|
IProperty property,
|
|
|
|
|
string? culture,
|
|
|
|
|
string? segment,
|
|
|
|
|
bool published,
|
|
|
|
|
IEnumerable<string> availableCultures,
|
|
|
|
|
IDictionary<Guid, IContentType> contentTypeDictionary)
|
|
|
|
|
{
|
|
|
|
|
var result = new List<IndexValue>();
|
|
|
|
|
|
|
|
|
|
var index = 0;
|
|
|
|
|
foreach (RawDataItem rawData in GetDataItems(deserializedPropertyValue, published))
|
|
|
|
|
{
|
|
|
|
|
if (contentTypeDictionary.TryGetValue(rawData.ContentTypeKey, out IContentType? contentType) is false)
|
|
|
|
|
{
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var propertyTypeDictionary =
|
|
|
|
|
contentType
|
|
|
|
|
.CompositionPropertyTypes
|
|
|
|
|
.Select(propertyType =>
|
|
|
|
|
{
|
|
|
|
|
// We want to ensure that the nested properties are set vary by culture if the parent is
|
|
|
|
|
// This is because it's perfectly valid to have a nested property type that's set to invariant even if the parent varies.
|
|
|
|
|
// For instance in a block list, the list it self can vary, but the elements can be invariant, at the same time.
|
|
|
|
|
if (culture is not null)
|
|
|
|
|
{
|
|
|
|
|
propertyType.Variations |= ContentVariation.Culture;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (segment is not null)
|
|
|
|
|
{
|
|
|
|
|
propertyType.Variations |= ContentVariation.Segment;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return propertyType;
|
|
|
|
|
})
|
|
|
|
|
.ToDictionary(x => x.Alias);
|
|
|
|
|
|
|
|
|
|
result.AddRange(GetNestedResults(
|
|
|
|
|
$"{property.Alias}.items[{index}]",
|
|
|
|
|
culture,
|
|
|
|
|
segment,
|
|
|
|
|
published,
|
|
|
|
|
propertyTypeDictionary,
|
|
|
|
|
rawData,
|
|
|
|
|
availableCultures,
|
|
|
|
|
contentTypeDictionary));
|
|
|
|
|
|
|
|
|
|
index++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return RenameKeysToEnsureRawSegmentsIsAPrefix(result);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Rename keys that count the RAW-constant, to ensure the RAW-constant is a prefix.
|
|
|
|
|
/// </summary>
|
|
|
|
|
private IEnumerable<IndexValue> RenameKeysToEnsureRawSegmentsIsAPrefix(
|
|
|
|
|
List<IndexValue> indexContent)
|
|
|
|
|
{
|
|
|
|
|
foreach (IndexValue indexValue in indexContent)
|
|
|
|
|
{
|
|
|
|
|
// Tests if key includes the RawFieldPrefix and it is not in the start
|
|
|
|
|
if (indexValue.FieldName.Substring(1).Contains(UmbracoExamineFieldNames.RawFieldPrefix))
|
|
|
|
|
{
|
|
|
|
|
indexValue.FieldName = UmbracoExamineFieldNames.RawFieldPrefix +
|
|
|
|
|
indexValue.FieldName.Replace(UmbracoExamineFieldNames.RawFieldPrefix, string.Empty);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return indexContent;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Get the data items of a parent item. E.g. block list have contentData.
|
|
|
|
|
/// </summary>
|
|
|
|
|
protected abstract IEnumerable<RawDataItem> GetDataItems(TSerialized input, bool published);
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Unwraps block item data as data items.
|
|
|
|
|
/// </summary>
|
|
|
|
|
protected IEnumerable<RawDataItem> GetDataItems(IList<BlockItemData> contentData, IList<BlockItemVariation> expose, bool published)
|
|
|
|
|
{
|
|
|
|
|
if (published is false)
|
|
|
|
|
{
|
|
|
|
|
return contentData.Select(ToRawData);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var indexData = new List<RawDataItem>();
|
|
|
|
|
foreach (BlockItemData blockItemData in contentData)
|
|
|
|
|
{
|
|
|
|
|
var exposedCultures = expose
|
|
|
|
|
.Where(e => e.ContentKey == blockItemData.Key)
|
|
|
|
|
.Select(e => e.Culture)
|
|
|
|
|
.ToArray();
|
|
|
|
|
|
|
|
|
|
if (exposedCultures.Any() is false)
|
|
|
|
|
{
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (exposedCultures.Contains(null)
|
|
|
|
|
|| exposedCultures.ContainsAll(blockItemData.Values.Select(v => v.Culture)))
|
|
|
|
|
{
|
|
|
|
|
indexData.Add(ToRawData(blockItemData));
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
indexData.Add(
|
|
|
|
|
ToRawData(
|
|
|
|
|
blockItemData.ContentTypeKey,
|
2024-10-28 13:19:46 +01:00
|
|
|
blockItemData.Values.Where(value => value.Culture is null || exposedCultures.Contains(value.Culture))));
|
2024-10-20 15:42:13 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return indexData;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Index a key with the name of the property, using the relevant content of all the children.
|
|
|
|
|
/// </summary>
|
|
|
|
|
protected override IEnumerable<IndexValue> HandleResume(
|
|
|
|
|
List<IndexValue> indexedContent,
|
|
|
|
|
IProperty property,
|
|
|
|
|
string? culture,
|
|
|
|
|
string? segment,
|
|
|
|
|
bool published)
|
|
|
|
|
{
|
|
|
|
|
var indexedCultures = indexedContent
|
|
|
|
|
.DistinctBy(v => v.Culture)
|
|
|
|
|
.Select(v => v.Culture)
|
|
|
|
|
.WhereNotNull()
|
|
|
|
|
.ToArray();
|
|
|
|
|
var cultures = indexedCultures.Any()
|
|
|
|
|
? indexedCultures
|
|
|
|
|
: new string?[] { culture };
|
|
|
|
|
|
|
|
|
|
return cultures.Select(c => new IndexValue
|
|
|
|
|
{
|
|
|
|
|
Culture = c, FieldName = property.Alias, Values = [GetResumeFromAllContent(indexedContent, c)]
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Gets a resume as string of all the content in this nested type.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="indexedContent">All the indexed content for this property.</param>
|
2024-10-28 12:58:28 +01:00
|
|
|
/// <param name="culture">The culture to get the resume for.</param>
|
2024-10-20 15:42:13 +02:00
|
|
|
/// <returns>the string with all relevant content from </returns>
|
|
|
|
|
private static string GetResumeFromAllContent(List<IndexValue> indexedContent, string? culture)
|
|
|
|
|
{
|
|
|
|
|
var stringBuilder = new StringBuilder();
|
|
|
|
|
foreach (IndexValue indexValue in indexedContent.Where(v => v.Culture == culture || v.Culture is null))
|
|
|
|
|
{
|
|
|
|
|
// Ignore Raw fields
|
|
|
|
|
if (indexValue.FieldName.Contains(UmbracoExamineFieldNames.RawFieldPrefix))
|
|
|
|
|
{
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
foreach (var value in indexValue.Values)
|
|
|
|
|
{
|
|
|
|
|
if (value is not null)
|
|
|
|
|
{
|
|
|
|
|
stringBuilder.AppendLine(value.ToString());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return stringBuilder.ToString();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Gets the content to index for the nested type. E.g. Block list, Nested Content, etc..
|
|
|
|
|
/// </summary>
|
|
|
|
|
private IEnumerable<IndexValue> GetNestedResults(
|
|
|
|
|
string keyPrefix,
|
|
|
|
|
string? culture,
|
|
|
|
|
string? segment,
|
|
|
|
|
bool published,
|
|
|
|
|
IDictionary<string, IPropertyType> propertyTypeDictionary,
|
|
|
|
|
RawDataItem rawData,
|
|
|
|
|
IEnumerable<string> availableCultures,
|
|
|
|
|
IDictionary<Guid,IContentType> contentTypeDictionary)
|
|
|
|
|
{
|
|
|
|
|
foreach (RawPropertyData rawPropertyData in rawData.Properties)
|
|
|
|
|
{
|
|
|
|
|
if (propertyTypeDictionary.TryGetValue(rawPropertyData.Alias, out IPropertyType? propertyType))
|
|
|
|
|
{
|
|
|
|
|
IDataEditor? editor = _propertyEditorCollection[propertyType.PropertyEditorAlias];
|
|
|
|
|
if (editor is null)
|
|
|
|
|
{
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
IProperty subProperty = new Property(propertyType);
|
|
|
|
|
IEnumerable<IndexValue> indexValues = null!;
|
|
|
|
|
|
|
|
|
|
var propertyCulture = rawPropertyData.Culture ?? culture;
|
|
|
|
|
|
|
|
|
|
if (propertyType.VariesByCulture() && propertyCulture is null)
|
|
|
|
|
{
|
|
|
|
|
foreach (var availableCulture in availableCultures)
|
|
|
|
|
{
|
|
|
|
|
subProperty.SetValue(rawPropertyData.Value, availableCulture, segment);
|
|
|
|
|
if (published)
|
|
|
|
|
{
|
|
|
|
|
subProperty.PublishValues(availableCulture, segment ?? "*");
|
|
|
|
|
}
|
|
|
|
|
indexValues =
|
|
|
|
|
editor.PropertyIndexValueFactory.GetIndexValues(subProperty, availableCulture, segment, published, availableCultures, contentTypeDictionary);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
subProperty.SetValue(rawPropertyData.Value, propertyCulture, segment);
|
|
|
|
|
if (published)
|
|
|
|
|
{
|
|
|
|
|
subProperty.PublishValues(propertyCulture ?? "*", segment ?? "*");
|
|
|
|
|
}
|
|
|
|
|
indexValues = editor.PropertyIndexValueFactory.GetIndexValues(subProperty, propertyCulture, segment, published, availableCultures, contentTypeDictionary);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var rawDataCultures = rawData.Properties.Select(property => property.Culture).Distinct().WhereNotNull().ToArray();
|
|
|
|
|
foreach (IndexValue indexValue in indexValues)
|
|
|
|
|
{
|
|
|
|
|
indexValue.FieldName = $"{keyPrefix}.{indexValue.FieldName}";
|
|
|
|
|
|
|
|
|
|
if (indexValue.Culture is null && rawDataCultures.Any())
|
|
|
|
|
{
|
|
|
|
|
foreach (var rawDataCulture in rawDataCultures)
|
|
|
|
|
{
|
|
|
|
|
yield return new IndexValue
|
|
|
|
|
{
|
|
|
|
|
Culture = rawDataCulture,
|
|
|
|
|
FieldName = indexValue.FieldName,
|
|
|
|
|
Values = indexValue.Values
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
indexValue.Culture = rawDataCultures.Any() ? indexValue.Culture : null;
|
|
|
|
|
yield return indexValue;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private RawDataItem ToRawData(BlockItemData blockItemData)
|
|
|
|
|
=> ToRawData(blockItemData.ContentTypeKey, blockItemData.Values);
|
|
|
|
|
|
|
|
|
|
private RawDataItem ToRawData(Guid contentTypeKey, IEnumerable<BlockPropertyValue> values)
|
|
|
|
|
=> new()
|
|
|
|
|
{
|
|
|
|
|
ContentTypeKey = contentTypeKey,
|
|
|
|
|
Properties = values.Select(value => new RawPropertyData
|
|
|
|
|
{
|
|
|
|
|
Alias = value.Alias,
|
|
|
|
|
Culture = value.Culture,
|
|
|
|
|
Value = value.Value
|
|
|
|
|
})
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
protected class RawDataItem
|
|
|
|
|
{
|
|
|
|
|
public required Guid ContentTypeKey { get; init; }
|
|
|
|
|
|
|
|
|
|
public required IEnumerable<RawPropertyData> Properties { get; init; }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected class RawPropertyData
|
|
|
|
|
{
|
|
|
|
|
public required string Alias { get; init; }
|
|
|
|
|
|
|
|
|
|
public required object? Value { get; init; }
|
|
|
|
|
|
|
|
|
|
public required string? Culture { get; init; }
|
|
|
|
|
}
|
|
|
|
|
}
|