diff --git a/src/Umbraco.Cms.Api.Management/Controllers/DataType/References/ReferencedByDataTypeController.cs b/src/Umbraco.Cms.Api.Management/Controllers/DataType/References/ReferencedByDataTypeController.cs
new file mode 100644
index 0000000000..b36815d408
--- /dev/null
+++ b/src/Umbraco.Cms.Api.Management/Controllers/DataType/References/ReferencedByDataTypeController.cs
@@ -0,0 +1,46 @@
+using Asp.Versioning;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
+using Umbraco.Cms.Api.Common.ViewModels.Pagination;
+using Umbraco.Cms.Api.Management.Factories;
+using Umbraco.Cms.Api.Management.ViewModels.TrackedReferences;
+using Umbraco.Cms.Core.Models;
+using Umbraco.Cms.Core.Services;
+
+namespace Umbraco.Cms.Api.Management.Controllers.DataType.References;
+
+[ApiVersion("1.0")]
+public class ReferencedByDataTypeController : DataTypeControllerBase
+{
+ private readonly IDataTypeService _dataTypeService;
+ private readonly IRelationTypePresentationFactory _relationTypePresentationFactory;
+
+ public ReferencedByDataTypeController(IDataTypeService dataTypeService, IRelationTypePresentationFactory relationTypePresentationFactory)
+ {
+ _dataTypeService = dataTypeService;
+ _relationTypePresentationFactory = relationTypePresentationFactory;
+ }
+
+ ///
+ /// Gets a paged list of references for the current data type, so you can see where it is being used.
+ ///
+ [HttpGet("{id:guid}/referenced-by")]
+ [MapToApiVersion("1.0")]
+ [ProducesResponseType(typeof(PagedViewModel), StatusCodes.Status200OK)]
+ public async Task>> ReferencedBy(
+ CancellationToken cancellationToken,
+ Guid id,
+ int skip = 0,
+ int take = 20)
+ {
+ PagedModel relationItems = await _dataTypeService.GetPagedRelationsAsync(id, skip, take);
+
+ var pagedViewModel = new PagedViewModel
+ {
+ Total = relationItems.Total,
+ Items = await _relationTypePresentationFactory.CreateReferenceResponseModelsAsync(relationItems.Items),
+ };
+
+ return pagedViewModel;
+ }
+}
diff --git a/src/Umbraco.Cms.Api.Management/Controllers/DataType/ReferencesDataTypeController.cs b/src/Umbraco.Cms.Api.Management/Controllers/DataType/ReferencesDataTypeController.cs
index c25586e93c..0eee28e49b 100644
--- a/src/Umbraco.Cms.Api.Management/Controllers/DataType/ReferencesDataTypeController.cs
+++ b/src/Umbraco.Cms.Api.Management/Controllers/DataType/ReferencesDataTypeController.cs
@@ -1,4 +1,4 @@
-using Asp.Versioning;
+using Asp.Versioning;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.Factories;
@@ -10,6 +10,7 @@ using Umbraco.Cms.Core.Services.OperationStatus;
namespace Umbraco.Cms.Api.Management.Controllers.DataType;
[ApiVersion("1.0")]
+[Obsolete("Please use ReferencedByDataTypeController and the referenced-by endpoint. Scheduled for removal in Umbraco 17.")]
public class ReferencesDataTypeController : DataTypeControllerBase
{
private readonly IDataTypeService _dataTypeService;
diff --git a/src/Umbraco.Cms.Api.Management/Factories/RelationTypePresentationFactory.cs b/src/Umbraco.Cms.Api.Management/Factories/RelationTypePresentationFactory.cs
index a6fb1cbae8..40bcf9f04c 100644
--- a/src/Umbraco.Cms.Api.Management/Factories/RelationTypePresentationFactory.cs
+++ b/src/Umbraco.Cms.Api.Management/Factories/RelationTypePresentationFactory.cs
@@ -56,9 +56,12 @@ public class RelationTypePresentationFactory : IRelationTypePresentationFactory
IReferenceResponseModel[] result = relationItemModelsCollection.Select(relationItemModel =>
relationItemModel.NodeType switch
{
- Constants.UdiEntityType.Document => MapDocumentReference(relationItemModel, slimEntities),
- Constants.UdiEntityType.Media => _umbracoMapper.Map(relationItemModel),
- Constants.UdiEntityType.Member => _umbracoMapper.Map(relationItemModel),
+ Constants.ReferenceType.Document => MapDocumentReference(relationItemModel, slimEntities),
+ Constants.ReferenceType.Media => _umbracoMapper.Map(relationItemModel),
+ Constants.ReferenceType.Member => _umbracoMapper.Map(relationItemModel),
+ Constants.ReferenceType.DocumentTypePropertyType => _umbracoMapper.Map(relationItemModel),
+ Constants.ReferenceType.MediaTypePropertyType => _umbracoMapper.Map(relationItemModel),
+ Constants.ReferenceType.MemberTypePropertyType => _umbracoMapper.Map(relationItemModel),
_ => _umbracoMapper.Map(relationItemModel),
}).WhereNotNull().ToArray();
diff --git a/src/Umbraco.Cms.Api.Management/Mapping/TrackedReferences/TrackedReferenceViewModelsMapDefinition.cs b/src/Umbraco.Cms.Api.Management/Mapping/TrackedReferences/TrackedReferenceViewModelsMapDefinition.cs
index e9f2700f5a..c4efca3088 100644
--- a/src/Umbraco.Cms.Api.Management/Mapping/TrackedReferences/TrackedReferenceViewModelsMapDefinition.cs
+++ b/src/Umbraco.Cms.Api.Management/Mapping/TrackedReferences/TrackedReferenceViewModelsMapDefinition.cs
@@ -12,6 +12,9 @@ public class TrackedReferenceViewModelsMapDefinition : IMapDefinition
mapper.Define((source, context) => new DocumentReferenceResponseModel(), Map);
mapper.Define((source, context) => new MediaReferenceResponseModel(), Map);
mapper.Define((source, context) => new MemberReferenceResponseModel(), Map);
+ mapper.Define((source, context) => new DocumentTypePropertyTypeReferenceResponseModel(), Map);
+ mapper.Define((source, context) => new MediaTypePropertyTypeReferenceResponseModel(), Map);
+ mapper.Define((source, context) => new MemberTypePropertyTypeReferenceResponseModel(), Map);
mapper.Define((source, context) => new DefaultReferenceResponseModel(), Map);
mapper.Define((source, context) => new ReferenceByIdModel(), Map);
mapper.Define((source, context) => new ReferenceByIdModel(), Map);
@@ -25,6 +28,7 @@ public class TrackedReferenceViewModelsMapDefinition : IMapDefinition
target.Published = source.NodePublished;
target.DocumentType = new TrackedReferenceDocumentType
{
+ Id = source.ContentTypeKey,
Alias = source.ContentTypeAlias,
Icon = source.ContentTypeIcon,
Name = source.ContentTypeName,
@@ -38,6 +42,7 @@ public class TrackedReferenceViewModelsMapDefinition : IMapDefinition
target.Name = source.NodeName;
target.MediaType = new TrackedReferenceMediaType
{
+ Id = source.ContentTypeKey,
Alias = source.ContentTypeAlias,
Icon = source.ContentTypeIcon,
Name = source.ContentTypeName,
@@ -51,6 +56,52 @@ public class TrackedReferenceViewModelsMapDefinition : IMapDefinition
target.Name = source.NodeName;
target.MemberType = new TrackedReferenceMemberType
{
+ Id = source.ContentTypeKey,
+ Alias = source.ContentTypeAlias,
+ Icon = source.ContentTypeIcon,
+ Name = source.ContentTypeName,
+ };
+ }
+
+ // Umbraco.Code.MapAll
+ private void Map(RelationItemModel source, DocumentTypePropertyTypeReferenceResponseModel target, MapperContext context)
+ {
+ target.Id = source.NodeKey;
+ target.Name = source.NodeName;
+ target.Alias = source.NodeAlias;
+ target.DocumentType = new TrackedReferenceDocumentType
+ {
+ Id = source.ContentTypeKey,
+ Alias = source.ContentTypeAlias,
+ Icon = source.ContentTypeIcon,
+ Name = source.ContentTypeName,
+ };
+ }
+
+ // Umbraco.Code.MapAll
+ private void Map(RelationItemModel source, MediaTypePropertyTypeReferenceResponseModel target, MapperContext context)
+ {
+ target.Id = source.NodeKey;
+ target.Name = source.NodeName;
+ target.Alias = source.NodeAlias;
+ target.MediaType = new TrackedReferenceMediaType
+ {
+ Id = source.ContentTypeKey,
+ Alias = source.ContentTypeAlias,
+ Icon = source.ContentTypeIcon,
+ Name = source.ContentTypeName,
+ };
+ }
+
+ // Umbraco.Code.MapAll
+ private void Map(RelationItemModel source, MemberTypePropertyTypeReferenceResponseModel target, MapperContext context)
+ {
+ target.Id = source.NodeKey;
+ target.Name = source.NodeName;
+ target.Alias = source.NodeAlias;
+ target.MemberType = new TrackedReferenceMemberType
+ {
+ Id = source.ContentTypeKey,
Alias = source.ContentTypeAlias,
Icon = source.ContentTypeIcon,
Name = source.ContentTypeName,
diff --git a/src/Umbraco.Cms.Api.Management/OpenApi.json b/src/Umbraco.Cms.Api.Management/OpenApi.json
index e87920c9ba..304e344e62 100644
--- a/src/Umbraco.Cms.Api.Management/OpenApi.json
+++ b/src/Umbraco.Cms.Api.Management/OpenApi.json
@@ -816,6 +816,70 @@
]
}
},
+ "/umbraco/management/api/v1/data-type/{id}/referenced-by": {
+ "get": {
+ "tags": [
+ "Data Type"
+ ],
+ "operationId": "GetDataTypeByIdReferencedBy",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "format": "uuid"
+ }
+ },
+ {
+ "name": "skip",
+ "in": "query",
+ "schema": {
+ "type": "integer",
+ "format": "int32",
+ "default": 0
+ }
+ },
+ {
+ "name": "take",
+ "in": "query",
+ "schema": {
+ "type": "integer",
+ "format": "int32",
+ "default": 20
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "oneOf": [
+ {
+ "$ref": "#/components/schemas/PagedIReferenceResponseModel"
+ }
+ ]
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "The resource is protected and requires an authentication token"
+ },
+ "403": {
+ "description": "The authenticated user does not have access to this resource"
+ }
+ },
+ "security": [
+ {
+ "Backoffice User": [ ]
+ }
+ ]
+ }
+ },
"/umbraco/management/api/v1/data-type/{id}/references": {
"get": {
"tags": [
@@ -872,6 +936,7 @@
"description": "The authenticated user does not have access to this resource"
}
},
+ "deprecated": true,
"security": [
{
"Backoffice User": [ ]
@@ -37931,6 +37996,45 @@
},
"additionalProperties": false
},
+ "DocumentTypePropertyReferenceResponseModel": {
+ "required": [
+ "$type",
+ "documentType",
+ "id"
+ ],
+ "type": "object",
+ "properties": {
+ "$type": {
+ "type": "string"
+ },
+ "id": {
+ "type": "string",
+ "format": "uuid"
+ },
+ "name": {
+ "type": "string",
+ "nullable": true
+ },
+ "alias": {
+ "type": "string",
+ "nullable": true
+ },
+ "documentType": {
+ "oneOf": [
+ {
+ "$ref": "#/components/schemas/TrackedReferenceDocumentTypeModel"
+ }
+ ]
+ }
+ },
+ "additionalProperties": false,
+ "discriminator": {
+ "propertyName": "$type",
+ "mapping": {
+ "DocumentTypePropertyReferenceResponseModel": "#/components/schemas/DocumentTypePropertyReferenceResponseModel"
+ }
+ }
+ },
"DocumentTypePropertyTypeContainerResponseModel": {
"required": [
"id",
@@ -39995,6 +40099,45 @@
},
"additionalProperties": false
},
+ "MediaTypePropertyReferenceResponseModel": {
+ "required": [
+ "$type",
+ "id",
+ "mediaType"
+ ],
+ "type": "object",
+ "properties": {
+ "$type": {
+ "type": "string"
+ },
+ "id": {
+ "type": "string",
+ "format": "uuid"
+ },
+ "name": {
+ "type": "string",
+ "nullable": true
+ },
+ "alias": {
+ "type": "string",
+ "nullable": true
+ },
+ "mediaType": {
+ "oneOf": [
+ {
+ "$ref": "#/components/schemas/TrackedReferenceMediaTypeModel"
+ }
+ ]
+ }
+ },
+ "additionalProperties": false,
+ "discriminator": {
+ "propertyName": "$type",
+ "mapping": {
+ "MediaTypePropertyReferenceResponseModel": "#/components/schemas/MediaTypePropertyReferenceResponseModel"
+ }
+ }
+ },
"MediaTypePropertyTypeContainerResponseModel": {
"required": [
"id",
@@ -40773,6 +40916,45 @@
},
"additionalProperties": false
},
+ "MemberTypePropertyReferenceResponseModel": {
+ "required": [
+ "$type",
+ "id",
+ "memberType"
+ ],
+ "type": "object",
+ "properties": {
+ "$type": {
+ "type": "string"
+ },
+ "id": {
+ "type": "string",
+ "format": "uuid"
+ },
+ "name": {
+ "type": "string",
+ "nullable": true
+ },
+ "alias": {
+ "type": "string",
+ "nullable": true
+ },
+ "memberType": {
+ "oneOf": [
+ {
+ "$ref": "#/components/schemas/TrackedReferenceMemberTypeModel"
+ }
+ ]
+ }
+ },
+ "additionalProperties": false,
+ "discriminator": {
+ "propertyName": "$type",
+ "mapping": {
+ "MemberTypePropertyReferenceResponseModel": "#/components/schemas/MemberTypePropertyReferenceResponseModel"
+ }
+ }
+ },
"MemberTypePropertyTypeContainerResponseModel": {
"required": [
"id",
@@ -41980,11 +42162,20 @@
{
"$ref": "#/components/schemas/DocumentReferenceResponseModel"
},
+ {
+ "$ref": "#/components/schemas/DocumentTypePropertyReferenceResponseModel"
+ },
{
"$ref": "#/components/schemas/MediaReferenceResponseModel"
},
+ {
+ "$ref": "#/components/schemas/MediaTypePropertyReferenceResponseModel"
+ },
{
"$ref": "#/components/schemas/MemberReferenceResponseModel"
+ },
+ {
+ "$ref": "#/components/schemas/MemberTypePropertyReferenceResponseModel"
}
]
}
@@ -44639,8 +44830,15 @@
"additionalProperties": false
},
"TrackedReferenceDocumentTypeModel": {
+ "required": [
+ "id"
+ ],
"type": "object",
"properties": {
+ "id": {
+ "type": "string",
+ "format": "uuid"
+ },
"icon": {
"type": "string",
"nullable": true
@@ -44657,8 +44855,15 @@
"additionalProperties": false
},
"TrackedReferenceMediaTypeModel": {
+ "required": [
+ "id"
+ ],
"type": "object",
"properties": {
+ "id": {
+ "type": "string",
+ "format": "uuid"
+ },
"icon": {
"type": "string",
"nullable": true
@@ -44675,8 +44880,15 @@
"additionalProperties": false
},
"TrackedReferenceMemberTypeModel": {
+ "required": [
+ "id"
+ ],
"type": "object",
"properties": {
+ "id": {
+ "type": "string",
+ "format": "uuid"
+ },
"icon": {
"type": "string",
"nullable": true
diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/TrackedReferences/ContentTypePropertyTypeReferenceResponseModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/TrackedReferences/ContentTypePropertyTypeReferenceResponseModel.cs
new file mode 100644
index 0000000000..83dc6a1a7a
--- /dev/null
+++ b/src/Umbraco.Cms.Api.Management/ViewModels/TrackedReferences/ContentTypePropertyTypeReferenceResponseModel.cs
@@ -0,0 +1,6 @@
+namespace Umbraco.Cms.Api.Management.ViewModels.TrackedReferences;
+
+public abstract class ContentTypePropertyTypeReferenceResponseModel : ReferenceResponseModel
+{
+ public string? Alias { get; set; }
+}
diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/TrackedReferences/DocumentTypePropertyTypeReferenceResponseModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/TrackedReferences/DocumentTypePropertyTypeReferenceResponseModel.cs
new file mode 100644
index 0000000000..b2ef3e4a0a
--- /dev/null
+++ b/src/Umbraco.Cms.Api.Management/ViewModels/TrackedReferences/DocumentTypePropertyTypeReferenceResponseModel.cs
@@ -0,0 +1,6 @@
+namespace Umbraco.Cms.Api.Management.ViewModels.TrackedReferences;
+
+public class DocumentTypePropertyTypeReferenceResponseModel : ContentTypePropertyTypeReferenceResponseModel
+{
+ public TrackedReferenceDocumentType DocumentType { get; set; } = new();
+}
diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/TrackedReferences/MediaTypePropertyTypeReferenceResponseModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/TrackedReferences/MediaTypePropertyTypeReferenceResponseModel.cs
new file mode 100644
index 0000000000..1baf647654
--- /dev/null
+++ b/src/Umbraco.Cms.Api.Management/ViewModels/TrackedReferences/MediaTypePropertyTypeReferenceResponseModel.cs
@@ -0,0 +1,6 @@
+namespace Umbraco.Cms.Api.Management.ViewModels.TrackedReferences;
+
+public class MediaTypePropertyTypeReferenceResponseModel : ContentTypePropertyTypeReferenceResponseModel
+{
+ public TrackedReferenceMediaType MediaType { get; set; } = new();
+}
diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/TrackedReferences/MemberTypePropertyTypeReferenceResponseModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/TrackedReferences/MemberTypePropertyTypeReferenceResponseModel.cs
new file mode 100644
index 0000000000..199a4b0ba1
--- /dev/null
+++ b/src/Umbraco.Cms.Api.Management/ViewModels/TrackedReferences/MemberTypePropertyTypeReferenceResponseModel.cs
@@ -0,0 +1,6 @@
+namespace Umbraco.Cms.Api.Management.ViewModels.TrackedReferences;
+
+public class MemberTypePropertyTypeReferenceResponseModel : ContentTypePropertyTypeReferenceResponseModel
+{
+ public TrackedReferenceMemberType MemberType { get; set; } = new();
+}
diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/TrackedReferences/TrackedReferenceContentType.cs b/src/Umbraco.Cms.Api.Management/ViewModels/TrackedReferences/TrackedReferenceContentType.cs
index 31456abada..15ac365e41 100644
--- a/src/Umbraco.Cms.Api.Management/ViewModels/TrackedReferences/TrackedReferenceContentType.cs
+++ b/src/Umbraco.Cms.Api.Management/ViewModels/TrackedReferences/TrackedReferenceContentType.cs
@@ -1,7 +1,9 @@
-namespace Umbraco.Cms.Api.Management.ViewModels.TrackedReferences;
+namespace Umbraco.Cms.Api.Management.ViewModels.TrackedReferences;
public abstract class TrackedReferenceContentType
{
+ public Guid Id { get; set; }
+
public string? Icon { get; set; }
public string? Alias { get; set; }
diff --git a/src/Umbraco.Core/Constants-ReferenceTypes.cs b/src/Umbraco.Core/Constants-ReferenceTypes.cs
new file mode 100644
index 0000000000..b006a0d590
--- /dev/null
+++ b/src/Umbraco.Core/Constants-ReferenceTypes.cs
@@ -0,0 +1,25 @@
+namespace Umbraco.Cms.Core;
+
+public static partial class Constants
+{
+ ///
+ /// Defines reference types.
+ ///
+ ///
+ /// Reference types are used to identify the type of entity that is being referenced when exposing references
+ /// between Umbraco entities.
+ /// These are used in the management API and backoffice to indicate and warn editors when working with an entity,
+ /// as to what other entities depend on it.
+ /// These consist of references managed by Umbraco relations (e.g. document, media and member).
+ /// But also references that come from schema (e.g. data type usage on content types).
+ ///
+ public static class ReferenceType
+ {
+ public const string Document = UdiEntityType.Document;
+ public const string Media = UdiEntityType.Media;
+ public const string Member = UdiEntityType.Member;
+ public const string DocumentTypePropertyType = "document-type-property-type";
+ public const string MediaTypePropertyType = "media-type-property-type";
+ public const string MemberTypePropertyType = "member-type-property-type";
+ }
+}
diff --git a/src/Umbraco.Core/Models/RelationItem.cs b/src/Umbraco.Core/Models/RelationItem.cs
index a865e7cc2f..41fde0e867 100644
--- a/src/Umbraco.Core/Models/RelationItem.cs
+++ b/src/Umbraco.Core/Models/RelationItem.cs
@@ -23,6 +23,9 @@ public class RelationItem
[DataMember(Name = "published")]
public bool? NodePublished { get; set; }
+ [DataMember(Name = "contentTypeKey")]
+ public Guid ContentTypeKey { get; set; }
+
[DataMember(Name = "icon")]
public string? ContentTypeIcon { get; set; }
diff --git a/src/Umbraco.Core/Models/RelationItemModel.cs b/src/Umbraco.Core/Models/RelationItemModel.cs
index a05c8f6591..1ca3bb9e11 100644
--- a/src/Umbraco.Core/Models/RelationItemModel.cs
+++ b/src/Umbraco.Core/Models/RelationItemModel.cs
@@ -1,15 +1,19 @@
-namespace Umbraco.Cms.Core.Models;
+namespace Umbraco.Cms.Core.Models;
public class RelationItemModel
{
public Guid NodeKey { get; set; }
+ public string? NodeAlias { get; set; }
+
public string? NodeName { get; set; }
public string? NodeType { get; set; }
public bool? NodePublished { get; set; }
+ public Guid ContentTypeKey { get; set; }
+
public string? ContentTypeIcon { get; set; }
public string? ContentTypeAlias { get; set; }
diff --git a/src/Umbraco.Core/Persistence/Repositories/IDataTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IDataTypeRepository.cs
index ad113533d8..f0babf61f3 100644
--- a/src/Umbraco.Core/Persistence/Repositories/IDataTypeRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/IDataTypeRepository.cs
@@ -24,6 +24,5 @@ public interface IDataTypeRepository : IReadWriteQueryRepository
///
///
///
-
IReadOnlyDictionary> FindListViewUsages(int id) => throw new NotImplementedException();
}
diff --git a/src/Umbraco.Core/Services/DataTypeService.cs b/src/Umbraco.Core/Services/DataTypeService.cs
index f22948968a..7bc0b4d95f 100644
--- a/src/Umbraco.Core/Services/DataTypeService.cs
+++ b/src/Umbraco.Core/Services/DataTypeService.cs
@@ -25,12 +25,15 @@ namespace Umbraco.Cms.Core.Services.Implement
private readonly IDataTypeRepository _dataTypeRepository;
private readonly IDataTypeContainerRepository _dataTypeContainerRepository;
private readonly IContentTypeRepository _contentTypeRepository;
+ private readonly IMediaTypeRepository _mediaTypeRepository;
+ private readonly IMemberTypeRepository _memberTypeRepository;
private readonly IAuditRepository _auditRepository;
private readonly IIOHelper _ioHelper;
private readonly IDataTypeContainerService _dataTypeContainerService;
private readonly IUserIdKeyResolver _userIdKeyResolver;
private readonly Lazy _idKeyMap;
+ [Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 17.")]
public DataTypeService(
ICoreScopeProvider provider,
ILoggerFactory loggerFactory,
@@ -41,12 +44,41 @@ namespace Umbraco.Cms.Core.Services.Implement
IContentTypeRepository contentTypeRepository,
IIOHelper ioHelper,
Lazy idKeyMap)
+ : this(
+ provider,
+ loggerFactory,
+ eventMessagesFactory,
+ dataTypeRepository,
+ dataValueEditorFactory,
+ auditRepository,
+ contentTypeRepository,
+ StaticServiceProvider.Instance.GetRequiredService(),
+ StaticServiceProvider.Instance.GetRequiredService(),
+ ioHelper,
+ idKeyMap)
+ {
+ }
+
+ public DataTypeService(
+ ICoreScopeProvider provider,
+ ILoggerFactory loggerFactory,
+ IEventMessagesFactory eventMessagesFactory,
+ IDataTypeRepository dataTypeRepository,
+ IDataValueEditorFactory dataValueEditorFactory,
+ IAuditRepository auditRepository,
+ IContentTypeRepository contentTypeRepository,
+ IMediaTypeRepository mediaTypeRepository,
+ IMemberTypeRepository memberTypeRepository,
+ IIOHelper ioHelper,
+ Lazy idKeyMap)
: base(provider, loggerFactory, eventMessagesFactory)
{
_dataValueEditorFactory = dataValueEditorFactory;
_dataTypeRepository = dataTypeRepository;
_auditRepository = auditRepository;
_contentTypeRepository = contentTypeRepository;
+ _mediaTypeRepository = mediaTypeRepository;
+ _memberTypeRepository = memberTypeRepository;
_ioHelper = ioHelper;
_idKeyMap = idKeyMap;
@@ -703,12 +735,119 @@ namespace Umbraco.Cms.Core.Services.Implement
return await Task.FromResult(Attempt.SucceedWithStatus(DataTypeOperationStatus.Success, usages));
}
+ ///
public IReadOnlyDictionary> GetListViewReferences(int id)
{
using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true);
return _dataTypeRepository.FindListViewUsages(id);
}
+ ///
+ public Task> GetPagedRelationsAsync(Guid key, int skip, int take)
+ {
+ using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true);
+
+ IDataType? dataType = GetDataTypeFromRepository(key);
+ if (dataType == null)
+ {
+ // Is an unexpected response, but returning an empty collection aligns with how we handle retrieval of concrete Umbraco
+ // relations based on documents, media and members.
+ return Task.FromResult(new PagedModel());
+ }
+
+ // We don't really need true paging here, as the number of data type relations will be small compared to what there could
+ // potentially by for concrete Umbraco relations based on documents, media and members.
+ // So we'll retrieve all usages for the data type and construct a paged response.
+ // This allows us to re-use the existing repository methods used for FindUsages and FindListViewUsages.
+ IReadOnlyDictionary> usages = _dataTypeRepository.FindUsages(dataType.Id);
+ IReadOnlyDictionary> listViewUsages = _dataTypeRepository.FindListViewUsages(dataType.Id);
+
+ // Combine the property and list view usages into a single collection of property aliases and content type UDIs.
+ IList<(string PropertyAlias, Udi Udi)> combinedUsages = usages
+ .SelectMany(kvp => kvp.Value.Select(value => (value, kvp.Key)))
+ .Concat(listViewUsages.SelectMany(kvp => kvp.Value.Select(value => (value, kvp.Key))))
+ .ToList();
+
+ var totalItems = combinedUsages.Count;
+
+ // Create the page of items.
+ IList<(string PropertyAlias, Udi Udi)> pagedUsages = combinedUsages
+ .OrderBy(x => x.Udi.EntityType) // Document types first, then media types, then member types.
+ .ThenBy(x => x.PropertyAlias)
+ .Skip(skip)
+ .Take(take)
+ .ToList();
+
+ // Get the content types for the UDIs referenced in the page of items to construct the response from.
+ // They could be document, media or member types.
+ IList contentTypes = GetReferencedContentTypes(pagedUsages);
+
+ IEnumerable relations = pagedUsages
+ .Select(x =>
+ {
+ // Get the matching content type so we can populate the content type and property details.
+ IContentTypeComposition contentType = contentTypes.Single(y => y.Key == ((GuidUdi)x.Udi).Guid);
+
+ string nodeType = x.Udi.EntityType switch
+ {
+ Constants.UdiEntityType.DocumentType => Constants.ReferenceType.DocumentTypePropertyType,
+ Constants.UdiEntityType.MediaType => Constants.ReferenceType.MediaTypePropertyType,
+ Constants.UdiEntityType.MemberType => Constants.ReferenceType.MemberTypePropertyType,
+ _ => throw new ArgumentOutOfRangeException(nameof(x.Udi.EntityType)),
+ };
+
+ // Look-up the property details from the property alias. This will be null for a list view reference.
+ IPropertyType? propertyType = contentType.PropertyTypes.SingleOrDefault(y => y.Alias == x.PropertyAlias);
+ return new RelationItemModel
+ {
+ ContentTypeKey = contentType.Key,
+ ContentTypeAlias = contentType.Alias,
+ ContentTypeIcon = contentType.Icon,
+ ContentTypeName = contentType.Name,
+ NodeType = nodeType,
+ NodeName = propertyType?.Name ?? x.PropertyAlias,
+ NodeAlias = x.PropertyAlias,
+ NodeKey = propertyType?.Key ?? Guid.Empty,
+ };
+ });
+
+ var pagedModel = new PagedModel(totalItems, relations);
+ return Task.FromResult(pagedModel);
+ }
+
+ private IList GetReferencedContentTypes(IList<(string PropertyAlias, Udi Udi)> pagedUsages)
+ {
+ IEnumerable documentTypes = GetContentTypes(
+ pagedUsages,
+ Constants.UdiEntityType.DocumentType,
+ _contentTypeRepository);
+ IEnumerable mediaTypes = GetContentTypes(
+ pagedUsages,
+ Constants.UdiEntityType.MediaType,
+ _mediaTypeRepository);
+ IEnumerable memberTypes = GetContentTypes(
+ pagedUsages,
+ Constants.UdiEntityType.MemberType,
+ _memberTypeRepository);
+ return documentTypes.Concat(mediaTypes).Concat(memberTypes).ToList();
+ }
+
+ private static IEnumerable GetContentTypes(
+ IEnumerable<(string PropertyAlias, Udi Udi)> dataTypeUsages,
+ string entityType,
+ IContentTypeRepositoryBase repository)
+ where T : IContentTypeComposition
+ {
+ Guid[] contentTypeKeys = dataTypeUsages
+ .Where(x => x.Udi is GuidUdi && x.Udi.EntityType == entityType)
+ .Select(x => ((GuidUdi)x.Udi).Guid)
+ .Distinct()
+ .ToArray();
+ return contentTypeKeys.Length > 0
+ ? repository.GetMany(contentTypeKeys)
+ : [];
+ }
+
///
public IEnumerable ValidateConfigurationData(IDataType dataType)
{
diff --git a/src/Umbraco.Core/Services/IDataTypeService.cs b/src/Umbraco.Core/Services/IDataTypeService.cs
index 3a4576552c..0f2f58ceb8 100644
--- a/src/Umbraco.Core/Services/IDataTypeService.cs
+++ b/src/Umbraco.Core/Services/IDataTypeService.cs
@@ -18,6 +18,7 @@ public interface IDataTypeService : IService
[Obsolete("Please use GetReferencesAsync. Will be deleted in V15.")]
IReadOnlyDictionary> GetReferences(int id);
+ [Obsolete("Please use GetPagedRelationsAsync. Scheduled for removal in Umbraco 17.")]
IReadOnlyDictionary> GetListViewReferences(int id) => throw new NotImplementedException();
///
@@ -25,8 +26,25 @@ public interface IDataTypeService : IService
///
/// The guid Id of the
///
+ [Obsolete("Please use GetPagedRelationsAsync. Scheduled for removal in Umbraco 17.")]
Task>, DataTypeOperationStatus>> GetReferencesAsync(Guid id);
+ ///
+ /// Gets a paged result of items which are in relation with the current data type.
+ ///
+ /// The identifier of the data type to retrieve relations for.
+ /// The amount of items to skip
+ /// The amount of items to take.
+ /// A paged result of objects.
+ ///
+ /// Note that the model and method signature here aligns with with how we handle retrieval of concrete Umbraco
+ /// relations based on documents, media and members in .
+ /// The intention is that we align data type relations with these so they can be handled polymorphically at the management API
+ /// and backoffice UI level.
+ ///
+ Task> GetPagedRelationsAsync(Guid key, int skip, int take)
+ => Task.FromResult(new PagedModel());
+
[Obsolete("Please use IDataTypeContainerService for all data type container operations. Will be removed in V15.")]
Attempt?> CreateContainer(int parentId, Guid key, string name,
int userId = Constants.Security.SuperUserId);
diff --git a/src/Umbraco.Infrastructure/Mapping/RelationModelMapDefinition.cs b/src/Umbraco.Infrastructure/Mapping/RelationModelMapDefinition.cs
index 8ace25c07f..1f0c986e0d 100644
--- a/src/Umbraco.Infrastructure/Mapping/RelationModelMapDefinition.cs
+++ b/src/Umbraco.Infrastructure/Mapping/RelationModelMapDefinition.cs
@@ -1,4 +1,4 @@
-using Umbraco.Cms.Core.Mapping;
+using Umbraco.Cms.Core.Mapping;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement;
@@ -19,6 +19,7 @@ public class RelationModelMapDefinition : IMapDefinition
target.RelationTypeName = source.RelationTypeName;
target.RelationTypeIsBidirectional = source.RelationTypeIsBidirectional;
target.RelationTypeIsDependency = source.RelationTypeIsDependency;
+ target.ContentTypeKey = source.ChildContentTypeKey;
target.ContentTypeAlias = source.ChildContentTypeAlias;
target.ContentTypeIcon = source.ChildContentTypeIcon;
target.ContentTypeName = source.ChildContentTypeName;
diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RelationRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RelationRepository.cs
index a38bf4547f..2786bbfd1e 100644
--- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RelationRepository.cs
+++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RelationRepository.cs
@@ -475,6 +475,9 @@ internal class RelationItemDto
[Column(Name = "nodeObjectType")]
public Guid ChildNodeObjectType { get; set; }
+ [Column(Name = "contentTypeKey")]
+ public Guid ChildContentTypeKey { get; set; }
+
[Column(Name = "contentTypeIcon")]
public string? ChildContentTypeIcon { get; set; }
diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TrackedReferencesRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TrackedReferencesRepository.cs
index ced64f4996..d9bda52916 100644
--- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TrackedReferencesRepository.cs
+++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TrackedReferencesRepository.cs
@@ -35,6 +35,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
"[n].[text] as nodeName",
"[n].[nodeObjectType] as nodeObjectType",
"[d].[published] as nodePublished",
+ "[ctn].[uniqueId] as contentTypeKey",
"[ct].[icon] as contentTypeIcon",
"[ct].[alias] as contentTypeAlias",
"[ctn].[text] as contentTypeName",
@@ -188,6 +189,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
"[n].[text] as nodeName",
"[n].[nodeObjectType] as nodeObjectType",
"[d].[published] as nodePublished",
+ "[ctn].[uniqueId] as contentTypeKey",
"[ct].[icon] as contentTypeIcon",
"[ct].[alias] as contentTypeAlias",
"[ctn].[text] as contentTypeName",
@@ -250,6 +252,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
"[n].[text] as nodeName",
"[n].[nodeObjectType] as nodeObjectType",
"[d].[published] as nodePublished",
+ "[ctn].[uniqueId] as contentTypeKey",
"[ct].[icon] as contentTypeIcon",
"[ct].[alias] as contentTypeAlias",
"[ctn].[text] as contentTypeName",
@@ -336,6 +339,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
"[n].[text] as nodeName",
"[n].[nodeObjectType] as nodeObjectType",
"[d].[published] as nodePublished",
+ "[ctn].[uniqueId] as contentTypeKey",
"[ct].[icon] as contentTypeIcon",
"[ct].[alias] as contentTypeAlias",
"[ctn].[text] as contentTypeName",
@@ -411,6 +415,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
"[n].[uniqueId] as nodeKey",
"[n].[text] as nodeName",
"[n].[nodeObjectType] as nodeObjectType",
+ "[ctn].[uniqueId] as contentTypeKey",
"[ct].[icon] as contentTypeIcon",
"[ct].[alias] as contentTypeAlias",
"[ctn].[text] as contentTypeName",
@@ -477,6 +482,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
"[n].[uniqueId] as nodeKey",
"[n].[text] as nodeName",
"[n].[nodeObjectType] as nodeObjectType",
+ "[ctn].[uniqueId] as contentTypeKey",
"[ct].[icon] as contentTypeIcon",
"[ct].[alias] as contentTypeAlias",
"[ctn].[text] as contentTypeName",
@@ -595,6 +601,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
"[n].[text] as nodeName",
"[n].[nodeObjectType] as nodeObjectType",
"[d].[published] as nodePublished",
+ "[ctn].[uniqueId] as contentTypeKey",
"[ct].[icon] as contentTypeIcon",
"[ct].[alias] as contentTypeAlias",
"[ctn].[text] as contentTypeName",
@@ -671,6 +678,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
"[n].[uniqueId] as nodeKey",
"[n].[text] as nodeName",
"[n].[nodeObjectType] as nodeObjectType",
+ "[ctn].[uniqueId] as contentTypeKey",
"[ct].[icon] as contentTypeIcon",
"[ct].[alias] as contentTypeAlias",
"[ctn].[text] as contentTypeName",
@@ -749,6 +757,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
"[n].[uniqueId] as nodeKey",
"[n].[text] as nodeName",
"[n].[nodeObjectType] as nodeObjectType",
+ "[ctn].[uniqueId] as contentTypeKey",
"[ct].[icon] as contentTypeIcon",
"[ct].[alias] as contentTypeAlias",
"[ctn].[text] as contentTypeName",
@@ -841,6 +850,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
RelationTypeName = dto.RelationTypeName,
RelationTypeIsBidirectional = dto.RelationTypeIsBidirectional,
RelationTypeIsDependency = dto.RelationTypeIsDependency,
+ ContentTypeKey = dto.ChildContentTypeKey,
ContentTypeAlias = dto.ChildContentTypeAlias,
ContentTypeIcon = dto.ChildContentTypeIcon,
ContentTypeName = dto.ChildContentTypeName,
diff --git a/tests/Umbraco.Tests.Common/Builders/MediaTypeBuilder.cs b/tests/Umbraco.Tests.Common/Builders/MediaTypeBuilder.cs
index dcde82b47d..6439899955 100644
--- a/tests/Umbraco.Tests.Common/Builders/MediaTypeBuilder.cs
+++ b/tests/Umbraco.Tests.Common/Builders/MediaTypeBuilder.cs
@@ -139,6 +139,7 @@ public class MediaTypeBuilder
var mediaType = builder
.WithAlias(alias)
.WithName(name)
+ .WithIcon("icon-picture")
.WithParentContentType(parent)
.AddPropertyGroup()
.WithAlias(propertyGroupAlias)
diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/DataTypeServiceTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/DataTypeServiceTests.cs
index 1c2b46137b..92a1567a4f 100644
--- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/DataTypeServiceTests.cs
+++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/DataTypeServiceTests.cs
@@ -30,6 +30,8 @@ public class DataTypeServiceTests : UmbracoIntegrationTest
private IContentTypeService ContentTypeService => GetRequiredService();
+ private IMediaTypeService MediaTypeService => GetRequiredService();
+
private IFileService FileService => GetRequiredService();
private IConfigurationEditorJsonSerializer ConfigurationEditorJsonSerializer =>
@@ -446,4 +448,43 @@ public class DataTypeServiceTests : UmbracoIntegrationTest
Assert.IsFalse(result.Success);
Assert.AreEqual(DataTypeOperationStatus.NonDeletable, result.Status);
}
+
+ [Test]
+ public async Task DataTypeService_Can_Get_References()
+ {
+ IEnumerable dataTypeDefinitions = await DataTypeService.GetByEditorAliasAsync(Constants.PropertyEditors.Aliases.RichText);
+
+ IContentType documentType = ContentTypeBuilder.CreateSimpleContentType("umbTextpage", "Text Page");
+ ContentTypeService.Save(documentType);
+
+ IMediaType mediaType = MediaTypeBuilder.CreateSimpleMediaType("umbMediaItem", "Media Item");
+ MediaTypeService.Save(mediaType);
+
+ documentType = ContentTypeService.Get(documentType.Id);
+ Assert.IsNotNull(documentType.PropertyTypes.SingleOrDefault(pt => pt.PropertyEditorAlias is Constants.PropertyEditors.Aliases.RichText));
+
+ mediaType = MediaTypeService.Get(mediaType.Id);
+ Assert.IsNotNull(mediaType.PropertyTypes.SingleOrDefault(pt => pt.PropertyEditorAlias is Constants.PropertyEditors.Aliases.RichText));
+
+ var definition = dataTypeDefinitions.First();
+ var definitionKey = definition.Key;
+ PagedModel result = await DataTypeService.GetPagedRelationsAsync(definitionKey, 0, 10);
+ Assert.AreEqual(2, result.Total);
+
+ RelationItemModel firstResult = result.Items.First();
+ Assert.AreEqual("umbTextpage", firstResult.ContentTypeAlias);
+ Assert.AreEqual("Text Page", firstResult.ContentTypeName);
+ Assert.AreEqual("icon-document", firstResult.ContentTypeIcon);
+ Assert.AreEqual(documentType.Key, firstResult.ContentTypeKey);
+ Assert.AreEqual("bodyText", firstResult.NodeAlias);
+ Assert.AreEqual("Body text", firstResult.NodeName);
+
+ RelationItemModel secondResult = result.Items.Skip(1).First();
+ Assert.AreEqual("umbMediaItem", secondResult.ContentTypeAlias);
+ Assert.AreEqual("Media Item", secondResult.ContentTypeName);
+ Assert.AreEqual("icon-picture", secondResult.ContentTypeIcon);
+ Assert.AreEqual(mediaType.Key, secondResult.ContentTypeKey);
+ Assert.AreEqual("bodyText", secondResult.NodeAlias);
+ Assert.AreEqual("Body text", secondResult.NodeName);
+ }
}