diff --git a/src/Umbraco.Core/Composing/BuilderCollectionBase.cs b/src/Umbraco.Core/Composing/BuilderCollectionBase.cs
index 722a6efca0..e92790ab4e 100644
--- a/src/Umbraco.Core/Composing/BuilderCollectionBase.cs
+++ b/src/Umbraco.Core/Composing/BuilderCollectionBase.cs
@@ -3,7 +3,7 @@ using System.Collections;
namespace Umbraco.Cms.Core.Composing;
///
-/// Provides a base class for builder collections.
+/// Provides a base class for builder collections.
///
/// The type of the items.
public abstract class BuilderCollectionBase : IBuilderCollection
@@ -11,21 +11,18 @@ public abstract class BuilderCollectionBase : IBuilderCollection
private readonly LazyReadOnlyCollection _items;
///
- /// Initializes a new instance of the with items.
+ /// Initializes a new instance of the with items.
///
/// The items.
- public BuilderCollectionBase(Func> items) => _items = new LazyReadOnlyCollection(items);
+ public BuilderCollectionBase(Func> items)
+ => _items = new LazyReadOnlyCollection(items);
///
public int Count => _items.Count;
- ///
- /// Gets an enumerator.
- ///
+ ///
public IEnumerator GetEnumerator() => _items.GetEnumerator();
- ///
- /// Gets an enumerator.
- ///
+ ///
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
diff --git a/src/Umbraco.Core/Composing/IBuilderCollection.cs b/src/Umbraco.Core/Composing/IBuilderCollection.cs
index 56036997bc..c8920149c5 100644
--- a/src/Umbraco.Core/Composing/IBuilderCollection.cs
+++ b/src/Umbraco.Core/Composing/IBuilderCollection.cs
@@ -1,13 +1,16 @@
namespace Umbraco.Cms.Core.Composing;
///
-/// Represents a builder collection, ie an immutable enumeration of items.
+/// Represents a builder collection, ie an immutable enumeration of items.
///
/// The type of the items.
public interface IBuilderCollection : IEnumerable
{
///
- /// Gets the number of items in the collection.
+ /// Gets the number of items in the collection.
///
+ ///
+ /// The count.
+ ///
int Count { get; }
}
diff --git a/src/Umbraco.Core/Models/Editors/UmbracoEntityReference.cs b/src/Umbraco.Core/Models/Editors/UmbracoEntityReference.cs
index c093962408..3f02be10b1 100644
--- a/src/Umbraco.Core/Models/Editors/UmbracoEntityReference.cs
+++ b/src/Umbraco.Core/Models/Editors/UmbracoEntityReference.cs
@@ -1,49 +1,88 @@
namespace Umbraco.Cms.Core.Models.Editors;
///
-/// Used to track reference to other entities in a property value
+/// Used to track a reference to another entity in a property value.
///
public struct UmbracoEntityReference : IEquatable
{
private static readonly UmbracoEntityReference _empty = new(UnknownTypeUdi.Instance, string.Empty);
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The UDI.
+ /// The relation type alias.
public UmbracoEntityReference(Udi udi, string relationTypeAlias)
{
Udi = udi ?? throw new ArgumentNullException(nameof(udi));
RelationTypeAlias = relationTypeAlias ?? throw new ArgumentNullException(nameof(relationTypeAlias));
}
+ ///
+ /// Initializes a new instance of the struct for a document or media item.
+ ///
+ /// The UDI.
public UmbracoEntityReference(Udi udi)
{
Udi = udi ?? throw new ArgumentNullException(nameof(udi));
switch (udi.EntityType)
{
+ case Constants.UdiEntityType.Document:
+ RelationTypeAlias = Constants.Conventions.RelationTypes.RelatedDocumentAlias;
+ break;
case Constants.UdiEntityType.Media:
RelationTypeAlias = Constants.Conventions.RelationTypes.RelatedMediaAlias;
break;
default:
- RelationTypeAlias = Constants.Conventions.RelationTypes.RelatedDocumentAlias;
+ // No relation type alias convention for this entity type, so leave it empty
+ RelationTypeAlias = string.Empty;
break;
}
}
+ ///
+ /// Gets the UDI.
+ ///
+ ///
+ /// The UDI.
+ ///
public Udi Udi { get; }
- public static UmbracoEntityReference Empty() => _empty;
-
- public static bool IsEmpty(UmbracoEntityReference reference) => reference == Empty();
-
+ ///
+ /// Gets the relation type alias.
+ ///
+ ///
+ /// The relation type alias.
+ ///
public string RelationTypeAlias { get; }
- public static bool operator ==(UmbracoEntityReference left, UmbracoEntityReference right) => left.Equals(right);
+ ///
+ /// Gets an empty reference.
+ ///
+ ///
+ /// An empty reference.
+ ///
+ public static UmbracoEntityReference Empty() => _empty;
+ ///
+ /// Determines whether the specified reference is empty.
+ ///
+ /// The reference.
+ ///
+ /// true if the specified reference is empty; otherwise, false .
+ ///
+ public static bool IsEmpty(UmbracoEntityReference reference) => reference == Empty();
+
+ ///
public override bool Equals(object? obj) => obj is UmbracoEntityReference reference && Equals(reference);
+ ///
public bool Equals(UmbracoEntityReference other) =>
EqualityComparer.Default.Equals(Udi, other.Udi) &&
RelationTypeAlias == other.RelationTypeAlias;
+ ///
public override int GetHashCode()
{
var hashCode = -487348478;
@@ -52,5 +91,9 @@ public struct UmbracoEntityReference : IEquatable
return hashCode;
}
+ ///
+ public static bool operator ==(UmbracoEntityReference left, UmbracoEntityReference right) => left.Equals(right);
+
+ ///
public static bool operator !=(UmbracoEntityReference left, UmbracoEntityReference right) => !(left == right);
}
diff --git a/src/Umbraco.Core/PropertyEditors/DataValueReferenceFactoryCollection.cs b/src/Umbraco.Core/PropertyEditors/DataValueReferenceFactoryCollection.cs
index 24d6f17eb0..c173e766e0 100644
--- a/src/Umbraco.Core/PropertyEditors/DataValueReferenceFactoryCollection.cs
+++ b/src/Umbraco.Core/PropertyEditors/DataValueReferenceFactoryCollection.cs
@@ -4,64 +4,148 @@ using Umbraco.Cms.Core.Models.Editors;
namespace Umbraco.Cms.Core.PropertyEditors;
+///
+/// Provides a builder collection for items.
+///
public class DataValueReferenceFactoryCollection : BuilderCollectionBase
{
- public DataValueReferenceFactoryCollection(Func> items)
- : base(items)
- {
- }
-
// TODO: We could further reduce circular dependencies with PropertyEditorCollection by not having IDataValueReference implemented
// by property editors and instead just use the already built in IDataValueReferenceFactory and/or refactor that into a more normal collection
- public IEnumerable GetAllReferences(
- IPropertyCollection properties,
- PropertyEditorCollection propertyEditors)
- {
- var trackedRelations = new HashSet();
- foreach (IProperty p in properties)
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The items.
+ public DataValueReferenceFactoryCollection(Func> items)
+ : base(items)
+ { }
+
+ ///
+ /// Gets all unique references from the specified properties.
+ ///
+ /// The properties.
+ /// The property editors.
+ ///
+ /// The unique references from the specified properties.
+ ///
+ public ISet GetAllReferences(IPropertyCollection properties, PropertyEditorCollection propertyEditors)
+ {
+ var references = new HashSet();
+
+ foreach (IProperty property in properties)
{
- if (!propertyEditors.TryGet(p.PropertyType.PropertyEditorAlias, out IDataEditor? editor))
+ if (!propertyEditors.TryGet(property.PropertyType.PropertyEditorAlias, out IDataEditor? dataEditor))
{
continue;
}
- // TODO: We will need to change this once we support tracking via variants/segments
- // for now, we are tracking values from ALL variants
- foreach (IPropertyValue propertyVal in p.Values)
+ // Only use edited value for now
+ references.UnionWith(GetReferences(dataEditor, property.Values.Select(x => x.EditedValue)));
+ }
+
+ return references;
+ }
+
+ ///
+ /// Gets the references.
+ ///
+ /// The data editor.
+ /// The values.
+ ///
+ /// The references.
+ ///
+ public IEnumerable GetReferences(IDataEditor dataEditor, params object?[] values)
+ => GetReferences(dataEditor, (IEnumerable)values);
+
+ ///
+ /// Gets the references.
+ ///
+ /// The data editor.
+ /// The values.
+ ///
+ /// The references.
+ ///
+ public IEnumerable GetReferences(IDataEditor dataEditor, IEnumerable values)
+ {
+ // TODO: We will need to change this once we support tracking via variants/segments
+ // for now, we are tracking values from ALL variants
+ if (dataEditor.GetValueEditor() is IDataValueReference dataValueReference)
+ {
+ foreach (UmbracoEntityReference reference in values.SelectMany(dataValueReference.GetReferences))
{
- var val = propertyVal.EditedValue;
-
- IDataValueEditor? valueEditor = editor?.GetValueEditor();
- if (valueEditor is IDataValueReference reference)
- {
- IEnumerable refs = reference.GetReferences(val);
- foreach (UmbracoEntityReference r in refs)
- {
- trackedRelations.Add(r);
- }
- }
-
- // Loop over collection that may be add to existing property editors
- // implementation of GetReferences in IDataValueReference.
- // Allows developers to add support for references by a
- // package /property editor that did not implement IDataValueReference themselves
- foreach (IDataValueReferenceFactory item in this)
- {
- // Check if this value reference is for this datatype/editor
- // Then call it's GetReferences method - to see if the value stored
- // in the dataeditor/property has referecnes to media/content items
- if (item.IsForEditor(editor))
- {
- foreach (UmbracoEntityReference r in item.GetDataValueReference().GetReferences(val))
- {
- trackedRelations.Add(r);
- }
- }
- }
+ yield return reference;
}
}
- return trackedRelations;
+ // Loop over collection that may be add to existing property editors
+ // implementation of GetReferences in IDataValueReference.
+ // Allows developers to add support for references by a
+ // package /property editor that did not implement IDataValueReference themselves
+ foreach (IDataValueReferenceFactory dataValueReferenceFactory in this)
+ {
+ // Check if this value reference is for this datatype/editor
+ // Then call it's GetReferences method - to see if the value stored
+ // in the dataeditor/property has references to media/content items
+ if (dataValueReferenceFactory.IsForEditor(dataEditor))
+ {
+ IDataValueReference factoryDataValueReference = dataValueReferenceFactory.GetDataValueReference();
+ foreach (UmbracoEntityReference reference in values.SelectMany(factoryDataValueReference.GetReferences))
+ {
+ yield return reference;
+ }
+ }
+ }
+ }
+
+ ///
+ /// Gets all relation type aliases that are automatically tracked.
+ ///
+ /// The property editors.
+ ///
+ /// All relation type aliases that are automatically tracked.
+ ///
+ public ISet GetAllAutomaticRelationTypesAliases(PropertyEditorCollection propertyEditors)
+ {
+ // Always add default automatic relation types
+ var automaticRelationTypeAliases = new HashSet(Constants.Conventions.RelationTypes.AutomaticRelationTypes);
+
+ // Add relation types for all property editors
+ foreach (IDataEditor dataEditor in propertyEditors)
+ {
+ automaticRelationTypeAliases.UnionWith(GetAutomaticRelationTypesAliases(dataEditor));
+ }
+
+ return automaticRelationTypeAliases;
+ }
+
+ ///
+ /// Gets the automatic relation types aliases.
+ ///
+ /// The data editor.
+ ///
+ /// The automatic relation types aliases.
+ ///
+ public IEnumerable GetAutomaticRelationTypesAliases(IDataEditor dataEditor)
+ {
+ if (dataEditor.GetValueEditor() is IDataValueReference dataValueReference)
+ {
+ // Return custom relation types from value editor implementation
+ foreach (var alias in dataValueReference.GetAutomaticRelationTypesAliases())
+ {
+ yield return alias;
+ }
+ }
+
+ foreach (IDataValueReferenceFactory dataValueReferenceFactory in this)
+ {
+ if (dataValueReferenceFactory.IsForEditor(dataEditor))
+ {
+ // Return custom relation types from factory
+ foreach (var alias in dataValueReferenceFactory.GetDataValueReference().GetAutomaticRelationTypesAliases())
+ {
+ yield return alias;
+ }
+ }
+ }
}
}
diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentRepositoryBase.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentRepositoryBase.cs
index 4f6aa228d0..6569ad4d89 100644
--- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentRepositoryBase.cs
+++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentRepositoryBase.cs
@@ -1083,91 +1083,59 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
protected void PersistRelations(TEntity entity)
{
// Get all references from our core built in DataEditors/Property Editors
- // Along with seeing if deverlopers want to collect additional references from the DataValueReferenceFactories collection
- var trackedRelations = new List();
- trackedRelations.AddRange(_dataValueReferenceFactories.GetAllReferences(entity.Properties, PropertyEditors));
-
- var relationTypeAliases = GetAutomaticRelationTypesAliases(entity.Properties, PropertyEditors).ToList();
-
- // At this point we potentially have a problem (see for example: https://github.com/umbraco/Umbraco.Forms.Issues/issues/1129).
- // If we have a custom relation type (i.e. not document or media) and use of this within a block grid/list property editor,
- // we'll get an error when saving the relations.
- // It happens because the block grid doesn't expose the automatic relation type aliases for all of it's nested properties, and
- // as such relationTypeAliases does not contain the custom relation type alias.
- // Auto-relations of that type are then then not deleted, and we get duplicate error on insert.
- // To resolve we can look at the relations we are going to be saving, and if they include any relation type aliases we haven't
- // already identified, we'll add them in, so they will also be removed along with the other auto-relations.
- relationTypeAliases.AddRange(trackedRelations.Select(x => x.RelationTypeAlias).Distinct().Except(relationTypeAliases));
+ // Along with seeing if developers want to collect additional references from the DataValueReferenceFactories collection
+ ISet references = _dataValueReferenceFactories.GetAllReferences(entity.Properties, PropertyEditors);
// First delete all auto-relations for this entity
- RelationRepository.DeleteByParent(entity.Id, relationTypeAliases.ToArray());
+ ISet automaticRelationTypeAliases = _dataValueReferenceFactories.GetAllAutomaticRelationTypesAliases(PropertyEditors);
+ RelationRepository.DeleteByParent(entity.Id, automaticRelationTypeAliases.ToArray());
- if (trackedRelations.Count == 0)
+ if (references.Count == 0)
{
+ // No need to add new references/relations
return;
}
- trackedRelations = trackedRelations.Distinct().ToList();
- var udiToGuids = trackedRelations.Select(x => x.Udi as GuidUdi)
- .ToDictionary(x => (Udi)x!, x => x!.Guid);
-
- // lookup in the DB all INT ids for the GUIDs and chuck into a dictionary
- var keyToIds = Database.Fetch(Sql()
- .Select(x => x.NodeId, x => x.UniqueId)
- .From()
- .WhereIn(x => x.UniqueId, udiToGuids.Values))
- .ToDictionary(x => x.UniqueId, x => x.NodeId);
-
- var allRelationTypes = RelationTypeRepository.GetMany(Array.Empty())?
- .ToDictionary(x => x.Alias, x => x);
-
- IEnumerable toSave = trackedRelations.Select(rel =>
- {
- if (allRelationTypes is null || !allRelationTypes.TryGetValue(rel.RelationTypeAlias, out IRelationType? relationType))
- {
- throw new InvalidOperationException($"The relation type {rel.RelationTypeAlias} does not exist");
- }
-
- if (!udiToGuids.TryGetValue(rel.Udi, out Guid guid))
- {
- return null; // This shouldn't happen!
- }
-
- if (!keyToIds.TryGetValue(guid, out var id))
- {
- return null; // This shouldn't happen!
- }
-
- return new ReadOnlyRelation(entity.Id, id, relationType.Id);
- }).WhereNotNull();
-
- // Save bulk relations
- RelationRepository.SaveBulk(toSave);
- }
-
- private IEnumerable GetAutomaticRelationTypesAliases(
- IPropertyCollection properties,
- PropertyEditorCollection propertyEditors)
- {
- var automaticRelationTypesAliases = new HashSet(Constants.Conventions.RelationTypes.AutomaticRelationTypes);
-
- foreach (IProperty property in properties)
+ // Lookup node IDs for all GUID based UDIs
+ IEnumerable keys = references.Select(x => x.Udi).OfType().Select(x => x.Guid);
+ var keysLookup = Database.FetchByGroups(keys, Constants.Sql.MaxParameterCount, guids =>
{
- if (propertyEditors.TryGet(property.PropertyType.PropertyEditorAlias, out IDataEditor? editor) is false )
- {
- continue;
- }
+ return Sql()
+ .Select(x => x.NodeId, x => x.UniqueId)
+ .From()
+ .WhereIn(x => x.UniqueId, guids);
+ }).ToDictionary(x => x.UniqueId, x => x.NodeId);
- if (editor.GetValueEditor() is IDataValueReference reference)
+ // Lookup all relation type IDs
+ var relationTypeLookup = RelationTypeRepository.GetMany(Array.Empty()).ToDictionary(x => x.Alias, x => x.Id);
+
+ // Get all valid relations
+ var relations = new List(references.Count);
+ foreach (UmbracoEntityReference reference in references)
+ {
+ if (!automaticRelationTypeAliases.Contains(reference.RelationTypeAlias))
{
- foreach (var alias in reference.GetAutomaticRelationTypesAliases())
- {
- automaticRelationTypesAliases.Add(alias);
- }
+ // Returning a reference that doesn't use an automatic relation type is an issue that should be fixed in code
+ Logger.LogError("The reference to {Udi} uses a relation type {RelationTypeAlias} that is not an automatic relation type.", reference.Udi, reference.RelationTypeAlias);
+ }
+ else if (!relationTypeLookup.TryGetValue(reference.RelationTypeAlias, out int relationTypeId))
+ {
+ // A non-existent relation type could be caused by an environment issue (e.g. it was manually removed)
+ Logger.LogWarning("The reference to {Udi} uses a relation type {RelationTypeAlias} that does not exist.", reference.Udi, reference.RelationTypeAlias);
+ }
+ else if (reference.Udi is not GuidUdi udi || !keysLookup.TryGetValue(udi.Guid, out var id))
+ {
+ // Relations only support references to items that are stored in the NodeDto table (because of foreign key constraints)
+ Logger.LogInformation("The reference to {Udi} can not be saved as relation, because doesn't have a node ID.", reference.Udi);
+ }
+ else
+ {
+ relations.Add(new ReadOnlyRelation(entity.Id, id, relationTypeId));
}
}
- return automaticRelationTypesAliases;
+ // Save bulk relations
+ RelationRepository.SaveBulk(relations);
}
///
diff --git a/src/Umbraco.Infrastructure/PropertyEditors/BlockEditorPropertyValueEditor.cs b/src/Umbraco.Infrastructure/PropertyEditors/BlockEditorPropertyValueEditor.cs
index c524c2c39b..9739553b99 100644
--- a/src/Umbraco.Infrastructure/PropertyEditors/BlockEditorPropertyValueEditor.cs
+++ b/src/Umbraco.Infrastructure/PropertyEditors/BlockEditorPropertyValueEditor.cs
@@ -17,12 +17,14 @@ internal abstract class BlockEditorPropertyValueEditor : DataValueEditor, IDataV
{
private BlockEditorValues? _blockEditorValues;
private readonly IDataTypeService _dataTypeService;
- private readonly ILogger _logger;
private readonly PropertyEditorCollection _propertyEditors;
+ private readonly DataValueReferenceFactoryCollection _dataValueReferenceFactories;
+ private readonly ILogger _logger;
protected BlockEditorPropertyValueEditor(
DataEditorAttribute attribute,
PropertyEditorCollection propertyEditors,
+ DataValueReferenceFactoryCollection dataValueReferenceFactories,
IDataTypeService dataTypeService,
ILocalizedTextService textService,
ILogger logger,
@@ -32,6 +34,7 @@ internal abstract class BlockEditorPropertyValueEditor : DataValueEditor, IDataV
: base(textService, shortStringHelper, jsonSerializer, ioHelper, attribute)
{
_propertyEditors = propertyEditors;
+ _dataValueReferenceFactories = dataValueReferenceFactories;
_dataTypeService = dataTypeService;
_logger = logger;
}
@@ -42,73 +45,58 @@ internal abstract class BlockEditorPropertyValueEditor : DataValueEditor, IDataV
set => _blockEditorValues = value;
}
+ ///
public IEnumerable GetReferences(object? value)
{
- var rawJson = value == null ? string.Empty : value is string str ? str : value.ToString();
-
- var result = new List();
- BlockEditorData? blockEditorData = BlockEditorValues.DeserializeAndClean(rawJson);
- if (blockEditorData == null)
+ foreach (BlockItemData.BlockPropertyValue propertyValue in GetAllPropertyValues(value))
{
- return Enumerable.Empty();
- }
-
- // loop through all content and settings data
- foreach (BlockItemData row in blockEditorData.BlockValue.ContentData.Concat(blockEditorData.BlockValue.SettingsData))
- {
- foreach (KeyValuePair prop in row.PropertyValues)
+ if (!_propertyEditors.TryGet(propertyValue.PropertyType.PropertyEditorAlias, out IDataEditor? dataEditor))
{
- IDataEditor? propEditor = _propertyEditors[prop.Value.PropertyType.PropertyEditorAlias];
+ continue;
+ }
- IDataValueEditor? valueEditor = propEditor?.GetValueEditor();
- if (!(valueEditor is IDataValueReference reference))
- {
- continue;
- }
-
- var val = prop.Value.Value?.ToString();
-
- IEnumerable refs = reference.GetReferences(val);
-
- result.AddRange(refs);
+ foreach (UmbracoEntityReference reference in _dataValueReferenceFactories.GetReferences(dataEditor, propertyValue.Value))
+ {
+ yield return reference;
}
}
-
- return result;
}
///
public IEnumerable GetTags(object? value, object? dataTypeConfiguration, int? languageId)
+ {
+ foreach (BlockItemData.BlockPropertyValue propertyValue in GetAllPropertyValues(value))
+ {
+ if (!_propertyEditors.TryGet(propertyValue.PropertyType.PropertyEditorAlias, out IDataEditor? dataEditor) ||
+ dataEditor.GetValueEditor() is not IDataValueTags dataValueTags)
+ {
+ continue;
+ }
+
+ object? configuration = _dataTypeService.GetDataType(propertyValue.PropertyType.DataTypeKey)?.Configuration;
+ foreach (ITag tag in dataValueTags.GetTags(propertyValue.Value, configuration, languageId))
+ {
+ yield return tag;
+ }
+ }
+ }
+
+ private IEnumerable GetAllPropertyValues(object? value)
{
var rawJson = value == null ? string.Empty : value is string str ? str : value.ToString();
BlockEditorData? blockEditorData = BlockEditorValues.DeserializeAndClean(rawJson);
- if (blockEditorData == null)
+ if (blockEditorData is null)
{
- return Enumerable.Empty();
+ yield break;
}
- var result = new List();
- // loop through all content and settings data
- foreach (BlockItemData row in blockEditorData.BlockValue.ContentData.Concat(blockEditorData.BlockValue.SettingsData))
+ // Return all property values from the content and settings data
+ IEnumerable data = blockEditorData.BlockValue.ContentData.Concat(blockEditorData.BlockValue.SettingsData);
+ foreach (BlockItemData.BlockPropertyValue propertyValue in data.SelectMany(x => x.PropertyValues.Select(x => x.Value)))
{
- foreach (KeyValuePair prop in row.PropertyValues)
- {
- IDataEditor? propEditor = _propertyEditors[prop.Value.PropertyType.PropertyEditorAlias];
-
- IDataValueEditor? valueEditor = propEditor?.GetValueEditor();
- if (valueEditor is not IDataValueTags tagsProvider)
- {
- continue;
- }
-
- object? configuration = _dataTypeService.GetDataType(prop.Value.PropertyType.DataTypeKey)?.Configuration;
-
- result.AddRange(tagsProvider.GetTags(prop.Value.Value, configuration, languageId));
- }
+ yield return propertyValue;
}
-
- return result;
}
#region Convert database // editor
diff --git a/src/Umbraco.Infrastructure/PropertyEditors/BlockGridPropertyEditorBase.cs b/src/Umbraco.Infrastructure/PropertyEditors/BlockGridPropertyEditorBase.cs
index 73b767bd4f..7e67c2d8a7 100644
--- a/src/Umbraco.Infrastructure/PropertyEditors/BlockGridPropertyEditorBase.cs
+++ b/src/Umbraco.Infrastructure/PropertyEditors/BlockGridPropertyEditorBase.cs
@@ -50,6 +50,7 @@ public abstract class BlockGridPropertyEditorBase : DataEditor
public BlockGridEditorPropertyValueEditor(
DataEditorAttribute attribute,
PropertyEditorCollection propertyEditors,
+ DataValueReferenceFactoryCollection dataValueReferenceFactories,
IDataTypeService dataTypeService,
ILocalizedTextService textService,
ILogger logger,
@@ -58,7 +59,7 @@ public abstract class BlockGridPropertyEditorBase : DataEditor
IIOHelper ioHelper,
IContentTypeService contentTypeService,
IPropertyValidationService propertyValidationService)
- : base(attribute, propertyEditors, dataTypeService, textService, logger, shortStringHelper, jsonSerializer, ioHelper)
+ : base(attribute, propertyEditors, dataValueReferenceFactories, dataTypeService, textService, logger, shortStringHelper, jsonSerializer, ioHelper)
{
BlockEditorValues = new BlockEditorValues(new BlockGridEditorDataConverter(jsonSerializer), contentTypeService, logger);
Validators.Add(new BlockEditorValidator(propertyValidationService, BlockEditorValues, contentTypeService));
diff --git a/src/Umbraco.Infrastructure/PropertyEditors/BlockListPropertyEditorBase.cs b/src/Umbraco.Infrastructure/PropertyEditors/BlockListPropertyEditorBase.cs
index 194383560e..d59af3817c 100644
--- a/src/Umbraco.Infrastructure/PropertyEditors/BlockListPropertyEditorBase.cs
+++ b/src/Umbraco.Infrastructure/PropertyEditors/BlockListPropertyEditorBase.cs
@@ -46,6 +46,7 @@ public abstract class BlockListPropertyEditorBase : DataEditor
public BlockListEditorPropertyValueEditor(
DataEditorAttribute attribute,
PropertyEditorCollection propertyEditors,
+ DataValueReferenceFactoryCollection dataValueReferenceFactories,
IDataTypeService dataTypeService,
IContentTypeService contentTypeService,
ILocalizedTextService textService,
@@ -54,7 +55,7 @@ public abstract class BlockListPropertyEditorBase : DataEditor
IJsonSerializer jsonSerializer,
IIOHelper ioHelper,
IPropertyValidationService propertyValidationService) :
- base(attribute, propertyEditors, dataTypeService, textService, logger, shortStringHelper, jsonSerializer, ioHelper)
+ base(attribute, propertyEditors, dataValueReferenceFactories,dataTypeService, textService, logger, shortStringHelper, jsonSerializer, ioHelper)
{
BlockEditorValues = new BlockEditorValues(new BlockListEditorDataConverter(), contentTypeService, logger);
Validators.Add(new BlockEditorValidator(propertyValidationService, BlockEditorValues, contentTypeService));
diff --git a/src/Umbraco.Infrastructure/PropertyEditors/NestedContentPropertyEditor.cs b/src/Umbraco.Infrastructure/PropertyEditors/NestedContentPropertyEditor.cs
index bf5781079a..87d80e3972 100644
--- a/src/Umbraco.Infrastructure/PropertyEditors/NestedContentPropertyEditor.cs
+++ b/src/Umbraco.Infrastructure/PropertyEditors/NestedContentPropertyEditor.cs
@@ -91,9 +91,10 @@ public class NestedContentPropertyEditor : DataEditor
internal class NestedContentPropertyValueEditor : DataValueEditor, IDataValueReference, IDataValueTags
{
private readonly IDataTypeService _dataTypeService;
+ private readonly PropertyEditorCollection _propertyEditors;
+ private readonly DataValueReferenceFactoryCollection _dataValueReferenceFactories;
private readonly ILogger _logger;
private readonly NestedContentValues _nestedContentValues;
- private readonly PropertyEditorCollection _propertyEditors;
public NestedContentPropertyValueEditor(
IDataTypeService dataTypeService,
@@ -102,16 +103,19 @@ public class NestedContentPropertyEditor : DataEditor
IShortStringHelper shortStringHelper,
DataEditorAttribute attribute,
PropertyEditorCollection propertyEditors,
+ DataValueReferenceFactoryCollection dataValueReferenceFactories,
ILogger logger,
IJsonSerializer jsonSerializer,
IIOHelper ioHelper,
IPropertyValidationService propertyValidationService)
: base(localizedTextService, shortStringHelper, jsonSerializer, ioHelper, attribute)
{
- _propertyEditors = propertyEditors;
_dataTypeService = dataTypeService;
+ _propertyEditors = propertyEditors;
+ _dataValueReferenceFactories = dataValueReferenceFactories;
_logger = logger;
_nestedContentValues = new NestedContentValues(contentTypeService);
+
Validators.Add(new NestedContentValidator(propertyValidationService, _nestedContentValues, contentTypeService));
}
@@ -139,66 +143,45 @@ public class NestedContentPropertyEditor : DataEditor
}
}
+ ///
public IEnumerable GetReferences(object? value)
{
- var rawJson = value == null ? string.Empty : value is string str ? str : value.ToString();
-
- var result = new List();
-
- foreach (NestedContentValues.NestedContentRowValue row in _nestedContentValues.GetPropertyValues(rawJson))
+ foreach (NestedContentValues.NestedContentPropertyValue propertyValue in GetAllPropertyValues(value))
{
- foreach (KeyValuePair prop in
- row.PropertyValues)
+ if (!_propertyEditors.TryGet(propertyValue.PropertyType.PropertyEditorAlias, out IDataEditor? dataEditor))
{
- IDataEditor? propEditor = _propertyEditors[prop.Value.PropertyType.PropertyEditorAlias];
+ continue;
+ }
- IDataValueEditor? valueEditor = propEditor?.GetValueEditor();
- if (!(valueEditor is IDataValueReference reference))
- {
- continue;
- }
-
- var val = prop.Value.Value?.ToString();
-
- IEnumerable refs = reference.GetReferences(val);
-
- result.AddRange(refs);
+ foreach (UmbracoEntityReference reference in _dataValueReferenceFactories.GetReferences(dataEditor, propertyValue.Value))
+ {
+ yield return reference;
}
}
-
- return result;
}
///
public IEnumerable GetTags(object? value, object? dataTypeConfiguration, int? languageId)
{
- IReadOnlyList rows =
- _nestedContentValues.GetPropertyValues(value);
-
- var result = new List();
-
- foreach (NestedContentValues.NestedContentRowValue row in rows.ToList())
+ foreach (NestedContentValues.NestedContentPropertyValue propertyValue in GetAllPropertyValues(value))
{
- foreach (KeyValuePair prop in row.PropertyValues
- .ToList())
+ if (!_propertyEditors.TryGet(propertyValue.PropertyType.PropertyEditorAlias, out IDataEditor? dataEditor) ||
+ dataEditor.GetValueEditor() is not IDataValueTags dataValueTags)
{
- IDataEditor? propEditor = _propertyEditors[prop.Value.PropertyType.PropertyEditorAlias];
+ continue;
+ }
- IDataValueEditor? valueEditor = propEditor?.GetValueEditor();
- if (valueEditor is not IDataValueTags tagsProvider)
- {
- continue;
- }
-
- object? configuration = _dataTypeService.GetDataType(prop.Value.PropertyType.DataTypeKey)?.Configuration;
-
- result.AddRange(tagsProvider.GetTags(prop.Value.Value, configuration, languageId));
+ object? configuration = _dataTypeService.GetDataType(propertyValue.PropertyType.DataTypeKey)?.Configuration;
+ foreach (ITag tag in dataValueTags.GetTags(propertyValue.Value, configuration, languageId))
+ {
+ yield return tag;
}
}
-
- return result;
}
+ private IEnumerable GetAllPropertyValues(object? value)
+ => _nestedContentValues.GetPropertyValues(value).SelectMany(x => x.PropertyValues.Values);
+
#region DB to String
public override string ConvertDbToString(IPropertyType propertyType, object? propertyValue)
@@ -424,7 +407,8 @@ public class NestedContentPropertyEditor : DataEditor
// set values to null
row.PropertyValues[elementTypeProp.Alias] = new NestedContentValues.NestedContentPropertyValue
{
- PropertyType = elementTypeProp, Value = null,
+ PropertyType = elementTypeProp,
+ Value = null,
};
row.RawPropertyValues[elementTypeProp.Alias] = null;
}
diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/DataValueEditorReuseTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/DataValueEditorReuseTests.cs
index d88a9689ab..75d4f0d509 100644
--- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/DataValueEditorReuseTests.cs
+++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/DataValueEditorReuseTests.cs
@@ -1,4 +1,3 @@
-using System.Linq;
using Microsoft.Extensions.Logging;
using Moq;
using NUnit.Framework;
@@ -15,6 +14,7 @@ public class DataValueEditorReuseTests
{
private Mock _dataValueEditorFactoryMock;
private PropertyEditorCollection _propertyEditorCollection;
+ private DataValueReferenceFactoryCollection _dataValueReferenceFactories;
[SetUp]
public void SetUp()
@@ -31,6 +31,7 @@ public class DataValueEditorReuseTests
Mock.Of()));
_propertyEditorCollection = new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty));
+ _dataValueReferenceFactories = new DataValueReferenceFactoryCollection(Enumerable.Empty);
_dataValueEditorFactoryMock
.Setup(m =>
@@ -38,6 +39,7 @@ public class DataValueEditorReuseTests
.Returns(() => new BlockListPropertyEditorBase.BlockListEditorPropertyValueEditor(
new DataEditorAttribute("a", "b", "c"),
_propertyEditorCollection,
+ _dataValueReferenceFactories,
Mock.Of(),
Mock.Of(),
Mock.Of(),
@@ -93,7 +95,7 @@ public class DataValueEditorReuseTests
{
var blockListPropertyEditor = new BlockListPropertyEditor(
_dataValueEditorFactoryMock.Object,
- new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty)),
+ _propertyEditorCollection,
Mock.Of(),
Mock.Of(),
Mock.Of());
@@ -114,7 +116,7 @@ public class DataValueEditorReuseTests
{
var blockListPropertyEditor = new BlockListPropertyEditor(
_dataValueEditorFactoryMock.Object,
- new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty)),
+ _propertyEditorCollection,
Mock.Of(),
Mock.Of(),
Mock.Of());
diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/DataValueReferenceFactoryCollectionTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/DataValueReferenceFactoryCollectionTests.cs
index 6108f59e2e..38fc5125dc 100644
--- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/DataValueReferenceFactoryCollectionTests.cs
+++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/DataValueReferenceFactoryCollectionTests.cs
@@ -1,8 +1,6 @@
// Copyright (c) Umbraco.
// See LICENSE for more details.
-using System.Collections.Generic;
-using System.Linq;
using Moq;
using NUnit.Framework;
using Umbraco.Cms.Core;
@@ -173,6 +171,33 @@ public class DataValueReferenceFactoryCollectionTests
Assert.AreEqual(trackedUdi4, result.ElementAt(1).Udi.ToString());
}
+ [Test]
+ public void GetAutomaticRelationTypesAliases_ContainsDefault()
+ {
+ var collection = new DataValueReferenceFactoryCollection(Enumerable.Empty);
+ var propertyEditors = new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty));
+
+ var result = collection.GetAllAutomaticRelationTypesAliases(propertyEditors).ToArray();
+
+ var expected = Constants.Conventions.RelationTypes.AutomaticRelationTypes;
+ CollectionAssert.AreEquivalent(expected, result, "Result does not contain the expected relation type aliases.");
+ }
+
+ [Test]
+ public void GetAutomaticRelationTypesAliases_ContainsCustom()
+ {
+ var collection = new DataValueReferenceFactoryCollection(() => new TestDataValueReferenceFactory().Yield());
+
+ var labelPropertyEditor = new LabelPropertyEditor(DataValueEditorFactory, IOHelper, EditorConfigurationParser);
+ var propertyEditors = new PropertyEditorCollection(new DataEditorCollection(() => labelPropertyEditor.Yield()));
+ var serializer = new ConfigurationEditorJsonSerializer();
+
+ var result = collection.GetAllAutomaticRelationTypesAliases(propertyEditors).ToArray();
+
+ var expected = Constants.Conventions.RelationTypes.AutomaticRelationTypes.Append("umbTest");
+ CollectionAssert.AreEquivalent(expected, result, "Result does not contain the expected relation type aliases.");
+ }
+
private class TestDataValueReferenceFactory : IDataValueReferenceFactory
{
public IDataValueReference GetDataValueReference() => new TestMediaDataValueReference();
@@ -196,6 +221,12 @@ public class DataValueReferenceFactoryCollectionTests
yield return new UmbracoEntityReference(udi);
}
}
+
+ public IEnumerable GetAutomaticRelationTypesAliases() => new[]
+ {
+ "umbTest",
+ "umbTest", // Duplicate on purpose to test distinct aliases
+ };
}
}
}