From 2d0a1a03e616359ac7e4a435fd69ad0819dbbc74 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Mon, 28 Jul 2025 12:28:36 +0200 Subject: [PATCH 01/22] Create sign provider collection and call registered providers on rendering a page of tree item view models. Re-work tree controller constructors to provide registered providers as a collection. --- .../Tree/AncestorsDataTypeTreeController.cs | 9 +++ .../Tree/ChildrenDataTypeTreeController.cs | 11 +++- .../Tree/DataTypeTreeControllerBase.cs | 16 +++++- .../Tree/RootDataTypeTreeController.cs | 12 +++- .../Tree/SiblingsDataTypeTreeController.cs | 9 +++ .../Tree/AncestorsDictionaryTreeController.cs | 11 +++- .../Tree/ChildrenDictionaryTreeController.cs | 11 +++- .../Tree/DictionaryTreeControllerBase.cs | 14 ++++- .../Tree/RootDictionaryTreeController.cs | 10 +++- .../Tree/AncestorsDocumentTreeController.cs | 25 +++++++++ .../Tree/ChildrenDocumentTreeController.cs | 27 ++++++++- .../Tree/DocumentTreeControllerBase.cs | 28 +++++++++- .../Tree/RootDocumentTreeController.cs | 27 ++++++++- .../Tree/SiblingsDocumentTreeController.cs | 25 +++++++++ ...ncestorsDocumentBlueprintTreeController.cs | 9 +++ ...ChildrenDocumentBlueprintTreeController.cs | 11 +++- .../DocumentBlueprintTreeControllerBase.cs | 16 +++++- .../RootDocumentBlueprintTreeController.cs | 11 +++- ...SiblingsDocumentBlueprintTreeController.cs | 9 +++ .../AncestorsDocumentTypeTreeController.cs | 11 +++- .../ChildrenDocumentTypeTreeController.cs | 11 +++- .../Tree/DocumentTypeTreeControllerBase.cs | 16 +++++- .../Tree/RootDocumentTypeTreeController.cs | 11 +++- .../SiblingsDocumentTypeTreeController.cs | 9 +++ .../Tree/AncestorsMediaTreeController.cs | 18 +++++- .../Media/Tree/ChildrenMediaTreeController.cs | 18 +++++- .../Media/Tree/MediaTreeControllerBase.cs | 26 ++++++++- .../Media/Tree/RootMediaTreeController.cs | 18 +++++- .../Media/Tree/SiblingsMediaTreeController.cs | 16 ++++++ .../Tree/AncestorsMediaTypeTreeController.cs | 11 +++- .../Tree/ChildrenMediaTypeTreeController.cs | 11 +++- .../Tree/MediaTypeTreeControllerBase.cs | 16 +++++- .../Tree/RootMediaTypeTreeController.cs | 11 +++- .../Tree/SiblingsMediaTypeTreeController.cs | 9 +++ .../Tree/MemberGroupTreeControllerBase.cs | 9 ++- .../Tree/RootMemberGroupTreeController.cs | 11 +++- .../Tree/MemberTypeTreeControllerBase.cs | 17 +++++- .../Tree/RootMemberTypeTreeController.cs | 11 +++- .../Tree/AncestorsTemplateTreeController.cs | 11 +++- .../Tree/ChildrenTemplateTreeController.cs | 11 +++- .../Tree/RootTemplateTreeController.cs | 11 +++- .../Tree/SiblingsTemplateTreeController.cs | 10 ++++ .../Tree/TemplateTreeControllerBase.cs | 9 ++- .../Tree/EntityTreeControllerBase.cs | 55 +++++++++++++++---- .../Tree/FolderTreeControllerBase.cs | 14 ++++- .../Tree/NamedEntityTreeControllerBase.cs | 9 ++- .../Tree/UserStartNodeTreeControllerBase.cs | 21 ++++++- .../UmbracoBuilderExtensions.cs | 13 ++++- .../Services/Signs/ISignProvider.cs | 19 +++++++ .../Services/Signs/SignProviderCollection.cs | 18 ++++++ .../Signs/SignProviderCollectionBuilder.cs | 12 ++++ .../ViewModels/SignModel.cs | 8 +++ .../Tree/EntityTreeItemResponseModel.cs | 4 +- 53 files changed, 718 insertions(+), 58 deletions(-) create mode 100644 src/Umbraco.Cms.Api.Management/Services/Signs/ISignProvider.cs create mode 100644 src/Umbraco.Cms.Api.Management/Services/Signs/SignProviderCollection.cs create mode 100644 src/Umbraco.Cms.Api.Management/Services/Signs/SignProviderCollectionBuilder.cs create mode 100644 src/Umbraco.Cms.Api.Management/ViewModels/SignModel.cs diff --git a/src/Umbraco.Cms.Api.Management/Controllers/DataType/Tree/AncestorsDataTypeTreeController.cs b/src/Umbraco.Cms.Api.Management/Controllers/DataType/Tree/AncestorsDataTypeTreeController.cs index d93b76718dee..b3a103a01a3a 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/DataType/Tree/AncestorsDataTypeTreeController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/DataType/Tree/AncestorsDataTypeTreeController.cs @@ -1,6 +1,8 @@ using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; +using Umbraco.Cms.Api.Management.Services.Signs; using Umbraco.Cms.Api.Management.ViewModels.Tree; using Umbraco.Cms.Core.Services; @@ -9,11 +11,18 @@ namespace Umbraco.Cms.Api.Management.Controllers.DataType.Tree; [ApiVersion("1.0")] public class AncestorsDataTypeTreeController : DataTypeTreeControllerBase { + [Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 18.")] public AncestorsDataTypeTreeController(IEntityService entityService, IDataTypeService dataTypeService) : base(entityService, dataTypeService) { } + [ActivatorUtilitiesConstructor] + public AncestorsDataTypeTreeController(IEntityService entityService, SignProviderCollection signProviders, IDataTypeService dataTypeService) + : base(entityService, signProviders, dataTypeService) + { + } + [HttpGet("ancestors")] [MapToApiVersion("1.0")] [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] diff --git a/src/Umbraco.Cms.Api.Management/Controllers/DataType/Tree/ChildrenDataTypeTreeController.cs b/src/Umbraco.Cms.Api.Management/Controllers/DataType/Tree/ChildrenDataTypeTreeController.cs index cd9f4171cd68..7c801530ac0d 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/DataType/Tree/ChildrenDataTypeTreeController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/DataType/Tree/ChildrenDataTypeTreeController.cs @@ -1,20 +1,29 @@ -using Asp.Versioning; +using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Api.Common.ViewModels.Pagination; using Umbraco.Cms.Api.Management.ViewModels.Tree; +using Microsoft.Extensions.DependencyInjection; +using Umbraco.Cms.Api.Management.Services.Signs; namespace Umbraco.Cms.Api.Management.Controllers.DataType.Tree; [ApiVersion("1.0")] public class ChildrenDataTypeTreeController : DataTypeTreeControllerBase { + [Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 18.")] public ChildrenDataTypeTreeController(IEntityService entityService, IDataTypeService dataTypeService) : base(entityService, dataTypeService) { } + [ActivatorUtilitiesConstructor] + public ChildrenDataTypeTreeController(IEntityService entityService, SignProviderCollection signProviders, IDataTypeService dataTypeService) + : base(entityService, signProviders, dataTypeService) + { + } + [HttpGet("children")] [MapToApiVersion("1.0")] [ProducesResponseType(typeof(PagedViewModel), StatusCodes.Status200OK)] diff --git a/src/Umbraco.Cms.Api.Management/Controllers/DataType/Tree/DataTypeTreeControllerBase.cs b/src/Umbraco.Cms.Api.Management/Controllers/DataType/Tree/DataTypeTreeControllerBase.cs index 52a7319b1a11..f0dbb8a8ecfd 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/DataType/Tree/DataTypeTreeControllerBase.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/DataType/Tree/DataTypeTreeControllerBase.cs @@ -1,9 +1,12 @@ -using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Api.Management.Controllers.Tree; using Umbraco.Cms.Api.Management.Routing; +using Umbraco.Cms.Api.Management.Services.Signs; using Umbraco.Cms.Api.Management.ViewModels.Tree; using Umbraco.Cms.Core; +using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Entities; using Umbraco.Cms.Core.Services; @@ -19,8 +22,17 @@ public class DataTypeTreeControllerBase : FolderTreeControllerBase + : this( + entityService, + StaticServiceProvider.Instance.GetRequiredService(), + dataTypeService) + { + } + + public DataTypeTreeControllerBase(IEntityService entityService, SignProviderCollection signProviders, IDataTypeService dataTypeService) + : base(entityService, signProviders) => _dataTypeService = dataTypeService; protected override UmbracoObjectTypes ItemObjectType => UmbracoObjectTypes.DataType; diff --git a/src/Umbraco.Cms.Api.Management/Controllers/DataType/Tree/RootDataTypeTreeController.cs b/src/Umbraco.Cms.Api.Management/Controllers/DataType/Tree/RootDataTypeTreeController.cs index 0554bff8e5e7..fa1b5c7bfbd8 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/DataType/Tree/RootDataTypeTreeController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/DataType/Tree/RootDataTypeTreeController.cs @@ -1,21 +1,29 @@ -using Asp.Versioning; +using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Api.Common.ViewModels.Pagination; -using Umbraco.Cms.Api.Management.ViewModels.DataType.Item; using Umbraco.Cms.Api.Management.ViewModels.Tree; +using Microsoft.Extensions.DependencyInjection; +using Umbraco.Cms.Api.Management.Services.Signs; namespace Umbraco.Cms.Api.Management.Controllers.DataType.Tree; [ApiVersion("1.0")] public class RootDataTypeTreeController : DataTypeTreeControllerBase { + [Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 18.")] public RootDataTypeTreeController(IEntityService entityService, IDataTypeService dataTypeService) : base(entityService, dataTypeService) { } + [ActivatorUtilitiesConstructor] + public RootDataTypeTreeController(IEntityService entityService, SignProviderCollection signProviders, IDataTypeService dataTypeService) + : base(entityService, signProviders, dataTypeService) + { + } + [HttpGet("root")] [MapToApiVersion("1.0")] [ProducesResponseType(typeof(PagedViewModel), StatusCodes.Status200OK)] diff --git a/src/Umbraco.Cms.Api.Management/Controllers/DataType/Tree/SiblingsDataTypeTreeController.cs b/src/Umbraco.Cms.Api.Management/Controllers/DataType/Tree/SiblingsDataTypeTreeController.cs index 253d32e3e8cc..fdf98af7e143 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/DataType/Tree/SiblingsDataTypeTreeController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/DataType/Tree/SiblingsDataTypeTreeController.cs @@ -1,5 +1,7 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; +using Umbraco.Cms.Api.Management.Services.Signs; using Umbraco.Cms.Api.Management.ViewModels.Tree; using Umbraco.Cms.Core.Services; @@ -7,11 +9,18 @@ namespace Umbraco.Cms.Api.Management.Controllers.DataType.Tree; public class SiblingsDataTypeTreeController : DataTypeTreeControllerBase { + [Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 18.")] public SiblingsDataTypeTreeController(IEntityService entityService, IDataTypeService dataTypeService) : base(entityService, dataTypeService) { } + [ActivatorUtilitiesConstructor] + public SiblingsDataTypeTreeController(IEntityService entityService, SignProviderCollection signProviders, IDataTypeService dataTypeService) + : base(entityService, signProviders, dataTypeService) + { + } + [HttpGet("siblings")] [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] public Task>> Siblings(CancellationToken cancellationToken, Guid target, int before, int after) diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Dictionary/Tree/AncestorsDictionaryTreeController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Dictionary/Tree/AncestorsDictionaryTreeController.cs index 2c17199602e9..f7c94de4db72 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Dictionary/Tree/AncestorsDictionaryTreeController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Dictionary/Tree/AncestorsDictionaryTreeController.cs @@ -1,6 +1,8 @@ -using Asp.Versioning; +using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; +using Umbraco.Cms.Api.Management.Services.Signs; using Umbraco.Cms.Api.Management.ViewModels.Tree; using Umbraco.Cms.Core.Services; @@ -9,11 +11,18 @@ namespace Umbraco.Cms.Api.Management.Controllers.Dictionary.Tree; [ApiVersion("1.0")] public class AncestorsDictionaryTreeController : DictionaryTreeControllerBase { + [Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 18.")] public AncestorsDictionaryTreeController(IEntityService entityService, IDictionaryItemService dictionaryItemService) : base(entityService, dictionaryItemService) { } + [ActivatorUtilitiesConstructor] + public AncestorsDictionaryTreeController(IEntityService entityService, SignProviderCollection signProviders, IDictionaryItemService dictionaryItemService) + : base(entityService, signProviders, dictionaryItemService) + { + } + [HttpGet("ancestors")] [MapToApiVersion("1.0")] [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Dictionary/Tree/ChildrenDictionaryTreeController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Dictionary/Tree/ChildrenDictionaryTreeController.cs index 59b23ff801d4..7c98340c0a3e 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Dictionary/Tree/ChildrenDictionaryTreeController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Dictionary/Tree/ChildrenDictionaryTreeController.cs @@ -1,21 +1,30 @@ -using Asp.Versioning; +using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Api.Common.ViewModels.Pagination; using Umbraco.Cms.Api.Management.ViewModels.Tree; +using Microsoft.Extensions.DependencyInjection; +using Umbraco.Cms.Api.Management.Services.Signs; namespace Umbraco.Cms.Api.Management.Controllers.Dictionary.Tree; [ApiVersion("1.0")] public class ChildrenDictionaryTreeController : DictionaryTreeControllerBase { + [Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 18.")] public ChildrenDictionaryTreeController(IEntityService entityService, IDictionaryItemService dictionaryItemService) : base(entityService, dictionaryItemService) { } + [ActivatorUtilitiesConstructor] + public ChildrenDictionaryTreeController(IEntityService entityService, SignProviderCollection signProviders, IDictionaryItemService dictionaryItemService) + : base(entityService, signProviders, dictionaryItemService) + { + } + [HttpGet("children")] [MapToApiVersion("1.0")] [ProducesResponseType(typeof(PagedViewModel), StatusCodes.Status200OK)] diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Dictionary/Tree/DictionaryTreeControllerBase.cs b/src/Umbraco.Cms.Api.Management/Controllers/Dictionary/Tree/DictionaryTreeControllerBase.cs index 2b02ff541a0c..653337024573 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Dictionary/Tree/DictionaryTreeControllerBase.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Dictionary/Tree/DictionaryTreeControllerBase.cs @@ -1,10 +1,13 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Api.Management.Controllers.Tree; using Umbraco.Cms.Api.Management.Routing; +using Umbraco.Cms.Api.Management.Services.Signs; using Umbraco.Cms.Api.Management.ViewModels; using Umbraco.Cms.Api.Management.ViewModels.Tree; using Umbraco.Cms.Core; +using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Web.Common.Authorization; @@ -18,8 +21,17 @@ namespace Umbraco.Cms.Api.Management.Controllers.Dictionary.Tree; // tree controller base. We'll keep it though, in the hope that we can mend EntityService. public class DictionaryTreeControllerBase : NamedEntityTreeControllerBase { + [Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 18.")] public DictionaryTreeControllerBase(IEntityService entityService, IDictionaryItemService dictionaryItemService) - : base(entityService) => + : this( + entityService, + StaticServiceProvider.Instance.GetRequiredService(), + dictionaryItemService) + { + } + + public DictionaryTreeControllerBase(IEntityService entityService, SignProviderCollection signProviders, IDictionaryItemService dictionaryItemService) + : base(entityService, signProviders) => DictionaryItemService = dictionaryItemService; // dictionary items do not currently have a known UmbracoObjectType, so we'll settle with Unknown for now diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Dictionary/Tree/RootDictionaryTreeController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Dictionary/Tree/RootDictionaryTreeController.cs index a6d65ed76428..9f44b08a2294 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Dictionary/Tree/RootDictionaryTreeController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Dictionary/Tree/RootDictionaryTreeController.cs @@ -1,10 +1,12 @@ -using Asp.Versioning; +using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Api.Common.ViewModels.Pagination; using Umbraco.Cms.Api.Management.ViewModels.Tree; +using Microsoft.Extensions.DependencyInjection; +using Umbraco.Cms.Api.Management.Services.Signs; namespace Umbraco.Cms.Api.Management.Controllers.Dictionary.Tree; @@ -16,6 +18,12 @@ public RootDictionaryTreeController(IEntityService entityService, IDictionaryIte { } + [ActivatorUtilitiesConstructor] + public RootDictionaryTreeController(IEntityService entityService, SignProviderCollection signProviders, IDictionaryItemService dictionaryItemService) + : base(entityService, signProviders, dictionaryItemService) + { + } + [HttpGet("root")] [MapToApiVersion("1.0")] [ProducesResponseType(typeof(PagedViewModel), StatusCodes.Status200OK)] diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Document/Tree/AncestorsDocumentTreeController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Document/Tree/AncestorsDocumentTreeController.cs index eef178ea7086..5e3c347d0015 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Document/Tree/AncestorsDocumentTreeController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Document/Tree/AncestorsDocumentTreeController.cs @@ -1,8 +1,10 @@ using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Api.Management.Factories; using Umbraco.Cms.Api.Management.Services.Entities; +using Umbraco.Cms.Api.Management.Services.Signs; using Umbraco.Cms.Api.Management.ViewModels.Tree; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Security; @@ -13,6 +15,7 @@ namespace Umbraco.Cms.Api.Management.Controllers.Document.Tree; [ApiVersion("1.0")] public class AncestorsDocumentTreeController : DocumentTreeControllerBase { + [Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 18.")] public AncestorsDocumentTreeController( IEntityService entityService, IUserStartNodeEntitiesService userStartNodeEntitiesService, @@ -32,6 +35,28 @@ public AncestorsDocumentTreeController( { } + [ActivatorUtilitiesConstructor] + public AncestorsDocumentTreeController( + IEntityService entityService, + SignProviderCollection signProviders, + IUserStartNodeEntitiesService userStartNodeEntitiesService, + IDataTypeService dataTypeService, + IPublicAccessService publicAccessService, + AppCaches appCaches, + IBackOfficeSecurityAccessor backofficeSecurityAccessor, + IDocumentPresentationFactory documentPresentationFactory) + : base( + entityService, + signProviders, + userStartNodeEntitiesService, + dataTypeService, + publicAccessService, + appCaches, + backofficeSecurityAccessor, + documentPresentationFactory) + { + } + [HttpGet("ancestors")] [MapToApiVersion("1.0")] [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Document/Tree/ChildrenDocumentTreeController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Document/Tree/ChildrenDocumentTreeController.cs index 5c6b428424a3..351a0dc9e7c3 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Document/Tree/ChildrenDocumentTreeController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Document/Tree/ChildrenDocumentTreeController.cs @@ -1,4 +1,4 @@ -using Asp.Versioning; +using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Umbraco.Cms.Core.Cache; @@ -8,12 +8,15 @@ using Umbraco.Cms.Api.Common.ViewModels.Pagination; using Umbraco.Cms.Api.Management.Factories; using Umbraco.Cms.Api.Management.ViewModels.Tree; +using Microsoft.Extensions.DependencyInjection; +using Umbraco.Cms.Api.Management.Services.Signs; namespace Umbraco.Cms.Api.Management.Controllers.Document.Tree; [ApiVersion("1.0")] public class ChildrenDocumentTreeController : DocumentTreeControllerBase { + [Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 18.")] public ChildrenDocumentTreeController( IEntityService entityService, IUserStartNodeEntitiesService userStartNodeEntitiesService, @@ -33,6 +36,28 @@ public ChildrenDocumentTreeController( { } + [ActivatorUtilitiesConstructor] + public ChildrenDocumentTreeController( + IEntityService entityService, + SignProviderCollection signProviders, + IUserStartNodeEntitiesService userStartNodeEntitiesService, + IDataTypeService dataTypeService, + IPublicAccessService publicAccessService, + AppCaches appCaches, + IBackOfficeSecurityAccessor backofficeSecurityAccessor, + IDocumentPresentationFactory documentPresentationFactory) + : base( + entityService, + signProviders, + userStartNodeEntitiesService, + dataTypeService, + publicAccessService, + appCaches, + backofficeSecurityAccessor, + documentPresentationFactory) + { + } + [HttpGet("children")] [MapToApiVersion("1.0")] [ProducesResponseType(typeof(PagedViewModel), StatusCodes.Status200OK)] diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Document/Tree/DocumentTreeControllerBase.cs b/src/Umbraco.Cms.Api.Management/Controllers/Document/Tree/DocumentTreeControllerBase.cs index cacf862b57bb..a2015d7c7626 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Document/Tree/DocumentTreeControllerBase.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Document/Tree/DocumentTreeControllerBase.cs @@ -1,13 +1,16 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Api.Management.Controllers.Tree; using Umbraco.Cms.Api.Management.Factories; using Umbraco.Cms.Api.Management.Routing; using Umbraco.Cms.Api.Management.Services.Entities; +using Umbraco.Cms.Api.Management.Services.Signs; using Umbraco.Cms.Api.Management.ViewModels; using Umbraco.Cms.Api.Management.ViewModels.Tree; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Cache; +using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Entities; using Umbraco.Cms.Core.Security; @@ -26,6 +29,7 @@ public abstract class DocumentTreeControllerBase : UserStartNodeTreeControllerBa private readonly IBackOfficeSecurityAccessor _backofficeSecurityAccessor; private readonly IDocumentPresentationFactory _documentPresentationFactory; + [Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 18.")] protected DocumentTreeControllerBase( IEntityService entityService, IUserStartNodeEntitiesService userStartNodeEntitiesService, @@ -34,7 +38,29 @@ protected DocumentTreeControllerBase( AppCaches appCaches, IBackOfficeSecurityAccessor backofficeSecurityAccessor, IDocumentPresentationFactory documentPresentationFactory) - : base(entityService, userStartNodeEntitiesService, dataTypeService) + : this( + entityService, + StaticServiceProvider.Instance.GetRequiredService(), + userStartNodeEntitiesService, + dataTypeService, + publicAccessService, + appCaches, + backofficeSecurityAccessor, + documentPresentationFactory) + { + } + + [ActivatorUtilitiesConstructor] + protected DocumentTreeControllerBase( + IEntityService entityService, + SignProviderCollection signProviders, + IUserStartNodeEntitiesService userStartNodeEntitiesService, + IDataTypeService dataTypeService, + IPublicAccessService publicAccessService, + AppCaches appCaches, + IBackOfficeSecurityAccessor backofficeSecurityAccessor, + IDocumentPresentationFactory documentPresentationFactory) + : base(entityService, signProviders, userStartNodeEntitiesService, dataTypeService) { _publicAccessService = publicAccessService; _appCaches = appCaches; diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Document/Tree/RootDocumentTreeController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Document/Tree/RootDocumentTreeController.cs index a9d63dc4b037..6e9f0683334d 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Document/Tree/RootDocumentTreeController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Document/Tree/RootDocumentTreeController.cs @@ -1,4 +1,4 @@ -using Asp.Versioning; +using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Umbraco.Cms.Core.Cache; @@ -8,12 +8,15 @@ using Umbraco.Cms.Api.Common.ViewModels.Pagination; using Umbraco.Cms.Api.Management.Factories; using Umbraco.Cms.Api.Management.ViewModels.Tree; +using Microsoft.Extensions.DependencyInjection; +using Umbraco.Cms.Api.Management.Services.Signs; namespace Umbraco.Cms.Api.Management.Controllers.Document.Tree; [ApiVersion("1.0")] public class RootDocumentTreeController : DocumentTreeControllerBase { + [Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 18.")] public RootDocumentTreeController( IEntityService entityService, IUserStartNodeEntitiesService userStartNodeEntitiesService, @@ -33,6 +36,28 @@ public RootDocumentTreeController( { } + [ActivatorUtilitiesConstructor] + public RootDocumentTreeController( + IEntityService entityService, + SignProviderCollection signProviders, + IUserStartNodeEntitiesService userStartNodeEntitiesService, + IDataTypeService dataTypeService, + IPublicAccessService publicAccessService, + AppCaches appCaches, + IBackOfficeSecurityAccessor backofficeSecurityAccessor, + IDocumentPresentationFactory documentPresentationFactory) + : base( + entityService, + signProviders, + userStartNodeEntitiesService, + dataTypeService, + publicAccessService, + appCaches, + backofficeSecurityAccessor, + documentPresentationFactory) + { + } + [HttpGet("root")] [MapToApiVersion("1.0")] [ProducesResponseType(typeof(PagedViewModel), StatusCodes.Status200OK)] diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Document/Tree/SiblingsDocumentTreeController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Document/Tree/SiblingsDocumentTreeController.cs index 3fec79bf3624..8382d981496a 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Document/Tree/SiblingsDocumentTreeController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Document/Tree/SiblingsDocumentTreeController.cs @@ -1,8 +1,10 @@ using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Api.Management.Factories; using Umbraco.Cms.Api.Management.Services.Entities; +using Umbraco.Cms.Api.Management.Services.Signs; using Umbraco.Cms.Api.Management.ViewModels.Tree; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Security; @@ -13,6 +15,7 @@ namespace Umbraco.Cms.Api.Management.Controllers.Document.Tree; [ApiVersion("1.0")] public class SiblingsDocumentTreeController : DocumentTreeControllerBase { + [Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 18.")] public SiblingsDocumentTreeController( IEntityService entityService, IUserStartNodeEntitiesService userStartNodeEntitiesService, @@ -32,6 +35,28 @@ public SiblingsDocumentTreeController( { } + [ActivatorUtilitiesConstructor] + public SiblingsDocumentTreeController( + IEntityService entityService, + SignProviderCollection signProviders, + IUserStartNodeEntitiesService userStartNodeEntitiesService, + IDataTypeService dataTypeService, + IPublicAccessService publicAccessService, + AppCaches appCaches, + IBackOfficeSecurityAccessor backofficeSecurityAccessor, + IDocumentPresentationFactory documentPresentationFactory) + : base( + entityService, + signProviders, + userStartNodeEntitiesService, + dataTypeService, + publicAccessService, + appCaches, + backofficeSecurityAccessor, + documentPresentationFactory) + { + } + [HttpGet("siblings")] [MapToApiVersion("1.0")] [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] diff --git a/src/Umbraco.Cms.Api.Management/Controllers/DocumentBlueprint/Tree/AncestorsDocumentBlueprintTreeController.cs b/src/Umbraco.Cms.Api.Management/Controllers/DocumentBlueprint/Tree/AncestorsDocumentBlueprintTreeController.cs index 9124b1ae2a10..c3d155e54c5f 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/DocumentBlueprint/Tree/AncestorsDocumentBlueprintTreeController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/DocumentBlueprint/Tree/AncestorsDocumentBlueprintTreeController.cs @@ -1,7 +1,9 @@ using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Api.Management.Factories; +using Umbraco.Cms.Api.Management.Services.Signs; using Umbraco.Cms.Api.Management.ViewModels.Tree; using Umbraco.Cms.Core.Services; @@ -10,11 +12,18 @@ namespace Umbraco.Cms.Api.Management.Controllers.DocumentBlueprint.Tree; [ApiVersion("1.0")] public class AncestorsDocumentBlueprintTreeController : DocumentBlueprintTreeControllerBase { + [Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 18.")] public AncestorsDocumentBlueprintTreeController(IEntityService entityService, IDocumentPresentationFactory documentPresentationFactory) : base(entityService, documentPresentationFactory) { } + [ActivatorUtilitiesConstructor] + public AncestorsDocumentBlueprintTreeController(IEntityService entityService, SignProviderCollection signProviders, IDocumentPresentationFactory documentPresentationFactory) + : base(entityService, signProviders, documentPresentationFactory) + { + } + [HttpGet("ancestors")] [MapToApiVersion("1.0")] [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] diff --git a/src/Umbraco.Cms.Api.Management/Controllers/DocumentBlueprint/Tree/ChildrenDocumentBlueprintTreeController.cs b/src/Umbraco.Cms.Api.Management/Controllers/DocumentBlueprint/Tree/ChildrenDocumentBlueprintTreeController.cs index 92c1fe28b29a..7f2f3ea2267b 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/DocumentBlueprint/Tree/ChildrenDocumentBlueprintTreeController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/DocumentBlueprint/Tree/ChildrenDocumentBlueprintTreeController.cs @@ -1,8 +1,10 @@ -using Asp.Versioning; +using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Api.Common.ViewModels.Pagination; using Umbraco.Cms.Api.Management.Factories; +using Umbraco.Cms.Api.Management.Services.Signs; using Umbraco.Cms.Api.Management.ViewModels.Tree; using Umbraco.Cms.Core.Services; @@ -11,11 +13,18 @@ namespace Umbraco.Cms.Api.Management.Controllers.DocumentBlueprint.Tree; [ApiVersion("1.0")] public class ChildrenDocumentBlueprintTreeController : DocumentBlueprintTreeControllerBase { + [Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 18.")] public ChildrenDocumentBlueprintTreeController(IEntityService entityService, IDocumentPresentationFactory documentPresentationFactory) : base(entityService, documentPresentationFactory) { } + [ActivatorUtilitiesConstructor] + public ChildrenDocumentBlueprintTreeController(IEntityService entityService, SignProviderCollection signProviders, IDocumentPresentationFactory documentPresentationFactory) + : base(entityService, signProviders, documentPresentationFactory) + { + } + [HttpGet("children")] [MapToApiVersion("1.0")] [ProducesResponseType(typeof(PagedViewModel), StatusCodes.Status200OK)] diff --git a/src/Umbraco.Cms.Api.Management/Controllers/DocumentBlueprint/Tree/DocumentBlueprintTreeControllerBase.cs b/src/Umbraco.Cms.Api.Management/Controllers/DocumentBlueprint/Tree/DocumentBlueprintTreeControllerBase.cs index aeaf65b7ef08..3a02abb6f0bb 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/DocumentBlueprint/Tree/DocumentBlueprintTreeControllerBase.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/DocumentBlueprint/Tree/DocumentBlueprintTreeControllerBase.cs @@ -1,10 +1,13 @@ -using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Api.Management.Controllers.Tree; using Umbraco.Cms.Api.Management.Factories; using Umbraco.Cms.Api.Management.Routing; +using Umbraco.Cms.Api.Management.Services.Signs; using Umbraco.Cms.Api.Management.ViewModels.Tree; using Umbraco.Cms.Core; +using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Entities; using Umbraco.Cms.Core.Services; @@ -19,8 +22,17 @@ public class DocumentBlueprintTreeControllerBase : FolderTreeControllerBase(), + documentPresentationFactory) + { + } + + public DocumentBlueprintTreeControllerBase(IEntityService entityService, SignProviderCollection signProviders, IDocumentPresentationFactory documentPresentationFactory) + : base(entityService, signProviders) => _documentPresentationFactory = documentPresentationFactory; protected override UmbracoObjectTypes ItemObjectType => UmbracoObjectTypes.DocumentBlueprint; diff --git a/src/Umbraco.Cms.Api.Management/Controllers/DocumentBlueprint/Tree/RootDocumentBlueprintTreeController.cs b/src/Umbraco.Cms.Api.Management/Controllers/DocumentBlueprint/Tree/RootDocumentBlueprintTreeController.cs index 79e20385c1fa..b2a8bfa9cf96 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/DocumentBlueprint/Tree/RootDocumentBlueprintTreeController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/DocumentBlueprint/Tree/RootDocumentBlueprintTreeController.cs @@ -1,8 +1,10 @@ -using Asp.Versioning; +using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Api.Common.ViewModels.Pagination; using Umbraco.Cms.Api.Management.Factories; +using Umbraco.Cms.Api.Management.Services.Signs; using Umbraco.Cms.Api.Management.ViewModels.Tree; using Umbraco.Cms.Core.Services; @@ -11,11 +13,18 @@ namespace Umbraco.Cms.Api.Management.Controllers.DocumentBlueprint.Tree; [ApiVersion("1.0")] public class RootDocumentBlueprintTreeController : DocumentBlueprintTreeControllerBase { + [Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 18.")] public RootDocumentBlueprintTreeController(IEntityService entityService, IDocumentPresentationFactory documentPresentationFactory) : base(entityService, documentPresentationFactory) { } + [ActivatorUtilitiesConstructor] + public RootDocumentBlueprintTreeController(IEntityService entityService, SignProviderCollection signProviders, IDocumentPresentationFactory documentPresentationFactory) + : base(entityService, signProviders, documentPresentationFactory) + { + } + [HttpGet("root")] [MapToApiVersion("1.0")] [ProducesResponseType(typeof(PagedViewModel), StatusCodes.Status200OK)] diff --git a/src/Umbraco.Cms.Api.Management/Controllers/DocumentBlueprint/Tree/SiblingsDocumentBlueprintTreeController.cs b/src/Umbraco.Cms.Api.Management/Controllers/DocumentBlueprint/Tree/SiblingsDocumentBlueprintTreeController.cs index ac5578155fae..2cdb8b4235c9 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/DocumentBlueprint/Tree/SiblingsDocumentBlueprintTreeController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/DocumentBlueprint/Tree/SiblingsDocumentBlueprintTreeController.cs @@ -1,6 +1,8 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Api.Management.Factories; +using Umbraco.Cms.Api.Management.Services.Signs; using Umbraco.Cms.Api.Management.ViewModels.Tree; using Umbraco.Cms.Core.Services; @@ -8,11 +10,18 @@ namespace Umbraco.Cms.Api.Management.Controllers.DocumentBlueprint.Tree; public class SiblingsDocumentBlueprintTreeController : DocumentBlueprintTreeControllerBase { + [Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 18.")] public SiblingsDocumentBlueprintTreeController(IEntityService entityService, IDocumentPresentationFactory documentPresentationFactory) : base(entityService, documentPresentationFactory) { } + [ActivatorUtilitiesConstructor] + public SiblingsDocumentBlueprintTreeController(IEntityService entityService, SignProviderCollection signProviders, IDocumentPresentationFactory documentPresentationFactory) + : base(entityService, signProviders, documentPresentationFactory) + { + } + [HttpGet("siblings")] [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] public Task>> Siblings( diff --git a/src/Umbraco.Cms.Api.Management/Controllers/DocumentType/Tree/AncestorsDocumentTypeTreeController.cs b/src/Umbraco.Cms.Api.Management/Controllers/DocumentType/Tree/AncestorsDocumentTypeTreeController.cs index c49f7b0d3ccc..02bb34403f4f 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/DocumentType/Tree/AncestorsDocumentTypeTreeController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/DocumentType/Tree/AncestorsDocumentTypeTreeController.cs @@ -1,6 +1,8 @@ -using Asp.Versioning; +using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; +using Umbraco.Cms.Api.Management.Services.Signs; using Umbraco.Cms.Api.Management.ViewModels.Tree; using Umbraco.Cms.Core.Services; @@ -9,11 +11,18 @@ namespace Umbraco.Cms.Api.Management.Controllers.DocumentType.Tree; [ApiVersion("1.0")] public class AncestorsDocumentTypeTreeController : DocumentTypeTreeControllerBase { + [Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 18.")] public AncestorsDocumentTypeTreeController(IEntityService entityService, IContentTypeService contentTypeService) : base(entityService, contentTypeService) { } + [ActivatorUtilitiesConstructor] + public AncestorsDocumentTypeTreeController(IEntityService entityService, SignProviderCollection signProviders, IContentTypeService contentTypeService) + : base(entityService, signProviders, contentTypeService) + { + } + [HttpGet("ancestors")] [MapToApiVersion("1.0")] [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] diff --git a/src/Umbraco.Cms.Api.Management/Controllers/DocumentType/Tree/ChildrenDocumentTypeTreeController.cs b/src/Umbraco.Cms.Api.Management/Controllers/DocumentType/Tree/ChildrenDocumentTypeTreeController.cs index de25c4c1be17..1805c5096162 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/DocumentType/Tree/ChildrenDocumentTypeTreeController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/DocumentType/Tree/ChildrenDocumentTypeTreeController.cs @@ -1,20 +1,29 @@ -using Asp.Versioning; +using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Api.Common.ViewModels.Pagination; using Umbraco.Cms.Api.Management.ViewModels.Tree; +using Microsoft.Extensions.DependencyInjection; +using Umbraco.Cms.Api.Management.Services.Signs; namespace Umbraco.Cms.Api.Management.Controllers.DocumentType.Tree; [ApiVersion("1.0")] public class ChildrenDocumentTypeTreeController : DocumentTypeTreeControllerBase { + [Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 18.")] public ChildrenDocumentTypeTreeController(IEntityService entityService, IContentTypeService contentTypeService) : base(entityService, contentTypeService) { } + [ActivatorUtilitiesConstructor] + public ChildrenDocumentTypeTreeController(IEntityService entityService, SignProviderCollection signProviders, IContentTypeService contentTypeService) + : base(entityService, signProviders, contentTypeService) + { + } + [HttpGet("children")] [MapToApiVersion("1.0")] [ProducesResponseType(typeof(PagedViewModel), StatusCodes.Status200OK)] diff --git a/src/Umbraco.Cms.Api.Management/Controllers/DocumentType/Tree/DocumentTypeTreeControllerBase.cs b/src/Umbraco.Cms.Api.Management/Controllers/DocumentType/Tree/DocumentTypeTreeControllerBase.cs index 0a5441720016..a76fd49ee8da 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/DocumentType/Tree/DocumentTypeTreeControllerBase.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/DocumentType/Tree/DocumentTypeTreeControllerBase.cs @@ -1,9 +1,12 @@ -using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Api.Management.Controllers.Tree; using Umbraco.Cms.Api.Management.Routing; +using Umbraco.Cms.Api.Management.Services.Signs; using Umbraco.Cms.Api.Management.ViewModels.Tree; using Umbraco.Cms.Core; +using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Entities; using Umbraco.Cms.Core.Services; @@ -18,8 +21,17 @@ public class DocumentTypeTreeControllerBase : FolderTreeControllerBase + : this( + entityService, + StaticServiceProvider.Instance.GetRequiredService(), + contentTypeService) + { + } + + public DocumentTypeTreeControllerBase(IEntityService entityService, SignProviderCollection signProviders, IContentTypeService contentTypeService) + : base(entityService, signProviders) => _contentTypeService = contentTypeService; protected override UmbracoObjectTypes ItemObjectType => UmbracoObjectTypes.DocumentType; diff --git a/src/Umbraco.Cms.Api.Management/Controllers/DocumentType/Tree/RootDocumentTypeTreeController.cs b/src/Umbraco.Cms.Api.Management/Controllers/DocumentType/Tree/RootDocumentTypeTreeController.cs index 4824dc04957a..3a69c8a592f5 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/DocumentType/Tree/RootDocumentTypeTreeController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/DocumentType/Tree/RootDocumentTypeTreeController.cs @@ -1,20 +1,29 @@ -using Asp.Versioning; +using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Api.Common.ViewModels.Pagination; using Umbraco.Cms.Api.Management.ViewModels.Tree; +using Microsoft.Extensions.DependencyInjection; +using Umbraco.Cms.Api.Management.Services.Signs; namespace Umbraco.Cms.Api.Management.Controllers.DocumentType.Tree; [ApiVersion("1.0")] public class RootDocumentTypeTreeController : DocumentTypeTreeControllerBase { + [Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 18.")] public RootDocumentTypeTreeController(IEntityService entityService, IContentTypeService contentTypeService) : base(entityService, contentTypeService) { } + [ActivatorUtilitiesConstructor] + public RootDocumentTypeTreeController(IEntityService entityService, SignProviderCollection signProviders, IContentTypeService contentTypeService) + : base(entityService, signProviders, contentTypeService) + { + } + [HttpGet("root")] [MapToApiVersion("1.0")] [ProducesResponseType(typeof(PagedViewModel), StatusCodes.Status200OK)] diff --git a/src/Umbraco.Cms.Api.Management/Controllers/DocumentType/Tree/SiblingsDocumentTypeTreeController.cs b/src/Umbraco.Cms.Api.Management/Controllers/DocumentType/Tree/SiblingsDocumentTypeTreeController.cs index 7bb9c26358ef..39e4c6ada879 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/DocumentType/Tree/SiblingsDocumentTypeTreeController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/DocumentType/Tree/SiblingsDocumentTypeTreeController.cs @@ -1,5 +1,7 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; +using Umbraco.Cms.Api.Management.Services.Signs; using Umbraco.Cms.Api.Management.ViewModels.Tree; using Umbraco.Cms.Core.Services; @@ -7,11 +9,18 @@ namespace Umbraco.Cms.Api.Management.Controllers.DocumentType.Tree; public class SiblingsDocumentTypeTreeController : DocumentTypeTreeControllerBase { + [Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 18.")] public SiblingsDocumentTypeTreeController(IEntityService entityService, IContentTypeService contentTypeService) : base(entityService, contentTypeService) { } + [ActivatorUtilitiesConstructor] + public SiblingsDocumentTypeTreeController(IEntityService entityService, SignProviderCollection signProviders, IContentTypeService contentTypeService) + : base(entityService, signProviders, contentTypeService) + { + } + [HttpGet("siblings")] [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] public Task>> Siblings( diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Media/Tree/AncestorsMediaTreeController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Media/Tree/AncestorsMediaTreeController.cs index 9fde8d2a8e39..bf5bade404a6 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Media/Tree/AncestorsMediaTreeController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Media/Tree/AncestorsMediaTreeController.cs @@ -1,8 +1,10 @@ -using Asp.Versioning; +using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Api.Management.Factories; using Umbraco.Cms.Api.Management.Services.Entities; +using Umbraco.Cms.Api.Management.Services.Signs; using Umbraco.Cms.Api.Management.ViewModels.Tree; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Security; @@ -13,6 +15,7 @@ namespace Umbraco.Cms.Api.Management.Controllers.Media.Tree; [ApiVersion("1.0")] public class AncestorsMediaTreeController : MediaTreeControllerBase { + [Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 18.")] public AncestorsMediaTreeController( IEntityService entityService, IUserStartNodeEntitiesService userStartNodeEntitiesService, @@ -24,6 +27,19 @@ public AncestorsMediaTreeController( { } + [ActivatorUtilitiesConstructor] + public AncestorsMediaTreeController( + IEntityService entityService, + SignProviderCollection signProviders, + IUserStartNodeEntitiesService userStartNodeEntitiesService, + IDataTypeService dataTypeService, + AppCaches appCaches, + IBackOfficeSecurityAccessor backofficeSecurityAccessor, + IMediaPresentationFactory mediaPresentationFactory) + : base(entityService, signProviders, userStartNodeEntitiesService, dataTypeService, appCaches, backofficeSecurityAccessor, mediaPresentationFactory) + { + } + [HttpGet("ancestors")] [MapToApiVersion("1.0")] [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Media/Tree/ChildrenMediaTreeController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Media/Tree/ChildrenMediaTreeController.cs index 6aee4b020678..c61de0a65b25 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Media/Tree/ChildrenMediaTreeController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Media/Tree/ChildrenMediaTreeController.cs @@ -1,9 +1,11 @@ -using Asp.Versioning; +using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Api.Common.ViewModels.Pagination; using Umbraco.Cms.Api.Management.Factories; using Umbraco.Cms.Api.Management.Services.Entities; +using Umbraco.Cms.Api.Management.Services.Signs; using Umbraco.Cms.Api.Management.ViewModels.Tree; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Security; @@ -14,6 +16,7 @@ namespace Umbraco.Cms.Api.Management.Controllers.Media.Tree; [ApiVersion("1.0")] public class ChildrenMediaTreeController : MediaTreeControllerBase { + [Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 18.")] public ChildrenMediaTreeController( IEntityService entityService, IUserStartNodeEntitiesService userStartNodeEntitiesService, @@ -25,6 +28,19 @@ public ChildrenMediaTreeController( { } + [ActivatorUtilitiesConstructor] + public ChildrenMediaTreeController( + IEntityService entityService, + SignProviderCollection signProviders, + IUserStartNodeEntitiesService userStartNodeEntitiesService, + IDataTypeService dataTypeService, + AppCaches appCaches, + IBackOfficeSecurityAccessor backofficeSecurityAccessor, + IMediaPresentationFactory mediaPresentationFactory) + : base(entityService, signProviders, userStartNodeEntitiesService, dataTypeService, appCaches, backofficeSecurityAccessor, mediaPresentationFactory) + { + } + [HttpGet("children")] [MapToApiVersion("1.0")] [ProducesResponseType(typeof(PagedViewModel), StatusCodes.Status200OK)] diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Media/Tree/MediaTreeControllerBase.cs b/src/Umbraco.Cms.Api.Management/Controllers/Media/Tree/MediaTreeControllerBase.cs index 7b2f7facd4bb..f3283466c4e0 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Media/Tree/MediaTreeControllerBase.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Media/Tree/MediaTreeControllerBase.cs @@ -1,12 +1,15 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Api.Management.Controllers.Tree; using Umbraco.Cms.Api.Management.Factories; using Umbraco.Cms.Api.Management.Routing; using Umbraco.Cms.Api.Management.Services.Entities; +using Umbraco.Cms.Api.Management.Services.Signs; using Umbraco.Cms.Api.Management.ViewModels.Tree; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Cache; +using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Entities; using Umbraco.Cms.Core.Security; @@ -24,6 +27,7 @@ public class MediaTreeControllerBase : UserStartNodeTreeControllerBase(), + userStartNodeEntitiesService, + dataTypeService, + appCaches, + backofficeSecurityAccessor, + mediaPresentationFactory) + { + } + + [ActivatorUtilitiesConstructor] + public MediaTreeControllerBase( + IEntityService entityService, + SignProviderCollection signProviders, + IUserStartNodeEntitiesService userStartNodeEntitiesService, + IDataTypeService dataTypeService, + AppCaches appCaches, + IBackOfficeSecurityAccessor backofficeSecurityAccessor, + IMediaPresentationFactory mediaPresentationFactory) + : base(entityService, signProviders, userStartNodeEntitiesService, dataTypeService) { _appCaches = appCaches; _backofficeSecurityAccessor = backofficeSecurityAccessor; diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Media/Tree/RootMediaTreeController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Media/Tree/RootMediaTreeController.cs index d2ffa8a4bbca..2cadb4c4fce1 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Media/Tree/RootMediaTreeController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Media/Tree/RootMediaTreeController.cs @@ -1,9 +1,11 @@ -using Asp.Versioning; +using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Api.Common.ViewModels.Pagination; using Umbraco.Cms.Api.Management.Factories; using Umbraco.Cms.Api.Management.Services.Entities; +using Umbraco.Cms.Api.Management.Services.Signs; using Umbraco.Cms.Api.Management.ViewModels.Tree; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Security; @@ -14,6 +16,7 @@ namespace Umbraco.Cms.Api.Management.Controllers.Media.Tree; [ApiVersion("1.0")] public class RootMediaTreeController : MediaTreeControllerBase { + [Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 18.")] public RootMediaTreeController( IEntityService entityService, IUserStartNodeEntitiesService userStartNodeEntitiesService, @@ -25,6 +28,19 @@ public RootMediaTreeController( { } + [ActivatorUtilitiesConstructor] + public RootMediaTreeController( + IEntityService entityService, + SignProviderCollection signProviders, + IUserStartNodeEntitiesService userStartNodeEntitiesService, + IDataTypeService dataTypeService, + AppCaches appCaches, + IBackOfficeSecurityAccessor backofficeSecurityAccessor, + IMediaPresentationFactory mediaPresentationFactory) + : base(entityService, signProviders, userStartNodeEntitiesService, dataTypeService, appCaches, backofficeSecurityAccessor, mediaPresentationFactory) + { + } + [HttpGet("root")] [MapToApiVersion("1.0")] [ProducesResponseType(typeof(PagedViewModel), StatusCodes.Status200OK)] diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Media/Tree/SiblingsMediaTreeController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Media/Tree/SiblingsMediaTreeController.cs index f5708fa63853..9b663cbcbb10 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Media/Tree/SiblingsMediaTreeController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Media/Tree/SiblingsMediaTreeController.cs @@ -1,7 +1,9 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Api.Management.Factories; using Umbraco.Cms.Api.Management.Services.Entities; +using Umbraco.Cms.Api.Management.Services.Signs; using Umbraco.Cms.Api.Management.ViewModels.Tree; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Security; @@ -11,6 +13,7 @@ namespace Umbraco.Cms.Api.Management.Controllers.Media.Tree; public class SiblingsMediaTreeController : MediaTreeControllerBase { + [Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 18.")] public SiblingsMediaTreeController( IEntityService entityService, IUserStartNodeEntitiesService userStartNodeEntitiesService, @@ -22,6 +25,19 @@ public SiblingsMediaTreeController( { } + [ActivatorUtilitiesConstructor] + public SiblingsMediaTreeController( + IEntityService entityService, + SignProviderCollection signProviders, + IUserStartNodeEntitiesService userStartNodeEntitiesService, + IDataTypeService dataTypeService, + AppCaches appCaches, + IBackOfficeSecurityAccessor backofficeSecurityAccessor, + IMediaPresentationFactory mediaPresentationFactory) + : base(entityService, signProviders, userStartNodeEntitiesService, dataTypeService, appCaches, backofficeSecurityAccessor, mediaPresentationFactory) + { + } + [HttpGet("siblings")] [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] public Task>> Siblings(CancellationToken cancellationToken, Guid target, int before, int after) diff --git a/src/Umbraco.Cms.Api.Management/Controllers/MediaType/Tree/AncestorsMediaTypeTreeController.cs b/src/Umbraco.Cms.Api.Management/Controllers/MediaType/Tree/AncestorsMediaTypeTreeController.cs index ff90cea5293e..eeb3c73c5e97 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/MediaType/Tree/AncestorsMediaTypeTreeController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/MediaType/Tree/AncestorsMediaTypeTreeController.cs @@ -1,6 +1,8 @@ -using Asp.Versioning; +using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; +using Umbraco.Cms.Api.Management.Services.Signs; using Umbraco.Cms.Api.Management.ViewModels.Tree; using Umbraco.Cms.Core.Services; @@ -9,11 +11,18 @@ namespace Umbraco.Cms.Api.Management.Controllers.MediaType.Tree; [ApiVersion("1.0")] public class AncestorsMediaTypeTreeController : MediaTypeTreeControllerBase { + [Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 18.")] public AncestorsMediaTypeTreeController(IEntityService entityService, IMediaTypeService mediaTypeService) : base(entityService, mediaTypeService) { } + [ActivatorUtilitiesConstructor] + public AncestorsMediaTypeTreeController(IEntityService entityService, SignProviderCollection signProviders, IMediaTypeService mediaTypeService) + : base(entityService, signProviders, mediaTypeService) + { + } + [HttpGet("ancestors")] [MapToApiVersion("1.0")] [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] diff --git a/src/Umbraco.Cms.Api.Management/Controllers/MediaType/Tree/ChildrenMediaTypeTreeController.cs b/src/Umbraco.Cms.Api.Management/Controllers/MediaType/Tree/ChildrenMediaTypeTreeController.cs index 3662360c8954..545c5178e307 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/MediaType/Tree/ChildrenMediaTypeTreeController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/MediaType/Tree/ChildrenMediaTypeTreeController.cs @@ -1,7 +1,9 @@ -using Asp.Versioning; +using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Api.Common.ViewModels.Pagination; +using Umbraco.Cms.Api.Management.Services.Signs; using Umbraco.Cms.Api.Management.ViewModels.Tree; using Umbraco.Cms.Core.Services; @@ -10,11 +12,18 @@ namespace Umbraco.Cms.Api.Management.Controllers.MediaType.Tree; [ApiVersion("1.0")] public class ChildrenMediaTypeTreeController : MediaTypeTreeControllerBase { + [Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 18.")] public ChildrenMediaTypeTreeController(IEntityService entityService, IMediaTypeService mediaTypeService) : base(entityService, mediaTypeService) { } + [ActivatorUtilitiesConstructor] + public ChildrenMediaTypeTreeController(IEntityService entityService, SignProviderCollection signProviders, IMediaTypeService mediaTypeService) + : base(entityService, signProviders, mediaTypeService) + { + } + [HttpGet("children")] [MapToApiVersion("1.0")] [ProducesResponseType(typeof(PagedViewModel), StatusCodes.Status200OK)] diff --git a/src/Umbraco.Cms.Api.Management/Controllers/MediaType/Tree/MediaTypeTreeControllerBase.cs b/src/Umbraco.Cms.Api.Management/Controllers/MediaType/Tree/MediaTypeTreeControllerBase.cs index a3cc202b2845..9731a7fb4a85 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/MediaType/Tree/MediaTypeTreeControllerBase.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/MediaType/Tree/MediaTypeTreeControllerBase.cs @@ -1,9 +1,12 @@ -using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Api.Management.Controllers.Tree; using Umbraco.Cms.Api.Management.Routing; +using Umbraco.Cms.Api.Management.Services.Signs; using Umbraco.Cms.Api.Management.ViewModels.Tree; using Umbraco.Cms.Core; +using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Entities; using Umbraco.Cms.Core.Services; @@ -19,8 +22,17 @@ public class MediaTypeTreeControllerBase : FolderTreeControllerBase + : this( + entityService, + StaticServiceProvider.Instance.GetRequiredService(), + mediaTypeService) + { + } + + public MediaTypeTreeControllerBase(IEntityService entityService, SignProviderCollection signProviders, IMediaTypeService mediaTypeService) + : base(entityService, signProviders) => _mediaTypeService = mediaTypeService; protected override UmbracoObjectTypes ItemObjectType => UmbracoObjectTypes.MediaType; diff --git a/src/Umbraco.Cms.Api.Management/Controllers/MediaType/Tree/RootMediaTypeTreeController.cs b/src/Umbraco.Cms.Api.Management/Controllers/MediaType/Tree/RootMediaTypeTreeController.cs index 7c0aca479111..3def7719cf6d 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/MediaType/Tree/RootMediaTypeTreeController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/MediaType/Tree/RootMediaTypeTreeController.cs @@ -1,7 +1,9 @@ -using Asp.Versioning; +using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Api.Common.ViewModels.Pagination; +using Umbraco.Cms.Api.Management.Services.Signs; using Umbraco.Cms.Api.Management.ViewModels.Tree; using Umbraco.Cms.Core.Services; @@ -10,11 +12,18 @@ namespace Umbraco.Cms.Api.Management.Controllers.MediaType.Tree; [ApiVersion("1.0")] public class RootMediaTypeTreeController : MediaTypeTreeControllerBase { + [Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 18.")] public RootMediaTypeTreeController(IEntityService entityService, IMediaTypeService mediaTypeService) : base(entityService, mediaTypeService) { } + [ActivatorUtilitiesConstructor] + public RootMediaTypeTreeController(IEntityService entityService, SignProviderCollection signProviders, IMediaTypeService mediaTypeService) + : base(entityService, signProviders, mediaTypeService) + { + } + [HttpGet("root")] [MapToApiVersion("1.0")] [ProducesResponseType(typeof(PagedViewModel), StatusCodes.Status200OK)] diff --git a/src/Umbraco.Cms.Api.Management/Controllers/MediaType/Tree/SiblingsMediaTypeTreeController.cs b/src/Umbraco.Cms.Api.Management/Controllers/MediaType/Tree/SiblingsMediaTypeTreeController.cs index 1482788b57f6..e4ecba86445a 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/MediaType/Tree/SiblingsMediaTypeTreeController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/MediaType/Tree/SiblingsMediaTypeTreeController.cs @@ -1,5 +1,7 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; +using Umbraco.Cms.Api.Management.Services.Signs; using Umbraco.Cms.Api.Management.ViewModels.Tree; using Umbraco.Cms.Core.Services; @@ -7,11 +9,18 @@ namespace Umbraco.Cms.Api.Management.Controllers.MediaType.Tree; public class SiblingsMediaTypeTreeController : MediaTypeTreeControllerBase { + [Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 18.")] public SiblingsMediaTypeTreeController(IEntityService entityService, IMediaTypeService mediaTypeService) : base(entityService, mediaTypeService) { } + [ActivatorUtilitiesConstructor] + public SiblingsMediaTypeTreeController(IEntityService entityService, SignProviderCollection signProviders, IMediaTypeService mediaTypeService) + : base(entityService, signProviders, mediaTypeService) + { + } + [HttpGet("siblings")] [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] public Task>> Siblings(CancellationToken cancellationToken, Guid target, int before, int after) diff --git a/src/Umbraco.Cms.Api.Management/Controllers/MemberGroup/Tree/MemberGroupTreeControllerBase.cs b/src/Umbraco.Cms.Api.Management/Controllers/MemberGroup/Tree/MemberGroupTreeControllerBase.cs index 2066e7976819..90ef8d12d9a1 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/MemberGroup/Tree/MemberGroupTreeControllerBase.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/MemberGroup/Tree/MemberGroupTreeControllerBase.cs @@ -1,7 +1,8 @@ -using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Umbraco.Cms.Api.Management.Controllers.Tree; using Umbraco.Cms.Api.Management.Routing; +using Umbraco.Cms.Api.Management.Services.Signs; using Umbraco.Cms.Api.Management.ViewModels.Tree; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Models; @@ -15,10 +16,16 @@ namespace Umbraco.Cms.Api.Management.Controllers.MemberGroup.Tree; [Authorize(Policy = AuthorizationPolicies.TreeAccessMemberGroups)] public class MemberGroupTreeControllerBase : NamedEntityTreeControllerBase { + [Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 18.")] public MemberGroupTreeControllerBase(IEntityService entityService) : base(entityService) { } + public MemberGroupTreeControllerBase(IEntityService entityService, SignProviderCollection signProviders) + : base(entityService, signProviders) + { + } + protected override UmbracoObjectTypes ItemObjectType => UmbracoObjectTypes.MemberGroup; } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/MemberGroup/Tree/RootMemberGroupTreeController.cs b/src/Umbraco.Cms.Api.Management/Controllers/MemberGroup/Tree/RootMemberGroupTreeController.cs index 3328df551435..08f83192050e 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/MemberGroup/Tree/RootMemberGroupTreeController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/MemberGroup/Tree/RootMemberGroupTreeController.cs @@ -1,20 +1,29 @@ -using Asp.Versioning; +using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Api.Common.ViewModels.Pagination; using Umbraco.Cms.Api.Management.ViewModels.Tree; +using Umbraco.Cms.Api.Management.Services.Signs; +using Microsoft.Extensions.DependencyInjection; namespace Umbraco.Cms.Api.Management.Controllers.MemberGroup.Tree; [ApiVersion("1.0")] public class RootMemberGroupTreeController : MemberGroupTreeControllerBase { + [Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 18.")] public RootMemberGroupTreeController(IEntityService entityService) : base(entityService) { } + [ActivatorUtilitiesConstructor] + public RootMemberGroupTreeController(IEntityService entityService, SignProviderCollection signProviders) + : base(entityService, signProviders) + { + } + [HttpGet("root")] [MapToApiVersion("1.0")] [ProducesResponseType(typeof(PagedViewModel), StatusCodes.Status200OK)] diff --git a/src/Umbraco.Cms.Api.Management/Controllers/MemberType/Tree/MemberTypeTreeControllerBase.cs b/src/Umbraco.Cms.Api.Management/Controllers/MemberType/Tree/MemberTypeTreeControllerBase.cs index 9fc8111b6c27..a612b32d87d4 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/MemberType/Tree/MemberTypeTreeControllerBase.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/MemberType/Tree/MemberTypeTreeControllerBase.cs @@ -1,9 +1,12 @@ -using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Api.Management.Controllers.Tree; using Umbraco.Cms.Api.Management.Routing; +using Umbraco.Cms.Api.Management.Services.Signs; using Umbraco.Cms.Api.Management.ViewModels.Tree; using Umbraco.Cms.Core; +using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Entities; using Umbraco.Cms.Core.Services; @@ -18,10 +21,20 @@ public class MemberTypeTreeControllerBase : NamedEntityTreeControllerBase + : this( + entityService, + StaticServiceProvider.Instance.GetRequiredService(), + memberTypeService) + { + } + + public MemberTypeTreeControllerBase(IEntityService entityService, SignProviderCollection signProviders, IMemberTypeService memberTypeService) + : base(entityService, signProviders) => _memberTypeService = memberTypeService; + protected override UmbracoObjectTypes ItemObjectType => UmbracoObjectTypes.MemberType; protected override MemberTypeTreeItemResponseModel[] MapTreeItemViewModels(Guid? parentKey, IEntitySlim[] entities) diff --git a/src/Umbraco.Cms.Api.Management/Controllers/MemberType/Tree/RootMemberTypeTreeController.cs b/src/Umbraco.Cms.Api.Management/Controllers/MemberType/Tree/RootMemberTypeTreeController.cs index eeae87da51a2..bc1506c3b410 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/MemberType/Tree/RootMemberTypeTreeController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/MemberType/Tree/RootMemberTypeTreeController.cs @@ -1,20 +1,29 @@ -using Asp.Versioning; +using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Api.Common.ViewModels.Pagination; using Umbraco.Cms.Api.Management.ViewModels.Tree; +using Microsoft.Extensions.DependencyInjection; +using Umbraco.Cms.Api.Management.Services.Signs; namespace Umbraco.Cms.Api.Management.Controllers.MemberType.Tree; [ApiVersion("1.0")] public class RootMemberTypeTreeController : MemberTypeTreeControllerBase { + [Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 18.")] public RootMemberTypeTreeController(IEntityService entityService, IMemberTypeService memberTypeService) : base(entityService, memberTypeService) { } + [ActivatorUtilitiesConstructor] + public RootMemberTypeTreeController(IEntityService entityService, SignProviderCollection signProviders, IMemberTypeService memberTypeService) + : base(entityService, signProviders, memberTypeService) + { + } + [HttpGet("root")] [MapToApiVersion("1.0")] [ProducesResponseType(typeof(PagedViewModel), StatusCodes.Status200OK)] diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Template/Tree/AncestorsTemplateTreeController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Template/Tree/AncestorsTemplateTreeController.cs index 3d5f0d3ff516..943aeba37f59 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Template/Tree/AncestorsTemplateTreeController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Template/Tree/AncestorsTemplateTreeController.cs @@ -1,6 +1,8 @@ -using Asp.Versioning; +using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; +using Umbraco.Cms.Api.Management.Services.Signs; using Umbraco.Cms.Api.Management.ViewModels.Tree; using Umbraco.Cms.Core.Services; @@ -9,11 +11,18 @@ namespace Umbraco.Cms.Api.Management.Controllers.Template.Tree; [ApiVersion("1.0")] public class AncestorsTemplateTreeController : TemplateTreeControllerBase { + [Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 18.")] public AncestorsTemplateTreeController(IEntityService entityService) : base(entityService) { } + [ActivatorUtilitiesConstructor] + public AncestorsTemplateTreeController(IEntityService entityService, SignProviderCollection signProviders) + : base(entityService, signProviders) + { + } + [HttpGet("ancestors")] [MapToApiVersion("1.0")] [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Template/Tree/ChildrenTemplateTreeController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Template/Tree/ChildrenTemplateTreeController.cs index b5d487b0ed6a..edf8f5631828 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Template/Tree/ChildrenTemplateTreeController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Template/Tree/ChildrenTemplateTreeController.cs @@ -1,20 +1,29 @@ -using Asp.Versioning; +using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Api.Common.ViewModels.Pagination; using Umbraco.Cms.Api.Management.ViewModels.Tree; +using Microsoft.Extensions.DependencyInjection; +using Umbraco.Cms.Api.Management.Services.Signs; namespace Umbraco.Cms.Api.Management.Controllers.Template.Tree; [ApiVersion("1.0")] public class ChildrenTemplateTreeController : TemplateTreeControllerBase { + [Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 18.")] public ChildrenTemplateTreeController(IEntityService entityService) : base(entityService) { } + [ActivatorUtilitiesConstructor] + public ChildrenTemplateTreeController(IEntityService entityService, SignProviderCollection signProviders) + : base(entityService, signProviders) + { + } + [HttpGet("children")] [MapToApiVersion("1.0")] [ProducesResponseType(typeof(PagedViewModel), StatusCodes.Status200OK)] diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Template/Tree/RootTemplateTreeController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Template/Tree/RootTemplateTreeController.cs index 4de5731c40a5..8f95736d1e77 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Template/Tree/RootTemplateTreeController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Template/Tree/RootTemplateTreeController.cs @@ -1,20 +1,29 @@ -using Asp.Versioning; +using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Api.Common.ViewModels.Pagination; using Umbraco.Cms.Api.Management.ViewModels.Tree; +using Microsoft.Extensions.DependencyInjection; +using Umbraco.Cms.Api.Management.Services.Signs; namespace Umbraco.Cms.Api.Management.Controllers.Template.Tree; [ApiVersion("1.0")] public class RootTemplateTreeController : TemplateTreeControllerBase { + [Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 18.")] public RootTemplateTreeController(IEntityService entityService) : base(entityService) { } + [ActivatorUtilitiesConstructor] + public RootTemplateTreeController(IEntityService entityService, SignProviderCollection signProviders) + : base(entityService, signProviders) + { + } + [HttpGet("root")] [MapToApiVersion("1.0")] [ProducesResponseType(typeof(PagedViewModel), StatusCodes.Status200OK)] diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Template/Tree/SiblingsTemplateTreeController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Template/Tree/SiblingsTemplateTreeController.cs index ed27092ecb12..948801c14823 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Template/Tree/SiblingsTemplateTreeController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Template/Tree/SiblingsTemplateTreeController.cs @@ -1,5 +1,7 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; +using Umbraco.Cms.Api.Management.Services.Signs; using Umbraco.Cms.Api.Management.ViewModels.Tree; using Umbraco.Cms.Core.Services; @@ -7,11 +9,19 @@ namespace Umbraco.Cms.Api.Management.Controllers.Template.Tree; public class SiblingsTemplateTreeController : TemplateTreeControllerBase { + [Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 18.")] public SiblingsTemplateTreeController(IEntityService entityService) : base(entityService) { } + [ActivatorUtilitiesConstructor] + public SiblingsTemplateTreeController(IEntityService entityService, SignProviderCollection signProviders) + : base(entityService, signProviders) + { + } + + [HttpGet("siblings")] [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] public Task>> Siblings( diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Template/Tree/TemplateTreeControllerBase.cs b/src/Umbraco.Cms.Api.Management/Controllers/Template/Tree/TemplateTreeControllerBase.cs index 2ab19a1ee5fa..aa65c4f7a514 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Template/Tree/TemplateTreeControllerBase.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Template/Tree/TemplateTreeControllerBase.cs @@ -1,7 +1,8 @@ -using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Umbraco.Cms.Api.Management.Controllers.Tree; using Umbraco.Cms.Api.Management.Routing; +using Umbraco.Cms.Api.Management.Services.Signs; using Umbraco.Cms.Api.Management.ViewModels.Tree; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Models; @@ -15,10 +16,16 @@ namespace Umbraco.Cms.Api.Management.Controllers.Template.Tree; [Authorize(Policy = AuthorizationPolicies.TreeAccessTemplates)] public class TemplateTreeControllerBase : NamedEntityTreeControllerBase { + [Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 18.")] public TemplateTreeControllerBase(IEntityService entityService) : base(entityService) { } + public TemplateTreeControllerBase(IEntityService entityService, SignProviderCollection signProviders) + : base(entityService, signProviders) + { + } + protected override UmbracoObjectTypes ItemObjectType => UmbracoObjectTypes.Template; } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Tree/EntityTreeControllerBase.cs b/src/Umbraco.Cms.Api.Management/Controllers/Tree/EntityTreeControllerBase.cs index 13bbe9bc2ba9..e99338f8bf86 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Tree/EntityTreeControllerBase.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Tree/EntityTreeControllerBase.cs @@ -1,8 +1,11 @@ -using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Api.Common.ViewModels.Pagination; +using Umbraco.Cms.Api.Management.Services.Signs; using Umbraco.Cms.Api.Management.ViewModels; using Umbraco.Cms.Api.Management.ViewModels.Tree; using Umbraco.Cms.Core; +using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Extensions; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Entities; @@ -13,8 +16,21 @@ namespace Umbraco.Cms.Api.Management.Controllers.Tree; public abstract class EntityTreeControllerBase : ManagementApiControllerBase where TItem : EntityTreeItemResponseModel, new() { + private readonly SignProviderCollection _signProviders; + + [Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 18.")] protected EntityTreeControllerBase(IEntityService entityService) - => EntityService = entityService; + : this( + entityService, + StaticServiceProvider.Instance.GetRequiredService()) + { + } + + protected EntityTreeControllerBase(IEntityService entityService, SignProviderCollection signProviders) + { + EntityService = entityService; + _signProviders = signProviders; + } protected IEntityService EntityService { get; } @@ -22,34 +38,38 @@ protected EntityTreeControllerBase(IEntityService entityService) protected virtual Ordering ItemOrdering => Ordering.By(nameof(Infrastructure.Persistence.Dtos.NodeDto.Text)); - protected Task>> GetRoot(int skip, int take) + protected async Task>> GetRoot(int skip, int take) { IEntitySlim[] rootEntities = GetPagedRootEntities(skip, take, out var totalItems); TItem[] treeItemViewModels = MapTreeItemViewModels(null, rootEntities); + await PopulateSigns(treeItemViewModels, rootEntities); + PagedViewModel result = PagedViewModel(treeItemViewModels, totalItems); - return Task.FromResult>>(Ok(result)); + return Ok(result); } - protected Task>> GetChildren(Guid parentId, int skip, int take) + protected async Task>> GetChildren(Guid parentId, int skip, int take) { IEntitySlim[] children = GetPagedChildEntities(parentId, skip, take, out var totalItems); TItem[] treeItemViewModels = MapTreeItemViewModels(parentId, children); + await PopulateSigns(treeItemViewModels, children); + PagedViewModel result = PagedViewModel(treeItemViewModels, totalItems); - return Task.FromResult>>(Ok(result)); + return Ok(result); } - protected Task>> GetSiblings(Guid target, int before, int after) + protected async Task>> GetSiblings(Guid target, int before, int after) { IEntitySlim[] siblings = EntityService.GetSiblings(target, ItemObjectType, before, after, ItemOrdering).ToArray(); if (siblings.Length == 0) { - return Task.FromResult>>(NotFound()); + return NotFound(); } IEntitySlim? entity = siblings.FirstOrDefault(); @@ -57,15 +77,16 @@ protected Task>> GetSiblings(Guid target, int be ? EntityService.GetKey(entity.ParentId, ItemObjectType).Result : Constants.System.RootKey; - TItem[] treeItemsViewModels = MapTreeItemViewModels(parentKey, siblings); - return Task.FromResult>>(Ok(treeItemsViewModels)); + TItem[] treeItemViewModels = MapTreeItemViewModels(parentKey, siblings); + await PopulateSigns(treeItemViewModels, siblings); + return Ok(treeItemViewModels); } protected virtual async Task>> GetAncestors(Guid descendantKey, bool includeSelf = true) { IEntitySlim[] ancestorEntities = await GetAncestorEntitiesAsync(descendantKey, includeSelf); - TItem[] result = ancestorEntities + TItem[] treeItemViewModels = ancestorEntities .Select(ancestor => { IEntitySlim? parent = ancestor.ParentId > 0 @@ -76,7 +97,9 @@ protected virtual async Task>> GetAncestors(Guid }) .ToArray(); - return Ok(result); + await PopulateSigns(treeItemViewModels, ancestorEntities); + + return Ok(treeItemViewModels); } protected virtual Task GetAncestorEntitiesAsync(Guid descendantKey, bool includeSelf) @@ -122,6 +145,14 @@ protected virtual IEntitySlim[] GetPagedChildEntities(Guid parentKey, int skip, protected virtual TItem[] MapTreeItemViewModels(Guid? parentKey, IEntitySlim[] entities) => entities.Select(entity => MapTreeItemViewModel(parentKey, entity)).ToArray(); + protected virtual async Task PopulateSigns(TItem[] treeItemViewModels, IEnumerable entities) + { + foreach (ISignProvider signProvider in _signProviders) + { + await signProvider.PopulateTreeSignsAsync(treeItemViewModels, entities); + } + } + protected virtual TItem MapTreeItemViewModel(Guid? parentKey, IEntitySlim entity) { var viewModel = new TItem diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Tree/FolderTreeControllerBase.cs b/src/Umbraco.Cms.Api.Management/Controllers/Tree/FolderTreeControllerBase.cs index a7c201372c6d..c0f84395a734 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Tree/FolderTreeControllerBase.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Tree/FolderTreeControllerBase.cs @@ -5,6 +5,9 @@ using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Extensions; using Umbraco.Extensions; +using Umbraco.Cms.Api.Management.Services.Signs; +using Umbraco.Cms.Core.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; namespace Umbraco.Cms.Api.Management.Controllers.Tree; @@ -28,9 +31,16 @@ protected override Ordering ItemOrdering } } + [Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 18.")] protected FolderTreeControllerBase(IEntityService entityService) - : base(entityService) => - // ReSharper disable once VirtualMemberCallInConstructor + : this( + entityService, + StaticServiceProvider.Instance.GetRequiredService()) + { + } + + protected FolderTreeControllerBase(IEntityService entityService, SignProviderCollection signProviders) + : base(entityService, signProviders) => _folderObjectTypeId = FolderObjectType.GetGuid(); protected abstract UmbracoObjectTypes FolderObjectType { get; } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Tree/NamedEntityTreeControllerBase.cs b/src/Umbraco.Cms.Api.Management/Controllers/Tree/NamedEntityTreeControllerBase.cs index a50c6b1cbe42..be1fc3c6ce88 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Tree/NamedEntityTreeControllerBase.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Tree/NamedEntityTreeControllerBase.cs @@ -1,4 +1,5 @@ -using Umbraco.Cms.Api.Management.ViewModels.Tree; +using Umbraco.Cms.Api.Management.Services.Signs; +using Umbraco.Cms.Api.Management.ViewModels.Tree; using Umbraco.Cms.Core.Models.Entities; using Umbraco.Cms.Core.Services; @@ -7,11 +8,17 @@ namespace Umbraco.Cms.Api.Management.Controllers.Tree; public abstract class NamedEntityTreeControllerBase : EntityTreeControllerBase where TItem : NamedEntityTreeItemResponseModel, new() { + [Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 18.")] protected NamedEntityTreeControllerBase(IEntityService entityService) : base(entityService) { } + protected NamedEntityTreeControllerBase(IEntityService entityService, SignProviderCollection signProviders) + : base(entityService, signProviders) + { + } + protected override TItem MapTreeItemViewModel(Guid? parentKey, IEntitySlim entity) { TItem item = base.MapTreeItemViewModel(parentKey, entity); diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Tree/UserStartNodeTreeControllerBase.cs b/src/Umbraco.Cms.Api.Management/Controllers/Tree/UserStartNodeTreeControllerBase.cs index 8c15708f1f04..b71aa1fe0df1 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Tree/UserStartNodeTreeControllerBase.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Tree/UserStartNodeTreeControllerBase.cs @@ -1,7 +1,10 @@ -using Umbraco.Cms.Api.Management.Models.Entities; +using Microsoft.Extensions.DependencyInjection; +using Umbraco.Cms.Api.Management.Models.Entities; using Umbraco.Cms.Api.Management.Services.Entities; +using Umbraco.Cms.Api.Management.Services.Signs; using Umbraco.Cms.Api.Management.ViewModels.Tree; using Umbraco.Cms.Core; +using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Models.Entities; using Umbraco.Cms.Core.Services; using Umbraco.Extensions; @@ -19,11 +22,25 @@ public abstract class UserStartNodeTreeControllerBase : EntityTreeControl private Dictionary _accessMap = new(); private Guid? _dataTypeKey; + [Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 18.")] protected UserStartNodeTreeControllerBase( IEntityService entityService, IUserStartNodeEntitiesService userStartNodeEntitiesService, IDataTypeService dataTypeService) - : base(entityService) + : this( + entityService, + StaticServiceProvider.Instance.GetRequiredService(), + userStartNodeEntitiesService, + dataTypeService) + { + } + + protected UserStartNodeTreeControllerBase( + IEntityService entityService, + SignProviderCollection signProviders, + IUserStartNodeEntitiesService userStartNodeEntitiesService, + IDataTypeService dataTypeService) + : base(entityService, signProviders) { _userStartNodeEntitiesService = userStartNodeEntitiesService; _dataTypeService = dataTypeService; diff --git a/src/Umbraco.Cms.Api.Management/DependencyInjection/UmbracoBuilderExtensions.cs b/src/Umbraco.Cms.Api.Management/DependencyInjection/UmbracoBuilderExtensions.cs index 48e53c97e0d3..c80f60b4f3a3 100644 --- a/src/Umbraco.Cms.Api.Management/DependencyInjection/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.Cms.Api.Management/DependencyInjection/UmbracoBuilderExtensions.cs @@ -1,4 +1,4 @@ -using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Api.Common.Configuration; using Umbraco.Cms.Api.Common.DependencyInjection; using Umbraco.Cms.Api.Management.Configuration; @@ -7,9 +7,11 @@ using Umbraco.Cms.Api.Management.Routing; using Umbraco.Cms.Api.Management.Serialization; using Umbraco.Cms.Api.Management.Services; +using Umbraco.Cms.Api.Management.Services.Signs; using Umbraco.Cms.Core; using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Services.Filters; using Umbraco.Cms.Web.Common.ApplicationBuilder; namespace Umbraco.Extensions; @@ -95,6 +97,15 @@ public static IUmbracoBuilder AddUmbracoManagementApi(this IUmbracoBuilder build }); } + builder.SignProviders(); + return builder; } + + /// + /// Gets the sign providers collection builder. + /// + /// The builder. + public static SignProviderCollectionBuilder SignProviders(this IUmbracoBuilder builder) + => builder.WithCollectionBuilder(); } diff --git a/src/Umbraco.Cms.Api.Management/Services/Signs/ISignProvider.cs b/src/Umbraco.Cms.Api.Management/Services/Signs/ISignProvider.cs new file mode 100644 index 000000000000..6849de5c5755 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Services/Signs/ISignProvider.cs @@ -0,0 +1,19 @@ +using Umbraco.Cms.Api.Management.ViewModels.Tree; +using Umbraco.Cms.Core.Models.Entities; + +namespace Umbraco.Cms.Api.Management.Services.Signs; + +/// +/// Defines operation for the provision of presentation signs for tree and collection nodes. +/// +public interface ISignProvider +{ + /// + /// Populates the provided tree item view models with signs. + /// + /// Type of tree item view model. + /// The collection of tree item view models populatw with signs. + /// The entities from which the collection of tree item view models was populated. + Task PopulateTreeSignsAsync(TItem[] treeItemViewModels, IEnumerable entities) + where TItem : EntityTreeItemResponseModel, new(); +} diff --git a/src/Umbraco.Cms.Api.Management/Services/Signs/SignProviderCollection.cs b/src/Umbraco.Cms.Api.Management/Services/Signs/SignProviderCollection.cs new file mode 100644 index 000000000000..81c50aafd55e --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Services/Signs/SignProviderCollection.cs @@ -0,0 +1,18 @@ +using Umbraco.Cms.Core.Composing; + +namespace Umbraco.Cms.Api.Management.Services.Signs; + +/// +/// Defines an ordered collection of . +/// +public class SignProviderCollection : BuilderCollectionBase +{ + /// + /// Initializes a new instance of the class. + /// + /// The collection items. + public SignProviderCollection(Func> items) + : base(items) + { + } +} diff --git a/src/Umbraco.Cms.Api.Management/Services/Signs/SignProviderCollectionBuilder.cs b/src/Umbraco.Cms.Api.Management/Services/Signs/SignProviderCollectionBuilder.cs new file mode 100644 index 000000000000..a07d67634ff6 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Services/Signs/SignProviderCollectionBuilder.cs @@ -0,0 +1,12 @@ +using Umbraco.Cms.Core.Composing; + +namespace Umbraco.Cms.Api.Management.Services.Signs; + +/// +/// Builds an ordered collection of . +/// +public class SignProviderCollectionBuilder : OrderedCollectionBuilderBase +{ + /// + protected override SignProviderCollectionBuilder This => this; +} diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/SignModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/SignModel.cs new file mode 100644 index 000000000000..3378d9ba10a9 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/ViewModels/SignModel.cs @@ -0,0 +1,8 @@ +namespace Umbraco.Cms.Api.Management.ViewModels; + +public class SignModel +{ + public required string Provider { get; set; } + + public required string Alias { get; set; } +} diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/Tree/EntityTreeItemResponseModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/Tree/EntityTreeItemResponseModel.cs index 3373daf20d35..d336fce0c5d1 100644 --- a/src/Umbraco.Cms.Api.Management/ViewModels/Tree/EntityTreeItemResponseModel.cs +++ b/src/Umbraco.Cms.Api.Management/ViewModels/Tree/EntityTreeItemResponseModel.cs @@ -1,8 +1,10 @@ -namespace Umbraco.Cms.Api.Management.ViewModels.Tree; +namespace Umbraco.Cms.Api.Management.ViewModels.Tree; public class EntityTreeItemResponseModel : TreeItemPresentationModel { public Guid Id { get; set; } public ReferenceByIdModel? Parent { get; set; } + + public IEnumerable Signs { get; set; } = []; } From 949b16367ac834a9ec49a2e76895c0e62f7da4bc Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Mon, 28 Jul 2025 12:58:40 +0200 Subject: [PATCH 02/22] Stub implementation of sign provider for documents with a scheduled publish pending. --- .../Tree/EntityTreeControllerBase.cs | 2 +- .../UmbracoBuilder.Collections.cs | 24 ++++++++++++ .../UmbracoBuilderExtensions.cs | 11 +----- .../Services/Signs/HasScheduleSignProvider.cs | 37 +++++++++++++++++++ .../Services/Signs/ISignProvider.cs | 8 ++++ .../Tree/EntityTreeItemResponseModel.cs | 8 +++- src/Umbraco.Core/Constants-System.cs | 5 +++ src/Umbraco.Core/Services/IContentService.cs | 9 +++++ 8 files changed, 92 insertions(+), 12 deletions(-) create mode 100644 src/Umbraco.Cms.Api.Management/DependencyInjection/UmbracoBuilder.Collections.cs create mode 100644 src/Umbraco.Cms.Api.Management/Services/Signs/HasScheduleSignProvider.cs diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Tree/EntityTreeControllerBase.cs b/src/Umbraco.Cms.Api.Management/Controllers/Tree/EntityTreeControllerBase.cs index e99338f8bf86..c1d266e8f961 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Tree/EntityTreeControllerBase.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Tree/EntityTreeControllerBase.cs @@ -147,7 +147,7 @@ protected virtual TItem[] MapTreeItemViewModels(Guid? parentKey, IEntitySlim[] e protected virtual async Task PopulateSigns(TItem[] treeItemViewModels, IEnumerable entities) { - foreach (ISignProvider signProvider in _signProviders) + foreach (ISignProvider signProvider in _signProviders.Where(x => x.CanProvideTreeSigns())) { await signProvider.PopulateTreeSignsAsync(treeItemViewModels, entities); } diff --git a/src/Umbraco.Cms.Api.Management/DependencyInjection/UmbracoBuilder.Collections.cs b/src/Umbraco.Cms.Api.Management/DependencyInjection/UmbracoBuilder.Collections.cs new file mode 100644 index 000000000000..ad35b799fe7c --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/DependencyInjection/UmbracoBuilder.Collections.cs @@ -0,0 +1,24 @@ +using Umbraco.Cms.Api.Management.Services.Signs; +using Umbraco.Cms.Core.DependencyInjection; + +namespace Umbraco.Extensions +{ + /// + /// Extension methods for for the Umbraco back office + /// + public static partial class UmbracoBuilderExtensions + { + internal static void AddCollectionBuilders(this IUmbracoBuilder builder) + { + builder.SignProviders() + .Append(); + } + + /// + /// Gets the sign providers collection builder. + /// + /// The builder. + public static SignProviderCollectionBuilder SignProviders(this IUmbracoBuilder builder) + => builder.WithCollectionBuilder(); + } +} diff --git a/src/Umbraco.Cms.Api.Management/DependencyInjection/UmbracoBuilderExtensions.cs b/src/Umbraco.Cms.Api.Management/DependencyInjection/UmbracoBuilderExtensions.cs index c80f60b4f3a3..a788b1fad24d 100644 --- a/src/Umbraco.Cms.Api.Management/DependencyInjection/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.Cms.Api.Management/DependencyInjection/UmbracoBuilderExtensions.cs @@ -7,11 +7,9 @@ using Umbraco.Cms.Api.Management.Routing; using Umbraco.Cms.Api.Management.Serialization; using Umbraco.Cms.Api.Management.Services; -using Umbraco.Cms.Api.Management.Services.Signs; using Umbraco.Cms.Core; using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Core.Services.Filters; using Umbraco.Cms.Web.Common.ApplicationBuilder; namespace Umbraco.Extensions; @@ -97,15 +95,8 @@ public static IUmbracoBuilder AddUmbracoManagementApi(this IUmbracoBuilder build }); } - builder.SignProviders(); + builder.AddCollectionBuilders(); return builder; } - - /// - /// Gets the sign providers collection builder. - /// - /// The builder. - public static SignProviderCollectionBuilder SignProviders(this IUmbracoBuilder builder) - => builder.WithCollectionBuilder(); } diff --git a/src/Umbraco.Cms.Api.Management/Services/Signs/HasScheduleSignProvider.cs b/src/Umbraco.Cms.Api.Management/Services/Signs/HasScheduleSignProvider.cs new file mode 100644 index 000000000000..aae61f66d3b0 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Services/Signs/HasScheduleSignProvider.cs @@ -0,0 +1,37 @@ +using Umbraco.Cms.Api.Management.ViewModels.Tree; +using Umbraco.Cms.Core.Models.Entities; +using Umbraco.Cms.Core.Services; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Api.Management.Services.Signs; + +/// +/// Implements a that provides signs for content items that have a schedule. +/// +internal class HasScheduleSignProvider : ISignProvider +{ + private const string Alias = "ScheduledForPublish"; + + private readonly IContentService _contentService; + + /// + /// Initializes a new instance of the class. + /// + public HasScheduleSignProvider(IContentService contentService) => _contentService = contentService; + + /// + public bool CanProvideTreeSigns() => typeof(TItem) == typeof(DocumentTreeItemResponseModel); + + /// + public Task PopulateTreeSignsAsync(TItem[] treeItemViewModels, IEnumerable entities) + where TItem : EntityTreeItemResponseModel, new() + { + IEnumerable contentKeysScheduledForPublishing = _contentService.GetScheduledContentKeys(treeItemViewModels.Select(x => x.Id)); + foreach (Guid key in contentKeysScheduledForPublishing) + { + treeItemViewModels.First(x => x.Id == key).AddSign(Core.Constants.System.UmbracoSignProvider, Alias); + } + + return Task.CompletedTask; + } +} diff --git a/src/Umbraco.Cms.Api.Management/Services/Signs/ISignProvider.cs b/src/Umbraco.Cms.Api.Management/Services/Signs/ISignProvider.cs index 6849de5c5755..0d451231b761 100644 --- a/src/Umbraco.Cms.Api.Management/Services/Signs/ISignProvider.cs +++ b/src/Umbraco.Cms.Api.Management/Services/Signs/ISignProvider.cs @@ -1,3 +1,4 @@ +using System.Diagnostics.Eventing.Reader; using Umbraco.Cms.Api.Management.ViewModels.Tree; using Umbraco.Cms.Core.Models.Entities; @@ -8,6 +9,13 @@ namespace Umbraco.Cms.Api.Management.Services.Signs; /// public interface ISignProvider { + /// + /// Gets a value indicating whether this provider can provide tree signs for the specified item type. + /// + /// Type of tree item view model. + /// + bool CanProvideTreeSigns(); + /// /// Populates the provided tree item view models with signs. /// diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/Tree/EntityTreeItemResponseModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/Tree/EntityTreeItemResponseModel.cs index d336fce0c5d1..d50890d11ef8 100644 --- a/src/Umbraco.Cms.Api.Management/ViewModels/Tree/EntityTreeItemResponseModel.cs +++ b/src/Umbraco.Cms.Api.Management/ViewModels/Tree/EntityTreeItemResponseModel.cs @@ -2,9 +2,15 @@ namespace Umbraco.Cms.Api.Management.ViewModels.Tree; public class EntityTreeItemResponseModel : TreeItemPresentationModel { + private readonly List _signs = []; + public Guid Id { get; set; } public ReferenceByIdModel? Parent { get; set; } - public IEnumerable Signs { get; set; } = []; + public IEnumerable Signs => _signs.AsEnumerable(); + + public void AddSign(string provider, string alias) => _signs.Add(new SignModel { Provider = provider, Alias = alias }); + + public void RemoveSign(string provider, string alias) => _signs.RemoveAll(x => x.Provider == provider && x.Alias == alias); } diff --git a/src/Umbraco.Core/Constants-System.cs b/src/Umbraco.Core/Constants-System.cs index 62f96bb9cb11..6efc1745747a 100644 --- a/src/Umbraco.Core/Constants-System.cs +++ b/src/Umbraco.Core/Constants-System.cs @@ -115,5 +115,10 @@ public static class System public const string DataDirectoryPlaceholder = "|DataDirectory|"; public const string InvariantCulture = "*"; + + /// + /// The provider name for Umbraco core backoffice tree and collection signs. + /// + public const string UmbracoSignProvider = "umbCore"; } } diff --git a/src/Umbraco.Core/Services/IContentService.cs b/src/Umbraco.Core/Services/IContentService.cs index a7bde2dc46a8..3686431ebf99 100644 --- a/src/Umbraco.Core/Services/IContentService.cs +++ b/src/Umbraco.Core/Services/IContentService.cs @@ -275,6 +275,15 @@ IContent CreateBlueprintFromContent(IContent blueprint, string name, int userId /// bool HasChildren(int id); + /// + /// Gets the content Ids from the provided collection of content Ids that are scheduled for publishing. + /// + /// The content keys + /// + /// The provided collection of content Ids filtered for those that are scheduled for publishing. + /// + IEnumerable GetScheduledContentKeys(IEnumerable keys) => keys.Count() > 0 ? [keys.First()] : []; + #endregion #region Save, Delete Document From ec7345f75c37fb22838d3ac00ea9ed73cb464697 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Mon, 28 Jul 2025 13:43:00 +0200 Subject: [PATCH 03/22] Complete implementation of tree sign for pending scheduled publish. --- .../Repositories/IDocumentRepository.cs | 9 ++++++++ src/Umbraco.Core/Services/ContentService.cs | 17 ++++++++++++++ src/Umbraco.Core/Services/IContentService.cs | 4 ++-- .../Implement/DocumentRepository.cs | 22 +++++++++++++++++++ 4 files changed, 50 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/IDocumentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IDocumentRepository.cs index f6ee0b692651..4bcacb14a8ba 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IDocumentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IDocumentRepository.cs @@ -50,6 +50,15 @@ public interface IDocumentRepository : IContentRepository, IReadR /// IEnumerable GetContentForRelease(DateTime date); + /// + /// Gets the content Ids from the provided collection of content Ids that are scheduled for publishing. + /// + /// The content keys. + /// + /// The provided collection of content Ids filtered for those that are scheduled for publishing. + /// + IEnumerable GetScheduledContentKeys(Guid[] keys) => []; + /// /// Get the count of published items /// diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs index 0c3b61a1c368..016d4a83caf5 100644 --- a/src/Umbraco.Core/Services/ContentService.cs +++ b/src/Umbraco.Core/Services/ContentService.cs @@ -1007,6 +1007,23 @@ public IEnumerable GetPagedContentInRecycleBin(long pageIndex, int pag /// True if the content has any children otherwise False public bool HasChildren(int id) => CountChildren(id) > 0; + + /// + public IEnumerable GetScheduledContentKeys(IEnumerable keys) + { + Guid[] idsA = keys.ToArray(); + if (idsA.Length == 0) + { + return Enumerable.Empty(); + } + + using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) + { + scope.ReadLock(Constants.Locks.ContentTree); + return _documentRepository.GetScheduledContentKeys(idsA); + } + } + /// /// Checks if the passed in can be published based on the ancestors publish state. /// diff --git a/src/Umbraco.Core/Services/IContentService.cs b/src/Umbraco.Core/Services/IContentService.cs index 3686431ebf99..069ebeed2064 100644 --- a/src/Umbraco.Core/Services/IContentService.cs +++ b/src/Umbraco.Core/Services/IContentService.cs @@ -278,11 +278,11 @@ IContent CreateBlueprintFromContent(IContent blueprint, string name, int userId /// /// Gets the content Ids from the provided collection of content Ids that are scheduled for publishing. /// - /// The content keys + /// The content keys. /// /// The provided collection of content Ids filtered for those that are scheduled for publishing. /// - IEnumerable GetScheduledContentKeys(IEnumerable keys) => keys.Count() > 0 ? [keys.First()] : []; + IEnumerable GetScheduledContentKeys(IEnumerable keys) => []; #endregion diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentRepository.cs index 687a878c83c6..9df03eb7bf3b 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentRepository.cs @@ -1,6 +1,7 @@ using System.Globalization; using Microsoft.Extensions.Logging; using NPoco; +using Org.BouncyCastle.Crypto; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Events; @@ -1671,6 +1672,27 @@ public IEnumerable GetContentForRelease(DateTime date) return MapDtosToContent(Database.Fetch(sql)); } + /// + public IEnumerable GetScheduledContentKeys(Guid[] keys) + { + var action = ContentScheduleAction.Release.ToString(); + DateTime now = DateTime.UtcNow; + + Sql sql = SqlContext.Sql(); + sql + .Select(x => x.UniqueId) + .From() + .InnerJoin().On(left => left.NodeId, right => right.NodeId) + .InnerJoin().On(left => left.NodeId, right => right.NodeId) + .WhereIn(x => x.UniqueId, keys) + .WhereIn(x => x.NodeId, Sql() + .Select(x => x.NodeId) + .From() + .Where(x => x.Action == action && x.Date >= now)); + + return Database.Fetch(sql); + } + /// public IEnumerable GetContentForExpiration(DateTime date) { From 804b959b5512750adf6804a9586ee536502263e2 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Mon, 28 Jul 2025 14:19:47 +0200 Subject: [PATCH 04/22] Added integration test for new method on IContentService. --- .../Services/ContentServiceTests.cs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/ContentServiceTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/ContentServiceTests.cs index 3d04a9fa6cf6..0271b77e8482 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/ContentServiceTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/ContentServiceTests.cs @@ -687,6 +687,25 @@ public void Can_Get_Content_For_Expiration() Assert.That(contents.Count(), Is.EqualTo(1)); } + [Test] + public void Can_Get_Scheduled_Content_Keys() + { + // Arrange + var root = ContentService.GetById(Textpage.Id); + ContentService.Publish(root!, root!.AvailableCultures.ToArray()); + var content = ContentService.GetById(Subpage.Id); + var contentSchedule = ContentScheduleCollection.CreateWithEntry(DateTime.UtcNow.AddDays(1), null); + ContentService.PersistContentSchedule(content!, contentSchedule); + ContentService.Publish(content, content.AvailableCultures.ToArray()); + + // Act + var keys = ContentService.GetScheduledContentKeys([Textpage.Key, Subpage.Key, Subpage2.Key]).ToList(); + + // Assert + Assert.AreEqual(1, keys.Count); + Assert.AreEqual(Subpage.Key, keys.First()); + } + [Test] public void Can_Get_Content_For_Release() { From 13141d72a9a4b68b2a410e81de29c3dcc3ed1f7f Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Mon, 28 Jul 2025 14:46:51 +0200 Subject: [PATCH 05/22] Added unit test for HasScheduleSignProvider. --- .../Tree/ContentTreeItemResponseModel.cs | 2 - .../Signs/HasScheduleSignProviderTests.cs | 45 +++++++++++++++++++ 2 files changed, 45 insertions(+), 2 deletions(-) create mode 100644 tests/Umbraco.Tests.UnitTests/Umbraco.Cms.Api.Management/Services/Signs/HasScheduleSignProviderTests.cs diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/Tree/ContentTreeItemResponseModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/Tree/ContentTreeItemResponseModel.cs index a18f1c8ea882..aab2af128da4 100644 --- a/src/Umbraco.Cms.Api.Management/ViewModels/Tree/ContentTreeItemResponseModel.cs +++ b/src/Umbraco.Cms.Api.Management/ViewModels/Tree/ContentTreeItemResponseModel.cs @@ -6,7 +6,5 @@ public abstract class ContentTreeItemResponseModel : EntityTreeItemResponseModel public bool IsTrashed { get; set; } - public Guid Id { get; set; } - public DateTimeOffset CreateDate { get; set; } } diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Cms.Api.Management/Services/Signs/HasScheduleSignProviderTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Cms.Api.Management/Services/Signs/HasScheduleSignProviderTests.cs new file mode 100644 index 000000000000..14a742ff713c --- /dev/null +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Cms.Api.Management/Services/Signs/HasScheduleSignProviderTests.cs @@ -0,0 +1,45 @@ +using Moq; +using NUnit.Framework; +using Umbraco.Cms.Api.Management.Services.Signs; +using Umbraco.Cms.Api.Management.ViewModels.Tree; +using Umbraco.Cms.Core.Models.Entities; +using Umbraco.Cms.Core.Services; + +namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Cms.Api.Management.Services.Signs; + +[TestFixture] +internal class HasScheduleSignProviderTests +{ + [Test] + public async Task HasScheduleSignProvider_Should_Populate_Signs() + { + var entities = new List + { + new() { Key = Guid.NewGuid(), Name = "Item 1" }, + new() { Key = Guid.NewGuid(), Name = "Item 2" }, + }; + + var contentServiceMock = new Mock(); + contentServiceMock + .Setup(x => x.GetScheduledContentKeys(It.IsAny>())) + .Returns([entities[1].Key]); + var sut = new HasScheduleSignProvider(contentServiceMock.Object); + + Assert.IsTrue(sut.CanProvideTreeSigns()); + + var viewModels = new List + { + new() { Id = entities[0].Key }, + new() { Id = entities[1].Key }, + }; + + await sut.PopulateTreeSignsAsync(viewModels.ToArray(), entities); + + Assert.AreEqual(viewModels[0].Signs.Count(), 0); + Assert.AreEqual(viewModels[1].Signs.Count(), 1); + + var signModel = viewModels[1].Signs.First(); + Assert.AreEqual(global::Umbraco.Cms.Core.Constants.System.UmbracoSignProvider, signModel.Provider); + Assert.AreEqual("ScheduledForPublish", signModel.Alias); + } +} From c0b48b702396f3d87f8e9cc808222dc764052e38 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Tue, 29 Jul 2025 07:03:29 +0200 Subject: [PATCH 06/22] Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../Services/Signs/HasScheduleSignProvider.cs | 2 +- src/Umbraco.Cms.Api.Management/Services/Signs/ISignProvider.cs | 3 +-- .../Persistence/Repositories/Implement/DocumentRepository.cs | 1 - 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Cms.Api.Management/Services/Signs/HasScheduleSignProvider.cs b/src/Umbraco.Cms.Api.Management/Services/Signs/HasScheduleSignProvider.cs index aae61f66d3b0..0730139b99c8 100644 --- a/src/Umbraco.Cms.Api.Management/Services/Signs/HasScheduleSignProvider.cs +++ b/src/Umbraco.Cms.Api.Management/Services/Signs/HasScheduleSignProvider.cs @@ -6,7 +6,7 @@ namespace Umbraco.Cms.Api.Management.Services.Signs; /// -/// Implements a that provides signs for content items that have a schedule. +/// Implements a that provides signs for content items that have a schedule. /// internal class HasScheduleSignProvider : ISignProvider { diff --git a/src/Umbraco.Cms.Api.Management/Services/Signs/ISignProvider.cs b/src/Umbraco.Cms.Api.Management/Services/Signs/ISignProvider.cs index 0d451231b761..805a079fcb8a 100644 --- a/src/Umbraco.Cms.Api.Management/Services/Signs/ISignProvider.cs +++ b/src/Umbraco.Cms.Api.Management/Services/Signs/ISignProvider.cs @@ -1,4 +1,3 @@ -using System.Diagnostics.Eventing.Reader; using Umbraco.Cms.Api.Management.ViewModels.Tree; using Umbraco.Cms.Core.Models.Entities; @@ -20,7 +19,7 @@ public interface ISignProvider /// Populates the provided tree item view models with signs. /// /// Type of tree item view model. - /// The collection of tree item view models populatw with signs. + /// The collection of tree item view models populated with signs. /// The entities from which the collection of tree item view models was populated. Task PopulateTreeSignsAsync(TItem[] treeItemViewModels, IEnumerable entities) where TItem : EntityTreeItemResponseModel, new(); diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentRepository.cs index 9df03eb7bf3b..1279d621860b 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentRepository.cs @@ -1,7 +1,6 @@ using System.Globalization; using Microsoft.Extensions.Logging; using NPoco; -using Org.BouncyCastle.Crypto; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Events; From 54bb6aa80f8920941cb009e63397d74da0e83935 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Tue, 29 Jul 2025 08:34:57 +0200 Subject: [PATCH 07/22] Tidied usings and clarified method header comments. --- .../Tree/ChildrenDictionaryTreeController.cs | 8 ++++---- .../Dictionary/Tree/RootDictionaryTreeController.cs | 8 ++++---- .../Document/Tree/ChildrenDocumentTreeController.cs | 12 ++++++------ .../Document/Tree/RootDocumentTreeController.cs | 12 ++++++------ .../Tree/ChildrenDocumentTypeTreeController.cs | 6 +++--- .../Tree/RootDocumentTypeTreeController.cs | 6 +++--- .../MemberType/Tree/RootMemberTypeTreeController.cs | 6 +++--- .../Template/Tree/ChildrenTemplateTreeController.cs | 6 +++--- .../Template/Tree/RootTemplateTreeController.cs | 6 +++--- .../Controllers/Tree/FolderTreeControllerBase.cs | 8 ++++---- .../Services/Signs/HasScheduleSignProvider.cs | 2 +- .../Persistence/Repositories/IDocumentRepository.cs | 4 ++-- src/Umbraco.Core/Services/IContentService.cs | 4 ++-- 13 files changed, 44 insertions(+), 44 deletions(-) diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Dictionary/Tree/ChildrenDictionaryTreeController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Dictionary/Tree/ChildrenDictionaryTreeController.cs index 7c98340c0a3e..39c135be2bfe 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Dictionary/Tree/ChildrenDictionaryTreeController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Dictionary/Tree/ChildrenDictionaryTreeController.cs @@ -1,12 +1,12 @@ using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Api.Common.ViewModels.Pagination; -using Umbraco.Cms.Api.Management.ViewModels.Tree; using Microsoft.Extensions.DependencyInjection; +using Umbraco.Cms.Api.Common.ViewModels.Pagination; using Umbraco.Cms.Api.Management.Services.Signs; +using Umbraco.Cms.Api.Management.ViewModels.Tree; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Services; namespace Umbraco.Cms.Api.Management.Controllers.Dictionary.Tree; diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Dictionary/Tree/RootDictionaryTreeController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Dictionary/Tree/RootDictionaryTreeController.cs index 9f44b08a2294..7bac1e66a811 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Dictionary/Tree/RootDictionaryTreeController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Dictionary/Tree/RootDictionaryTreeController.cs @@ -1,12 +1,12 @@ using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Api.Common.ViewModels.Pagination; -using Umbraco.Cms.Api.Management.ViewModels.Tree; using Microsoft.Extensions.DependencyInjection; +using Umbraco.Cms.Api.Common.ViewModels.Pagination; using Umbraco.Cms.Api.Management.Services.Signs; +using Umbraco.Cms.Api.Management.ViewModels.Tree; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Services; namespace Umbraco.Cms.Api.Management.Controllers.Dictionary.Tree; diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Document/Tree/ChildrenDocumentTreeController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Document/Tree/ChildrenDocumentTreeController.cs index 351a0dc9e7c3..1c749f6d15e4 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Document/Tree/ChildrenDocumentTreeController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Document/Tree/ChildrenDocumentTreeController.cs @@ -1,15 +1,15 @@ using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using Umbraco.Cms.Core.Cache; -using Umbraco.Cms.Core.Security; -using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Api.Management.Services.Entities; +using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Api.Common.ViewModels.Pagination; using Umbraco.Cms.Api.Management.Factories; -using Umbraco.Cms.Api.Management.ViewModels.Tree; -using Microsoft.Extensions.DependencyInjection; +using Umbraco.Cms.Api.Management.Services.Entities; using Umbraco.Cms.Api.Management.Services.Signs; +using Umbraco.Cms.Api.Management.ViewModels.Tree; +using Umbraco.Cms.Core.Cache; +using Umbraco.Cms.Core.Security; +using Umbraco.Cms.Core.Services; namespace Umbraco.Cms.Api.Management.Controllers.Document.Tree; diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Document/Tree/RootDocumentTreeController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Document/Tree/RootDocumentTreeController.cs index 6e9f0683334d..822679e5813a 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Document/Tree/RootDocumentTreeController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Document/Tree/RootDocumentTreeController.cs @@ -1,15 +1,15 @@ using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using Umbraco.Cms.Core.Cache; -using Umbraco.Cms.Core.Security; -using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Api.Management.Services.Entities; +using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Api.Common.ViewModels.Pagination; using Umbraco.Cms.Api.Management.Factories; -using Umbraco.Cms.Api.Management.ViewModels.Tree; -using Microsoft.Extensions.DependencyInjection; +using Umbraco.Cms.Api.Management.Services.Entities; using Umbraco.Cms.Api.Management.Services.Signs; +using Umbraco.Cms.Api.Management.ViewModels.Tree; +using Umbraco.Cms.Core.Cache; +using Umbraco.Cms.Core.Security; +using Umbraco.Cms.Core.Services; namespace Umbraco.Cms.Api.Management.Controllers.Document.Tree; diff --git a/src/Umbraco.Cms.Api.Management/Controllers/DocumentType/Tree/ChildrenDocumentTypeTreeController.cs b/src/Umbraco.Cms.Api.Management/Controllers/DocumentType/Tree/ChildrenDocumentTypeTreeController.cs index 1805c5096162..9127a359d306 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/DocumentType/Tree/ChildrenDocumentTypeTreeController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/DocumentType/Tree/ChildrenDocumentTypeTreeController.cs @@ -1,11 +1,11 @@ using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Api.Common.ViewModels.Pagination; -using Umbraco.Cms.Api.Management.ViewModels.Tree; using Microsoft.Extensions.DependencyInjection; +using Umbraco.Cms.Api.Common.ViewModels.Pagination; using Umbraco.Cms.Api.Management.Services.Signs; +using Umbraco.Cms.Api.Management.ViewModels.Tree; +using Umbraco.Cms.Core.Services; namespace Umbraco.Cms.Api.Management.Controllers.DocumentType.Tree; diff --git a/src/Umbraco.Cms.Api.Management/Controllers/DocumentType/Tree/RootDocumentTypeTreeController.cs b/src/Umbraco.Cms.Api.Management/Controllers/DocumentType/Tree/RootDocumentTypeTreeController.cs index 3a69c8a592f5..b581b7bbb459 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/DocumentType/Tree/RootDocumentTypeTreeController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/DocumentType/Tree/RootDocumentTypeTreeController.cs @@ -1,11 +1,11 @@ using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Api.Common.ViewModels.Pagination; -using Umbraco.Cms.Api.Management.ViewModels.Tree; using Microsoft.Extensions.DependencyInjection; +using Umbraco.Cms.Api.Common.ViewModels.Pagination; using Umbraco.Cms.Api.Management.Services.Signs; +using Umbraco.Cms.Api.Management.ViewModels.Tree; +using Umbraco.Cms.Core.Services; namespace Umbraco.Cms.Api.Management.Controllers.DocumentType.Tree; diff --git a/src/Umbraco.Cms.Api.Management/Controllers/MemberType/Tree/RootMemberTypeTreeController.cs b/src/Umbraco.Cms.Api.Management/Controllers/MemberType/Tree/RootMemberTypeTreeController.cs index bc1506c3b410..4ce9248ecc68 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/MemberType/Tree/RootMemberTypeTreeController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/MemberType/Tree/RootMemberTypeTreeController.cs @@ -1,11 +1,11 @@ using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Api.Common.ViewModels.Pagination; -using Umbraco.Cms.Api.Management.ViewModels.Tree; using Microsoft.Extensions.DependencyInjection; +using Umbraco.Cms.Api.Common.ViewModels.Pagination; using Umbraco.Cms.Api.Management.Services.Signs; +using Umbraco.Cms.Api.Management.ViewModels.Tree; +using Umbraco.Cms.Core.Services; namespace Umbraco.Cms.Api.Management.Controllers.MemberType.Tree; diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Template/Tree/ChildrenTemplateTreeController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Template/Tree/ChildrenTemplateTreeController.cs index edf8f5631828..030be7d062b2 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Template/Tree/ChildrenTemplateTreeController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Template/Tree/ChildrenTemplateTreeController.cs @@ -1,11 +1,11 @@ using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Api.Common.ViewModels.Pagination; -using Umbraco.Cms.Api.Management.ViewModels.Tree; using Microsoft.Extensions.DependencyInjection; +using Umbraco.Cms.Api.Common.ViewModels.Pagination; using Umbraco.Cms.Api.Management.Services.Signs; +using Umbraco.Cms.Api.Management.ViewModels.Tree; +using Umbraco.Cms.Core.Services; namespace Umbraco.Cms.Api.Management.Controllers.Template.Tree; diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Template/Tree/RootTemplateTreeController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Template/Tree/RootTemplateTreeController.cs index 8f95736d1e77..871fdcf3682f 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Template/Tree/RootTemplateTreeController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Template/Tree/RootTemplateTreeController.cs @@ -1,11 +1,11 @@ using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Api.Common.ViewModels.Pagination; -using Umbraco.Cms.Api.Management.ViewModels.Tree; using Microsoft.Extensions.DependencyInjection; +using Umbraco.Cms.Api.Common.ViewModels.Pagination; using Umbraco.Cms.Api.Management.Services.Signs; +using Umbraco.Cms.Api.Management.ViewModels.Tree; +using Umbraco.Cms.Core.Services; namespace Umbraco.Cms.Api.Management.Controllers.Template.Tree; diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Tree/FolderTreeControllerBase.cs b/src/Umbraco.Cms.Api.Management/Controllers/Tree/FolderTreeControllerBase.cs index c0f84395a734..b2bd63c84635 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Tree/FolderTreeControllerBase.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Tree/FolderTreeControllerBase.cs @@ -1,13 +1,13 @@ +using Microsoft.Extensions.DependencyInjection; +using Umbraco.Cms.Api.Management.Services.Signs; using Umbraco.Cms.Api.Management.ViewModels.Tree; using Umbraco.Cms.Core; +using Umbraco.Cms.Core.DependencyInjection; +using Umbraco.Cms.Core.Extensions; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Entities; using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Core.Extensions; using Umbraco.Extensions; -using Umbraco.Cms.Api.Management.Services.Signs; -using Umbraco.Cms.Core.DependencyInjection; -using Microsoft.Extensions.DependencyInjection; namespace Umbraco.Cms.Api.Management.Controllers.Tree; diff --git a/src/Umbraco.Cms.Api.Management/Services/Signs/HasScheduleSignProvider.cs b/src/Umbraco.Cms.Api.Management/Services/Signs/HasScheduleSignProvider.cs index aae61f66d3b0..6f5aeb7e017c 100644 --- a/src/Umbraco.Cms.Api.Management/Services/Signs/HasScheduleSignProvider.cs +++ b/src/Umbraco.Cms.Api.Management/Services/Signs/HasScheduleSignProvider.cs @@ -6,7 +6,7 @@ namespace Umbraco.Cms.Api.Management.Services.Signs; /// -/// Implements a that provides signs for content items that have a schedule. +/// Implements a that provides signs for documents that are scheduled for publication. /// internal class HasScheduleSignProvider : ISignProvider { diff --git a/src/Umbraco.Core/Persistence/Repositories/IDocumentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IDocumentRepository.cs index 4bcacb14a8ba..9ff75c2b3d11 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IDocumentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IDocumentRepository.cs @@ -51,11 +51,11 @@ public interface IDocumentRepository : IContentRepository, IReadR IEnumerable GetContentForRelease(DateTime date); /// - /// Gets the content Ids from the provided collection of content Ids that are scheduled for publishing. + /// Gets the content keys from the provided collection of keys that are scheduled for publishing. /// /// The content keys. /// - /// The provided collection of content Ids filtered for those that are scheduled for publishing. + /// The provided collection of content keys filtered for those that are scheduled for publishing. /// IEnumerable GetScheduledContentKeys(Guid[] keys) => []; diff --git a/src/Umbraco.Core/Services/IContentService.cs b/src/Umbraco.Core/Services/IContentService.cs index 069ebeed2064..228d7fa6dbea 100644 --- a/src/Umbraco.Core/Services/IContentService.cs +++ b/src/Umbraco.Core/Services/IContentService.cs @@ -276,11 +276,11 @@ IContent CreateBlueprintFromContent(IContent blueprint, string name, int userId bool HasChildren(int id); /// - /// Gets the content Ids from the provided collection of content Ids that are scheduled for publishing. + /// Gets the content keys from the provided collection of keys that are scheduled for publishing. /// /// The content keys. /// - /// The provided collection of content Ids filtered for those that are scheduled for publishing. + /// The provided collection of content keys filtered for those that are scheduled for publishing. /// IEnumerable GetScheduledContentKeys(IEnumerable keys) => []; From 8b8dc1590c0b471ed102c39a4fe3c124f3de5a19 Mon Sep 17 00:00:00 2001 From: NillasKA Date: Mon, 11 Aug 2025 13:55:05 +0200 Subject: [PATCH 08/22] Adding a fixed prefix to all future signs, and removing the provider property --- .../Services/Signs/HasScheduleSignProvider.cs | 4 ++-- .../Services/Signs/ISignProvider.cs | 5 +++++ src/Umbraco.Cms.Api.Management/ViewModels/SignModel.cs | 2 -- .../ViewModels/Tree/EntityTreeItemResponseModel.cs | 4 ++-- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Cms.Api.Management/Services/Signs/HasScheduleSignProvider.cs b/src/Umbraco.Cms.Api.Management/Services/Signs/HasScheduleSignProvider.cs index c898042d9530..e4c2a25c9f9e 100644 --- a/src/Umbraco.Cms.Api.Management/Services/Signs/HasScheduleSignProvider.cs +++ b/src/Umbraco.Cms.Api.Management/Services/Signs/HasScheduleSignProvider.cs @@ -10,7 +10,7 @@ namespace Umbraco.Cms.Api.Management.Services.Signs; /// internal class HasScheduleSignProvider : ISignProvider { - private const string Alias = "ScheduledForPublish"; + private const string Alias = ISignProvider.Prefix + "ScheduledForPublish"; private readonly IContentService _contentService; @@ -29,7 +29,7 @@ public Task PopulateTreeSignsAsync(TItem[] treeItemViewModels, IEnumerabl IEnumerable contentKeysScheduledForPublishing = _contentService.GetScheduledContentKeys(treeItemViewModels.Select(x => x.Id)); foreach (Guid key in contentKeysScheduledForPublishing) { - treeItemViewModels.First(x => x.Id == key).AddSign(Core.Constants.System.UmbracoSignProvider, Alias); + treeItemViewModels.First(x => x.Id == key).AddSign(Alias); } return Task.CompletedTask; diff --git a/src/Umbraco.Cms.Api.Management/Services/Signs/ISignProvider.cs b/src/Umbraco.Cms.Api.Management/Services/Signs/ISignProvider.cs index 805a079fcb8a..f63993f0ef31 100644 --- a/src/Umbraco.Cms.Api.Management/Services/Signs/ISignProvider.cs +++ b/src/Umbraco.Cms.Api.Management/Services/Signs/ISignProvider.cs @@ -8,6 +8,11 @@ namespace Umbraco.Cms.Api.Management.Services.Signs; /// public interface ISignProvider { + /// + /// Prefix of each sign alias. + /// + const string Prefix = "Umb."; + /// /// Gets a value indicating whether this provider can provide tree signs for the specified item type. /// diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/SignModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/SignModel.cs index 3378d9ba10a9..0cfec8e36f82 100644 --- a/src/Umbraco.Cms.Api.Management/ViewModels/SignModel.cs +++ b/src/Umbraco.Cms.Api.Management/ViewModels/SignModel.cs @@ -2,7 +2,5 @@ namespace Umbraco.Cms.Api.Management.ViewModels; public class SignModel { - public required string Provider { get; set; } - public required string Alias { get; set; } } diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/Tree/EntityTreeItemResponseModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/Tree/EntityTreeItemResponseModel.cs index d50890d11ef8..a67fa87c8126 100644 --- a/src/Umbraco.Cms.Api.Management/ViewModels/Tree/EntityTreeItemResponseModel.cs +++ b/src/Umbraco.Cms.Api.Management/ViewModels/Tree/EntityTreeItemResponseModel.cs @@ -10,7 +10,7 @@ public class EntityTreeItemResponseModel : TreeItemPresentationModel public IEnumerable Signs => _signs.AsEnumerable(); - public void AddSign(string provider, string alias) => _signs.Add(new SignModel { Provider = provider, Alias = alias }); + public void AddSign(string alias) => _signs.Add(new SignModel { Alias = alias }); - public void RemoveSign(string provider, string alias) => _signs.RemoveAll(x => x.Provider == provider && x.Alias == alias); + public void RemoveSign(string alias) => _signs.RemoveAll(x => x.Alias == alias); } From 17bf1d919f3c8c82f10a8121a59c9495331a7792 Mon Sep 17 00:00:00 2001 From: NillasKA Date: Mon, 11 Aug 2025 14:22:41 +0200 Subject: [PATCH 09/22] Adding a sign for protected tree documents. --- .../UmbracoBuilder.Collections.cs | 3 ++- .../Services/Signs/IsProtectedSignProvider.cs | 27 +++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 src/Umbraco.Cms.Api.Management/Services/Signs/IsProtectedSignProvider.cs diff --git a/src/Umbraco.Cms.Api.Management/DependencyInjection/UmbracoBuilder.Collections.cs b/src/Umbraco.Cms.Api.Management/DependencyInjection/UmbracoBuilder.Collections.cs index ad35b799fe7c..ac7082ba0309 100644 --- a/src/Umbraco.Cms.Api.Management/DependencyInjection/UmbracoBuilder.Collections.cs +++ b/src/Umbraco.Cms.Api.Management/DependencyInjection/UmbracoBuilder.Collections.cs @@ -11,7 +11,8 @@ public static partial class UmbracoBuilderExtensions internal static void AddCollectionBuilders(this IUmbracoBuilder builder) { builder.SignProviders() - .Append(); + .Append() + .Append(); } /// diff --git a/src/Umbraco.Cms.Api.Management/Services/Signs/IsProtectedSignProvider.cs b/src/Umbraco.Cms.Api.Management/Services/Signs/IsProtectedSignProvider.cs new file mode 100644 index 000000000000..0f4196941d21 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Services/Signs/IsProtectedSignProvider.cs @@ -0,0 +1,27 @@ +using Umbraco.Cms.Api.Management.ViewModels.Tree; +using Umbraco.Cms.Core.Models.Entities; + +namespace Umbraco.Cms.Api.Management.Services.Signs; + +internal class IsProtectedSignProvider : ISignProvider +{ + private const string Alias = ISignProvider.Prefix + "IsProtected"; + + /// > + public bool CanProvideTreeSigns() => typeof(TItem) == typeof(DocumentTreeItemResponseModel); + + /// > + public Task PopulateTreeSignsAsync(TItem[] treeItemViewModels, IEnumerable entities) + where TItem : EntityTreeItemResponseModel, new() + { + foreach (TItem item in treeItemViewModels) + { + if (item is DocumentTreeItemResponseModel { IsProtected: true }) + { + item.AddSign(Alias); + } + } + + return Task.CompletedTask; + } +} From f44b53e1a5927202d46d60e41bf44bac9a9593f4 Mon Sep 17 00:00:00 2001 From: NillasKA Date: Mon, 11 Aug 2025 14:45:45 +0200 Subject: [PATCH 10/22] Adding IsProtectedSignProviderTest.cs & correcting HasScheduleSignProviderTests.cs to no longer assert the provider --- .../Signs/HasScheduleSignProviderTests.cs | 3 +- .../Signs/IsProtectedSignProviderTest.cs | 40 +++++++++++++++++++ 2 files changed, 41 insertions(+), 2 deletions(-) create mode 100644 tests/Umbraco.Tests.UnitTests/Umbraco.Cms.Api.Management/Services/Signs/IsProtectedSignProviderTest.cs diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Cms.Api.Management/Services/Signs/HasScheduleSignProviderTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Cms.Api.Management/Services/Signs/HasScheduleSignProviderTests.cs index 14a742ff713c..bde280ec2e51 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Cms.Api.Management/Services/Signs/HasScheduleSignProviderTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Cms.Api.Management/Services/Signs/HasScheduleSignProviderTests.cs @@ -39,7 +39,6 @@ public async Task HasScheduleSignProvider_Should_Populate_Signs() Assert.AreEqual(viewModels[1].Signs.Count(), 1); var signModel = viewModels[1].Signs.First(); - Assert.AreEqual(global::Umbraco.Cms.Core.Constants.System.UmbracoSignProvider, signModel.Provider); - Assert.AreEqual("ScheduledForPublish", signModel.Alias); + Assert.AreEqual("Umb.ScheduledForPublish", signModel.Alias); } } diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Cms.Api.Management/Services/Signs/IsProtectedSignProviderTest.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Cms.Api.Management/Services/Signs/IsProtectedSignProviderTest.cs new file mode 100644 index 000000000000..db76a1e86c2e --- /dev/null +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Cms.Api.Management/Services/Signs/IsProtectedSignProviderTest.cs @@ -0,0 +1,40 @@ +using Moq; +using NUnit.Framework; +using Umbraco.Cms.Api.Management.Services.Signs; +using Umbraco.Cms.Api.Management.ViewModels.Tree; +using Umbraco.Cms.Core.Models.Entities; +using Umbraco.Cms.Core.Services; + +namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Cms.Api.Management.Services.Signs; + +[TestFixture] +internal class IsProtectedSignProviderTest +{ + [Test] + public async Task IsProtectedSignProvider_Should_Populate_Signs() + { + var entities = new List + { + new() { Name = "Item 1" }, + new() { Name = "Item 2" }, + }; + + var sut = new IsProtectedSignProvider(); + + Assert.IsTrue(sut.CanProvideTreeSigns()); + + var viewModels = new List + { + new(), + new() { IsProtected = true }, + }; + + await sut.PopulateTreeSignsAsync(viewModels.ToArray(), entities); + + Assert.AreEqual(viewModels[0].Signs.Count(), 0); + Assert.AreEqual(viewModels[1].Signs.Count(), 1); + + var signModel = viewModels[1].Signs.First(); + Assert.AreEqual("Umb.IsProtected", signModel.Alias); + } +} From d7e854070164f0715785c094008e8dbda1b926fe Mon Sep 17 00:00:00 2001 From: NillasKA Date: Mon, 11 Aug 2025 15:20:40 +0200 Subject: [PATCH 11/22] Fixing minor things in accordance with CR --- .../Services/Signs/HasScheduleSignProvider.cs | 5 +++-- .../Services/Signs/ISignProvider.cs | 5 ----- .../Services/Signs/IsProtectedSignProvider.cs | 3 ++- src/Umbraco.Core/Constants-Conventions.cs | 11 +++++++++++ ...roviderTest.cs => IsProtectedSignProviderTests.cs} | 6 ++---- 5 files changed, 18 insertions(+), 12 deletions(-) rename tests/Umbraco.Tests.UnitTests/Umbraco.Cms.Api.Management/Services/Signs/{IsProtectedSignProviderTest.cs => IsProtectedSignProviderTests.cs} (90%) diff --git a/src/Umbraco.Cms.Api.Management/Services/Signs/HasScheduleSignProvider.cs b/src/Umbraco.Cms.Api.Management/Services/Signs/HasScheduleSignProvider.cs index e4c2a25c9f9e..985c88519935 100644 --- a/src/Umbraco.Cms.Api.Management/Services/Signs/HasScheduleSignProvider.cs +++ b/src/Umbraco.Cms.Api.Management/Services/Signs/HasScheduleSignProvider.cs @@ -1,7 +1,8 @@ +using Serilog.Core; using Umbraco.Cms.Api.Management.ViewModels.Tree; using Umbraco.Cms.Core.Models.Entities; using Umbraco.Cms.Core.Services; -using Umbraco.Extensions; +using Constants = Umbraco.Cms.Core.Constants; namespace Umbraco.Cms.Api.Management.Services.Signs; @@ -10,7 +11,7 @@ namespace Umbraco.Cms.Api.Management.Services.Signs; /// internal class HasScheduleSignProvider : ISignProvider { - private const string Alias = ISignProvider.Prefix + "ScheduledForPublish"; + private const string Alias = Constants.Conventions.Signs.Prefix + "ScheduledForPublish"; private readonly IContentService _contentService; diff --git a/src/Umbraco.Cms.Api.Management/Services/Signs/ISignProvider.cs b/src/Umbraco.Cms.Api.Management/Services/Signs/ISignProvider.cs index f63993f0ef31..805a079fcb8a 100644 --- a/src/Umbraco.Cms.Api.Management/Services/Signs/ISignProvider.cs +++ b/src/Umbraco.Cms.Api.Management/Services/Signs/ISignProvider.cs @@ -8,11 +8,6 @@ namespace Umbraco.Cms.Api.Management.Services.Signs; /// public interface ISignProvider { - /// - /// Prefix of each sign alias. - /// - const string Prefix = "Umb."; - /// /// Gets a value indicating whether this provider can provide tree signs for the specified item type. /// diff --git a/src/Umbraco.Cms.Api.Management/Services/Signs/IsProtectedSignProvider.cs b/src/Umbraco.Cms.Api.Management/Services/Signs/IsProtectedSignProvider.cs index 0f4196941d21..8bc12e0a1585 100644 --- a/src/Umbraco.Cms.Api.Management/Services/Signs/IsProtectedSignProvider.cs +++ b/src/Umbraco.Cms.Api.Management/Services/Signs/IsProtectedSignProvider.cs @@ -1,11 +1,12 @@ using Umbraco.Cms.Api.Management.ViewModels.Tree; +using Umbraco.Cms.Core; using Umbraco.Cms.Core.Models.Entities; namespace Umbraco.Cms.Api.Management.Services.Signs; internal class IsProtectedSignProvider : ISignProvider { - private const string Alias = ISignProvider.Prefix + "IsProtected"; + private const string Alias = Constants.Conventions.Signs.Prefix + "IsProtected"; /// > public bool CanProvideTreeSigns() => typeof(TItem) == typeof(DocumentTreeItemResponseModel); diff --git a/src/Umbraco.Core/Constants-Conventions.cs b/src/Umbraco.Core/Constants-Conventions.cs index 989b947adc09..7de76870026d 100644 --- a/src/Umbraco.Core/Constants-Conventions.cs +++ b/src/Umbraco.Core/Constants-Conventions.cs @@ -303,5 +303,16 @@ public static class Udi { public const string Prefix = "umb://"; } + + /// + /// Constants for all Sign aliases. + /// + public static class Signs + { + /// + /// Prefix for all signs aliases. + /// + public const string Prefix = "Umb."; + } } } diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Cms.Api.Management/Services/Signs/IsProtectedSignProviderTest.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Cms.Api.Management/Services/Signs/IsProtectedSignProviderTests.cs similarity index 90% rename from tests/Umbraco.Tests.UnitTests/Umbraco.Cms.Api.Management/Services/Signs/IsProtectedSignProviderTest.cs rename to tests/Umbraco.Tests.UnitTests/Umbraco.Cms.Api.Management/Services/Signs/IsProtectedSignProviderTests.cs index db76a1e86c2e..a0a15c7a2ebe 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Cms.Api.Management/Services/Signs/IsProtectedSignProviderTest.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Cms.Api.Management/Services/Signs/IsProtectedSignProviderTests.cs @@ -1,14 +1,12 @@ -using Moq; -using NUnit.Framework; +using NUnit.Framework; using Umbraco.Cms.Api.Management.Services.Signs; using Umbraco.Cms.Api.Management.ViewModels.Tree; using Umbraco.Cms.Core.Models.Entities; -using Umbraco.Cms.Core.Services; namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Cms.Api.Management.Services.Signs; [TestFixture] -internal class IsProtectedSignProviderTest +internal class IsProtectedSignProviderTests { [Test] public async Task IsProtectedSignProvider_Should_Populate_Signs() From 997a07986f5dad20ef3cf5603eadc4b40f551fd4 Mon Sep 17 00:00:00 2001 From: NillasKA Date: Tue, 12 Aug 2025 14:25:15 +0200 Subject: [PATCH 12/22] Adding collection items compatibility --- .../ContentCollectionControllerBase.cs | 32 +++++++++++++++- .../DocumentCollectionControllerBase.cs | 2 +- .../MediaCollectionControllerBase.cs | 3 +- .../Tree/EntityTreeControllerBase.cs | 2 +- .../Services/Signs/HasScheduleSignProvider.cs | 19 +++++++++- .../Services/Signs/ISignProvider.cs | 12 +++++- .../Services/Signs/IsProtectedSignProvider.cs | 22 ++++++++++- .../DocumentCollectionResponseModel.cs | 8 ++++ .../Signs/HasScheduleSignProviderTests.cs | 37 ++++++++++++++++++- .../Signs/IsProtectedSignProviderTests.cs | 33 ++++++++++++++++- 10 files changed, 156 insertions(+), 14 deletions(-) diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Content/ContentCollectionControllerBase.cs b/src/Umbraco.Cms.Api.Management/Controllers/Content/ContentCollectionControllerBase.cs index 2adfef96648f..5102e6b4ef88 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Content/ContentCollectionControllerBase.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Content/ContentCollectionControllerBase.cs @@ -1,7 +1,11 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Api.Common.ViewModels.Pagination; +using Umbraco.Cms.Api.Management.Services.Signs; using Umbraco.Cms.Api.Management.ViewModels.Content; +using Umbraco.Cms.Api.Management.ViewModels.Document.Collection; +using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Mapping; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.ContentEditing; @@ -11,15 +15,27 @@ namespace Umbraco.Cms.Api.Management.Controllers.Content; -public abstract class ContentCollectionControllerBase : ManagementApiControllerBase +public abstract class ContentCollectionControllerBase : ManagementApiControllerBase where TContent : class, IContentBase where TCollectionResponseModel : ContentResponseModelBase where TValueResponseModelBase : ValueResponseModelBase where TVariantResponseModel : VariantResponseModelBase + where TItem : DocumentCollectionResponseModel, new() { private readonly IUmbracoMapper _mapper; + private readonly SignProviderCollection _signProviders; - protected ContentCollectionControllerBase(IUmbracoMapper mapper) => _mapper = mapper; + protected ContentCollectionControllerBase(IUmbracoMapper mapper, SignProviderCollection signProvider) + { + _mapper = mapper; + _signProviders = signProvider; + } + + [Obsolete("Use the constructer with all parameters. To be removed in Umbraco 18")] + protected ContentCollectionControllerBase(IUmbracoMapper mapper) + : this(mapper, StaticServiceProvider.Instance.GetRequiredService()) + { + } [Obsolete("This method is no longer used and will be removed in Umbraco 17.")] protected IActionResult CollectionResult(ListViewPagedModel result) @@ -50,6 +66,10 @@ protected IActionResult CollectionResult(ListViewPagedModel result) protected IActionResult CollectionResult(List collectionResponseModels, long totalNumberOfItems) { + + TItem[] signTargets = collectionResponseModels.OfType().ToArray(); + PopulateSigns(signTargets).GetAwaiter().GetResult(); + var pageViewModel = new PagedViewModel { Items = collectionResponseModels, @@ -104,4 +124,12 @@ protected IActionResult ContentCollectionOperationStatusResult(ContentCollection StatusCode = StatusCodes.Status500InternalServerError, }, }); + + protected async Task PopulateSigns(TItem[] collectionViewModel) + { + foreach (ISignProvider signProvider in _signProviders.Where(x => x.CanProvideSigns())) + { + await signProvider.PopulateCollectionSignsAsync(collectionViewModel); + } + } } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Document/Collection/DocumentCollectionControllerBase.cs b/src/Umbraco.Cms.Api.Management/Controllers/Document/Collection/DocumentCollectionControllerBase.cs index b4ac5a86da1e..cf5358fc40c6 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Document/Collection/DocumentCollectionControllerBase.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Document/Collection/DocumentCollectionControllerBase.cs @@ -15,7 +15,7 @@ namespace Umbraco.Cms.Api.Management.Controllers.Document.Collection; [VersionedApiBackOfficeRoute($"{Constants.Web.RoutePath.Collection}/{Constants.UdiEntityType.Document}")] [ApiExplorerSettings(GroupName = nameof(Constants.UdiEntityType.Document))] [Authorize(Policy = AuthorizationPolicies.TreeAccessDocuments)] -public abstract class DocumentCollectionControllerBase : ContentCollectionControllerBase +public abstract class DocumentCollectionControllerBase : ContentCollectionControllerBase { protected DocumentCollectionControllerBase(IUmbracoMapper mapper) : base(mapper) diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Media/Collection/MediaCollectionControllerBase.cs b/src/Umbraco.Cms.Api.Management/Controllers/Media/Collection/MediaCollectionControllerBase.cs index cf6b05d8b96c..44c0616cd939 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Media/Collection/MediaCollectionControllerBase.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Media/Collection/MediaCollectionControllerBase.cs @@ -2,6 +2,7 @@ using Microsoft.AspNetCore.Mvc; using Umbraco.Cms.Api.Management.Controllers.Content; using Umbraco.Cms.Api.Management.Routing; +using Umbraco.Cms.Api.Management.ViewModels.Document.Collection; using Umbraco.Cms.Api.Management.ViewModels.Media; using Umbraco.Cms.Api.Management.ViewModels.Media.Collection; using Umbraco.Cms.Core; @@ -15,7 +16,7 @@ namespace Umbraco.Cms.Api.Management.Controllers.Media.Collection; [VersionedApiBackOfficeRoute($"{Constants.Web.RoutePath.Collection}/{Constants.UdiEntityType.Media}")] [ApiExplorerSettings(GroupName = nameof(Constants.UdiEntityType.Media))] [Authorize(Policy = AuthorizationPolicies.SectionAccessMedia)] -public abstract class MediaCollectionControllerBase : ContentCollectionControllerBase +public abstract class MediaCollectionControllerBase : ContentCollectionControllerBase { protected MediaCollectionControllerBase(IUmbracoMapper mapper) : base(mapper) diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Tree/EntityTreeControllerBase.cs b/src/Umbraco.Cms.Api.Management/Controllers/Tree/EntityTreeControllerBase.cs index 0ddf21ff977a..1d475e2af1f6 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Tree/EntityTreeControllerBase.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Tree/EntityTreeControllerBase.cs @@ -164,7 +164,7 @@ protected virtual TItem[] MapTreeItemViewModels(Guid? parentKey, IEntitySlim[] e protected virtual async Task PopulateSigns(TItem[] treeItemViewModels, IEnumerable entities) { - foreach (ISignProvider signProvider in _signProviders.Where(x => x.CanProvideTreeSigns())) + foreach (ISignProvider signProvider in _signProviders.Where(x => x.CanProvideSigns())) { await signProvider.PopulateTreeSignsAsync(treeItemViewModels, entities); } diff --git a/src/Umbraco.Cms.Api.Management/Services/Signs/HasScheduleSignProvider.cs b/src/Umbraco.Cms.Api.Management/Services/Signs/HasScheduleSignProvider.cs index 985c88519935..04954e05a332 100644 --- a/src/Umbraco.Cms.Api.Management/Services/Signs/HasScheduleSignProvider.cs +++ b/src/Umbraco.Cms.Api.Management/Services/Signs/HasScheduleSignProvider.cs @@ -1,4 +1,5 @@ using Serilog.Core; +using Umbraco.Cms.Api.Management.ViewModels.Document.Collection; using Umbraco.Cms.Api.Management.ViewModels.Tree; using Umbraco.Cms.Core.Models.Entities; using Umbraco.Cms.Core.Services; @@ -21,7 +22,10 @@ internal class HasScheduleSignProvider : ISignProvider public HasScheduleSignProvider(IContentService contentService) => _contentService = contentService; /// - public bool CanProvideTreeSigns() => typeof(TItem) == typeof(DocumentTreeItemResponseModel); + public bool CanProvideSigns() => + typeof(TItem) == typeof(DocumentTreeItemResponseModel) || + typeof(TItem) == typeof(DocumentCollectionResponseModel); + /// public Task PopulateTreeSignsAsync(TItem[] treeItemViewModels, IEnumerable entities) @@ -35,4 +39,17 @@ public Task PopulateTreeSignsAsync(TItem[] treeItemViewModels, IEnumerabl return Task.CompletedTask; } + + /// + public Task PopulateCollectionSignsAsync(TItem[] collectionItemViewModel) + where TItem : DocumentCollectionResponseModel, new() + { + IEnumerable contentKeysScheduledForPublishing = _contentService.GetScheduledContentKeys(collectionItemViewModel.Select(x => x.Id)); + foreach (Guid key in contentKeysScheduledForPublishing) + { + collectionItemViewModel.First(x => x.Id == key).AddSign(Alias); + } + + return Task.CompletedTask; + } } diff --git a/src/Umbraco.Cms.Api.Management/Services/Signs/ISignProvider.cs b/src/Umbraco.Cms.Api.Management/Services/Signs/ISignProvider.cs index 805a079fcb8a..1d9b66700302 100644 --- a/src/Umbraco.Cms.Api.Management/Services/Signs/ISignProvider.cs +++ b/src/Umbraco.Cms.Api.Management/Services/Signs/ISignProvider.cs @@ -1,3 +1,4 @@ +using Umbraco.Cms.Api.Management.ViewModels.Document.Collection; using Umbraco.Cms.Api.Management.ViewModels.Tree; using Umbraco.Cms.Core.Models.Entities; @@ -12,8 +13,7 @@ public interface ISignProvider /// Gets a value indicating whether this provider can provide tree signs for the specified item type. /// /// Type of tree item view model. - /// - bool CanProvideTreeSigns(); + bool CanProvideSigns(); /// /// Populates the provided tree item view models with signs. @@ -23,4 +23,12 @@ public interface ISignProvider /// The entities from which the collection of tree item view models was populated. Task PopulateTreeSignsAsync(TItem[] treeItemViewModels, IEnumerable entities) where TItem : EntityTreeItemResponseModel, new(); + + /// + /// Populates the provided collection item view models with signs. + /// + /// Type of collection item view model. + /// The collection of collection item view models populated with signs. + Task PopulateCollectionSignsAsync(TItem[] collectionItemViewModel) + where TItem : DocumentCollectionResponseModel, new(); } diff --git a/src/Umbraco.Cms.Api.Management/Services/Signs/IsProtectedSignProvider.cs b/src/Umbraco.Cms.Api.Management/Services/Signs/IsProtectedSignProvider.cs index 8bc12e0a1585..d39f3865aa81 100644 --- a/src/Umbraco.Cms.Api.Management/Services/Signs/IsProtectedSignProvider.cs +++ b/src/Umbraco.Cms.Api.Management/Services/Signs/IsProtectedSignProvider.cs @@ -1,4 +1,5 @@ -using Umbraco.Cms.Api.Management.ViewModels.Tree; +using Umbraco.Cms.Api.Management.ViewModels.Document.Collection; +using Umbraco.Cms.Api.Management.ViewModels.Tree; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Models.Entities; @@ -9,7 +10,9 @@ internal class IsProtectedSignProvider : ISignProvider private const string Alias = Constants.Conventions.Signs.Prefix + "IsProtected"; /// > - public bool CanProvideTreeSigns() => typeof(TItem) == typeof(DocumentTreeItemResponseModel); + public bool CanProvideSigns() => + typeof(TItem) == typeof(DocumentTreeItemResponseModel) || + typeof(TItem) == typeof(DocumentCollectionResponseModel); /// > public Task PopulateTreeSignsAsync(TItem[] treeItemViewModels, IEnumerable entities) @@ -25,4 +28,19 @@ public Task PopulateTreeSignsAsync(TItem[] treeItemViewModels, IEnumerabl return Task.CompletedTask; } + + /// + public Task PopulateCollectionSignsAsync(TItem[] collectionItemViewModel) + where TItem : DocumentCollectionResponseModel, new() + { + foreach (TItem item in collectionItemViewModel) + { + if (item is DocumentCollectionResponseModel { IsProtected: true }) + { + item.AddSign(Alias); + } + } + + return Task.CompletedTask; + } } diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/Document/Collection/DocumentCollectionResponseModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/Document/Collection/DocumentCollectionResponseModel.cs index 391714346a9f..ef7c11914259 100644 --- a/src/Umbraco.Cms.Api.Management/ViewModels/Document/Collection/DocumentCollectionResponseModel.cs +++ b/src/Umbraco.Cms.Api.Management/ViewModels/Document/Collection/DocumentCollectionResponseModel.cs @@ -5,6 +5,8 @@ namespace Umbraco.Cms.Api.Management.ViewModels.Document.Collection; public class DocumentCollectionResponseModel : ContentCollectionResponseModelBase { + private readonly List _signs = []; + public DocumentTypeCollectionReferenceResponseModel DocumentType { get; set; } = new(); public bool IsTrashed { get; set; } @@ -14,4 +16,10 @@ public class DocumentCollectionResponseModel : ContentCollectionResponseModelBas public IEnumerable Ancestors { get; set; } = []; public string? Updater { get; set; } + + public IEnumerable Signs => _signs.AsEnumerable(); + + public void AddSign(string alias) => _signs.Add(new SignModel { Alias = alias }); + + public void RemoveSign(string alias) => _signs.RemoveAll(x => x.Alias == alias); } diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Cms.Api.Management/Services/Signs/HasScheduleSignProviderTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Cms.Api.Management/Services/Signs/HasScheduleSignProviderTests.cs index bde280ec2e51..001f525de8b4 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Cms.Api.Management/Services/Signs/HasScheduleSignProviderTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Cms.Api.Management/Services/Signs/HasScheduleSignProviderTests.cs @@ -1,6 +1,7 @@ using Moq; using NUnit.Framework; using Umbraco.Cms.Api.Management.Services.Signs; +using Umbraco.Cms.Api.Management.ViewModels.Document.Collection; using Umbraco.Cms.Api.Management.ViewModels.Tree; using Umbraco.Cms.Core.Models.Entities; using Umbraco.Cms.Core.Services; @@ -11,7 +12,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Cms.Api.Management.Services.Signs; internal class HasScheduleSignProviderTests { [Test] - public async Task HasScheduleSignProvider_Should_Populate_Signs() + public async Task HasScheduleSignProvider_Should_Populate_Tree_Signs() { var entities = new List { @@ -25,7 +26,7 @@ public async Task HasScheduleSignProvider_Should_Populate_Signs() .Returns([entities[1].Key]); var sut = new HasScheduleSignProvider(contentServiceMock.Object); - Assert.IsTrue(sut.CanProvideTreeSigns()); + Assert.IsTrue(sut.CanProvideSigns()); var viewModels = new List { @@ -41,4 +42,36 @@ public async Task HasScheduleSignProvider_Should_Populate_Signs() var signModel = viewModels[1].Signs.First(); Assert.AreEqual("Umb.ScheduledForPublish", signModel.Alias); } + + [Test] + public async Task HasScheduleSignProvider_Should_Populate_Collection_Signs() + { + var entities = new List + { + new() { Key = Guid.NewGuid(), Name = "Item 1" }, + new() { Key = Guid.NewGuid(), Name = "Item 2" }, + }; + + var contentServiceMock = new Mock(); + contentServiceMock + .Setup(x => x.GetScheduledContentKeys(It.IsAny>())) + .Returns([entities[1].Key]); + var sut = new HasScheduleSignProvider(contentServiceMock.Object); + + Assert.IsTrue(sut.CanProvideSigns()); + + var viewModels = new List + { + new() { Id = entities[0].Key }, + new() { Id = entities[1].Key }, + }; + + await sut.PopulateCollectionSignsAsync(viewModels.ToArray()); + + Assert.AreEqual(viewModels[0].Signs.Count(), 0); + Assert.AreEqual(viewModels[1].Signs.Count(), 1); + + var signModel = viewModels[1].Signs.First(); + Assert.AreEqual("Umb.ScheduledForPublish", signModel.Alias); + } } diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Cms.Api.Management/Services/Signs/IsProtectedSignProviderTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Cms.Api.Management/Services/Signs/IsProtectedSignProviderTests.cs index a0a15c7a2ebe..f3d272ae878d 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Cms.Api.Management/Services/Signs/IsProtectedSignProviderTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Cms.Api.Management/Services/Signs/IsProtectedSignProviderTests.cs @@ -1,5 +1,6 @@ using NUnit.Framework; using Umbraco.Cms.Api.Management.Services.Signs; +using Umbraco.Cms.Api.Management.ViewModels.Document.Collection; using Umbraco.Cms.Api.Management.ViewModels.Tree; using Umbraco.Cms.Core.Models.Entities; @@ -9,7 +10,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Cms.Api.Management.Services.Signs; internal class IsProtectedSignProviderTests { [Test] - public async Task IsProtectedSignProvider_Should_Populate_Signs() + public async Task IsProtectedSignProvider_Should_Populate_Tree_Signs() { var entities = new List { @@ -19,7 +20,7 @@ public async Task IsProtectedSignProvider_Should_Populate_Signs() var sut = new IsProtectedSignProvider(); - Assert.IsTrue(sut.CanProvideTreeSigns()); + Assert.IsTrue(sut.CanProvideSigns()); var viewModels = new List { @@ -35,4 +36,32 @@ public async Task IsProtectedSignProvider_Should_Populate_Signs() var signModel = viewModels[1].Signs.First(); Assert.AreEqual("Umb.IsProtected", signModel.Alias); } + + [Test] + public async Task IsProtectedSignProvider_Should_Populate_Collection_Signs() + { + var entities = new List + { + new() { Name = "Item 1" }, + new() { Name = "Item 2" }, + }; + + var sut = new IsProtectedSignProvider(); + + Assert.IsTrue(sut.CanProvideSigns()); + + var viewModels = new List + { + new(), + new() { IsProtected = true }, + }; + + await sut.PopulateCollectionSignsAsync(viewModels.ToArray()); + + Assert.AreEqual(viewModels[0].Signs.Count(), 0); + Assert.AreEqual(viewModels[1].Signs.Count(), 1); + + var signModel = viewModels[1].Signs.First(); + Assert.AreEqual("Umb.IsProtected", signModel.Alias); + } } From 78660f7dc5d6bd90ef94534de2fca0db7c31dd18 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Wed, 13 Aug 2025 07:27:58 +0100 Subject: [PATCH 13/22] Introduced IHasSigns interface to provide more re-use across trees and collections. Fixed updates to base content controllers (no need to introduce a new type variable). Removed passing entities for populating tree signs (we aren't using it, so simplifies things). --- .../ContentCollectionControllerBase.cs | 16 ++++---- .../ByKeyDocumentCollectionController.cs | 1 + .../ByKeyMediaCollectionController.cs | 1 + .../Tree/EntityTreeControllerBase.cs | 12 +++--- .../Services/Signs/HasScheduleSignProvider.cs | 31 ++++++-------- .../Services/Signs/ISignProvider.cs | 29 +++++++------- .../Services/Signs/IsProtectedSignProvider.cs | 40 +++++++++---------- .../Content/ContentResponseModelBase.cs | 12 +++++- .../DocumentCollectionResponseModel.cs | 10 +---- .../ViewModels/IHasSigns.cs | 30 ++++++++++++++ .../Tree/EntityTreeItemResponseModel.cs | 2 +- .../Signs/HasScheduleSignProviderTests.cs | 4 +- .../Signs/IsProtectedSignProviderTests.cs | 6 +-- 13 files changed, 109 insertions(+), 85 deletions(-) create mode 100644 src/Umbraco.Cms.Api.Management/ViewModels/IHasSigns.cs diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Content/ContentCollectionControllerBase.cs b/src/Umbraco.Cms.Api.Management/Controllers/Content/ContentCollectionControllerBase.cs index 5102e6b4ef88..cb981fea74b1 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Content/ContentCollectionControllerBase.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Content/ContentCollectionControllerBase.cs @@ -4,7 +4,6 @@ using Umbraco.Cms.Api.Common.ViewModels.Pagination; using Umbraco.Cms.Api.Management.Services.Signs; using Umbraco.Cms.Api.Management.ViewModels.Content; -using Umbraco.Cms.Api.Management.ViewModels.Document.Collection; using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Mapping; using Umbraco.Cms.Core.Models; @@ -20,7 +19,6 @@ public abstract class ContentCollectionControllerBase where TValueResponseModelBase : ValueResponseModelBase where TVariantResponseModel : VariantResponseModelBase - where TItem : DocumentCollectionResponseModel, new() { private readonly IUmbracoMapper _mapper; private readonly SignProviderCollection _signProviders; @@ -64,11 +62,12 @@ protected IActionResult CollectionResult(ListViewPagedModel result) return Ok(pageViewModel); } + /// + /// Creates a collection result from the provided collection response models and total number of items. + /// protected IActionResult CollectionResult(List collectionResponseModels, long totalNumberOfItems) { - TItem[] signTargets = collectionResponseModels.OfType().ToArray(); - PopulateSigns(signTargets).GetAwaiter().GetResult(); var pageViewModel = new PagedViewModel { @@ -125,11 +124,14 @@ protected IActionResult ContentCollectionOperationStatusResult(ContentCollection }, }); - protected async Task PopulateSigns(TItem[] collectionViewModel) + /// + /// Populates the signs for the collection response models. + /// + protected async Task PopulateSigns(IEnumerable itemViewModels) { - foreach (ISignProvider signProvider in _signProviders.Where(x => x.CanProvideSigns())) + foreach (ISignProvider signProvider in _signProviders.Where(x => x.CanProvideSigns())) { - await signProvider.PopulateCollectionSignsAsync(collectionViewModel); + await signProvider.PopulateCollectionSignsAsync(itemViewModels); } } } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Document/Collection/ByKeyDocumentCollectionController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Document/Collection/ByKeyDocumentCollectionController.cs index f55c666cfcaf..4a3e7db8e87d 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Document/Collection/ByKeyDocumentCollectionController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Document/Collection/ByKeyDocumentCollectionController.cs @@ -65,6 +65,7 @@ public async Task ByKey( } List collectionResponseModels = await _documentCollectionPresentationFactory.CreateCollectionModelAsync(collectionAttempt.Result!); + await PopulateSigns(collectionResponseModels); return CollectionResult(collectionResponseModels, collectionAttempt.Result!.Items.Total); } } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Media/Collection/ByKeyMediaCollectionController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Media/Collection/ByKeyMediaCollectionController.cs index ba6eb5c8b398..0af7c9eac96d 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Media/Collection/ByKeyMediaCollectionController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Media/Collection/ByKeyMediaCollectionController.cs @@ -64,6 +64,7 @@ public async Task ByKey( } List collectionResponseModels = await _mediaCollectionPresentationFactory.CreateCollectionModelAsync(collectionAttempt.Result!); + await PopulateSigns(collectionResponseModels); return CollectionResult(collectionResponseModels, collectionAttempt.Result!.Items.Total); } } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Tree/EntityTreeControllerBase.cs b/src/Umbraco.Cms.Api.Management/Controllers/Tree/EntityTreeControllerBase.cs index 1d475e2af1f6..e69561877eeb 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Tree/EntityTreeControllerBase.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Tree/EntityTreeControllerBase.cs @@ -44,7 +44,7 @@ protected async Task>> GetRoot(int skip, int TItem[] treeItemViewModels = MapTreeItemViewModels(null, rootEntities); - await PopulateSigns(treeItemViewModels, rootEntities); + await PopulateSigns(treeItemViewModels); PagedViewModel result = PagedViewModel(treeItemViewModels, totalItems); @@ -57,7 +57,7 @@ protected async Task>> GetChildren(Guid paren TItem[] treeItemViewModels = MapTreeItemViewModels(parentId, children); - await PopulateSigns(treeItemViewModels, children); + await PopulateSigns(treeItemViewModels); PagedViewModel result = PagedViewModel(treeItemViewModels, totalItems); @@ -79,7 +79,7 @@ protected async Task>> GetSiblings(Guid targ TItem[] treeItemViewModels = MapTreeItemViewModels(parentKey, siblings); - await PopulateSigns(treeItemViewModels, siblings); + await PopulateSigns(treeItemViewModels); SubsetViewModel result = SubsetViewModel(treeItemViewModels, totalBefore, totalAfter); @@ -101,7 +101,7 @@ protected virtual async Task>> GetAncestors(Guid }) .ToArray(); - await PopulateSigns(treeItemViewModels, ancestorEntities); + await PopulateSigns(treeItemViewModels); return Ok(treeItemViewModels); } @@ -162,11 +162,11 @@ protected virtual IEntitySlim[] GetSiblingEntities(Guid target, int before, int protected virtual TItem[] MapTreeItemViewModels(Guid? parentKey, IEntitySlim[] entities) => entities.Select(entity => MapTreeItemViewModel(parentKey, entity)).ToArray(); - protected virtual async Task PopulateSigns(TItem[] treeItemViewModels, IEnumerable entities) + protected virtual async Task PopulateSigns(TItem[] treeItemViewModels) { foreach (ISignProvider signProvider in _signProviders.Where(x => x.CanProvideSigns())) { - await signProvider.PopulateTreeSignsAsync(treeItemViewModels, entities); + await signProvider.PopulateTreeSignsAsync(treeItemViewModels); } } diff --git a/src/Umbraco.Cms.Api.Management/Services/Signs/HasScheduleSignProvider.cs b/src/Umbraco.Cms.Api.Management/Services/Signs/HasScheduleSignProvider.cs index 04954e05a332..6625e2919be8 100644 --- a/src/Umbraco.Cms.Api.Management/Services/Signs/HasScheduleSignProvider.cs +++ b/src/Umbraco.Cms.Api.Management/Services/Signs/HasScheduleSignProvider.cs @@ -1,7 +1,6 @@ -using Serilog.Core; +using Umbraco.Cms.Api.Management.ViewModels; using Umbraco.Cms.Api.Management.ViewModels.Document.Collection; using Umbraco.Cms.Api.Management.ViewModels.Tree; -using Umbraco.Cms.Core.Models.Entities; using Umbraco.Cms.Core.Services; using Constants = Umbraco.Cms.Core.Constants; @@ -22,32 +21,26 @@ internal class HasScheduleSignProvider : ISignProvider public HasScheduleSignProvider(IContentService contentService) => _contentService = contentService; /// - public bool CanProvideSigns() => + public bool CanProvideSigns() + where TItem : IHasSigns => typeof(TItem) == typeof(DocumentTreeItemResponseModel) || typeof(TItem) == typeof(DocumentCollectionResponseModel); - /// - public Task PopulateTreeSignsAsync(TItem[] treeItemViewModels, IEnumerable entities) - where TItem : EntityTreeItemResponseModel, new() - { - IEnumerable contentKeysScheduledForPublishing = _contentService.GetScheduledContentKeys(treeItemViewModels.Select(x => x.Id)); - foreach (Guid key in contentKeysScheduledForPublishing) - { - treeItemViewModels.First(x => x.Id == key).AddSign(Alias); - } - - return Task.CompletedTask; - } + public Task PopulateTreeSignsAsync(IEnumerable itemViewModels) + where TItem : EntityTreeItemResponseModel, IHasSigns => PopulateSigns(itemViewModels); /// - public Task PopulateCollectionSignsAsync(TItem[] collectionItemViewModel) - where TItem : DocumentCollectionResponseModel, new() + public Task PopulateCollectionSignsAsync(IEnumerable itemViewModels) + where TItem : IHasSigns => PopulateSigns(itemViewModels); + + private Task PopulateSigns(IEnumerable itemViewModels) + where TItem : IHasSigns { - IEnumerable contentKeysScheduledForPublishing = _contentService.GetScheduledContentKeys(collectionItemViewModel.Select(x => x.Id)); + IEnumerable contentKeysScheduledForPublishing = _contentService.GetScheduledContentKeys(itemViewModels.Select(x => x.Id)); foreach (Guid key in contentKeysScheduledForPublishing) { - collectionItemViewModel.First(x => x.Id == key).AddSign(Alias); + itemViewModels.First(x => x.Id == key).AddSign(Alias); } return Task.CompletedTask; diff --git a/src/Umbraco.Cms.Api.Management/Services/Signs/ISignProvider.cs b/src/Umbraco.Cms.Api.Management/Services/Signs/ISignProvider.cs index 1d9b66700302..9574709f9fd1 100644 --- a/src/Umbraco.Cms.Api.Management/Services/Signs/ISignProvider.cs +++ b/src/Umbraco.Cms.Api.Management/Services/Signs/ISignProvider.cs @@ -1,6 +1,5 @@ -using Umbraco.Cms.Api.Management.ViewModels.Document.Collection; +using Umbraco.Cms.Api.Management.ViewModels; using Umbraco.Cms.Api.Management.ViewModels.Tree; -using Umbraco.Cms.Core.Models.Entities; namespace Umbraco.Cms.Api.Management.Services.Signs; @@ -10,25 +9,25 @@ namespace Umbraco.Cms.Api.Management.Services.Signs; public interface ISignProvider { /// - /// Gets a value indicating whether this provider can provide tree signs for the specified item type. + /// Gets a value indicating whether this provider can provide signs for the specified item type. /// - /// Type of tree item view model. - bool CanProvideSigns(); + /// Type of view model supporting signs. + bool CanProvideSigns() + where TItem : IHasSigns; /// /// Populates the provided tree item view models with signs. /// - /// Type of tree item view model. - /// The collection of tree item view models populated with signs. - /// The entities from which the collection of tree item view models was populated. - Task PopulateTreeSignsAsync(TItem[] treeItemViewModels, IEnumerable entities) - where TItem : EntityTreeItemResponseModel, new(); + /// Type of tree item view model supporting signs. + /// The collection of tree item view models to be populated with signs. + Task PopulateTreeSignsAsync(IEnumerable itemViewModels) + where TItem : EntityTreeItemResponseModel, IHasSigns; /// - /// Populates the provided collection item view models with signs. + /// Populates the provided collection view models with signs. /// - /// Type of collection item view model. - /// The collection of collection item view models populated with signs. - Task PopulateCollectionSignsAsync(TItem[] collectionItemViewModel) - where TItem : DocumentCollectionResponseModel, new(); + /// Type of collection view model supporting signs. + /// The collection of view models to be populated with signs. + Task PopulateCollectionSignsAsync(IEnumerable itemViewModels) + where TItem : IHasSigns; } diff --git a/src/Umbraco.Cms.Api.Management/Services/Signs/IsProtectedSignProvider.cs b/src/Umbraco.Cms.Api.Management/Services/Signs/IsProtectedSignProvider.cs index d39f3865aa81..b2e9c8e9ea82 100644 --- a/src/Umbraco.Cms.Api.Management/Services/Signs/IsProtectedSignProvider.cs +++ b/src/Umbraco.Cms.Api.Management/Services/Signs/IsProtectedSignProvider.cs @@ -1,41 +1,39 @@ -using Umbraco.Cms.Api.Management.ViewModels.Document.Collection; +using Umbraco.Cms.Api.Management.ViewModels; +using Umbraco.Cms.Api.Management.ViewModels.Document.Collection; using Umbraco.Cms.Api.Management.ViewModels.Tree; using Umbraco.Cms.Core; -using Umbraco.Cms.Core.Models.Entities; namespace Umbraco.Cms.Api.Management.Services.Signs; +/// +/// Implements a that provides signs for documents that are protected. +/// internal class IsProtectedSignProvider : ISignProvider { private const string Alias = Constants.Conventions.Signs.Prefix + "IsProtected"; /// > - public bool CanProvideSigns() => + public bool CanProvideSigns() + where TItem : IHasSigns => typeof(TItem) == typeof(DocumentTreeItemResponseModel) || typeof(TItem) == typeof(DocumentCollectionResponseModel); - /// > - public Task PopulateTreeSignsAsync(TItem[] treeItemViewModels, IEnumerable entities) - where TItem : EntityTreeItemResponseModel, new() - { - foreach (TItem item in treeItemViewModels) - { - if (item is DocumentTreeItemResponseModel { IsProtected: true }) - { - item.AddSign(Alias); - } - } - - return Task.CompletedTask; - } + /// + public Task PopulateTreeSignsAsync(IEnumerable itemViewModels) + where TItem : EntityTreeItemResponseModel, IHasSigns + => PopulateSigns(itemViewModels, x => x is DocumentTreeItemResponseModel { IsProtected: true }); /// - public Task PopulateCollectionSignsAsync(TItem[] collectionItemViewModel) - where TItem : DocumentCollectionResponseModel, new() + public Task PopulateCollectionSignsAsync(IEnumerable itemViewModels) + where TItem : IHasSigns + => PopulateSigns(itemViewModels, x => x is DocumentCollectionResponseModel { IsProtected: true }); + + private static Task PopulateSigns(IEnumerable itemViewModels, Func discrimator) + where TItem : IHasSigns { - foreach (TItem item in collectionItemViewModel) + foreach (TItem item in itemViewModels) { - if (item is DocumentCollectionResponseModel { IsProtected: true }) + if (discrimator(item)) { item.AddSign(Alias); } diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/Content/ContentResponseModelBase.cs b/src/Umbraco.Cms.Api.Management/ViewModels/Content/ContentResponseModelBase.cs index 1eddf7af46f0..8425079f524a 100644 --- a/src/Umbraco.Cms.Api.Management/ViewModels/Content/ContentResponseModelBase.cs +++ b/src/Umbraco.Cms.Api.Management/ViewModels/Content/ContentResponseModelBase.cs @@ -1,11 +1,19 @@ -using Umbraco.Cms.Core.Models.ContentEditing; +using Umbraco.Cms.Core.Models.ContentEditing; namespace Umbraco.Cms.Api.Management.ViewModels.Content; public abstract class ContentResponseModelBase - : ContentModelBase + : ContentModelBase, IHasSigns where TValueResponseModelBase : ValueModelBase where TVariantResponseModel : VariantResponseModelBase { + private readonly List _signs = []; + public Guid Id { get; set; } + + public IEnumerable Signs => _signs.AsEnumerable(); + + public void AddSign(string alias) => _signs.Add(new SignModel { Alias = alias }); + + public void RemoveSign(string alias) => _signs.RemoveAll(x => x.Alias == alias); } diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/Document/Collection/DocumentCollectionResponseModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/Document/Collection/DocumentCollectionResponseModel.cs index ef7c11914259..f1926587ce1d 100644 --- a/src/Umbraco.Cms.Api.Management/ViewModels/Document/Collection/DocumentCollectionResponseModel.cs +++ b/src/Umbraco.Cms.Api.Management/ViewModels/Document/Collection/DocumentCollectionResponseModel.cs @@ -3,10 +3,8 @@ namespace Umbraco.Cms.Api.Management.ViewModels.Document.Collection; -public class DocumentCollectionResponseModel : ContentCollectionResponseModelBase +public class DocumentCollectionResponseModel : ContentCollectionResponseModelBase, IHasSigns { - private readonly List _signs = []; - public DocumentTypeCollectionReferenceResponseModel DocumentType { get; set; } = new(); public bool IsTrashed { get; set; } @@ -16,10 +14,4 @@ public class DocumentCollectionResponseModel : ContentCollectionResponseModelBas public IEnumerable Ancestors { get; set; } = []; public string? Updater { get; set; } - - public IEnumerable Signs => _signs.AsEnumerable(); - - public void AddSign(string alias) => _signs.Add(new SignModel { Alias = alias }); - - public void RemoveSign(string alias) => _signs.RemoveAll(x => x.Alias == alias); } diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/IHasSigns.cs b/src/Umbraco.Cms.Api.Management/ViewModels/IHasSigns.cs new file mode 100644 index 000000000000..4f17a143bd03 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/ViewModels/IHasSigns.cs @@ -0,0 +1,30 @@ +namespace Umbraco.Cms.Api.Management.ViewModels; + +/// +/// Marker interface that indicates the type has support for backoffice signs (presented as icons +/// overlaying the main icon for the entity). +/// +public interface IHasSigns +{ + /// + /// Gets or sets the unique identifier for the entity. + /// + Guid Id { get; } + + /// + /// Gets the collection of signs for the entity. + /// + IEnumerable Signs { get; } + + /// + /// Adds a sign to the entity with the specified alias. + /// + /// + void AddSign(string alias); + + /// + /// Removes a sign from the entity with the specified alias. + /// + /// + void RemoveSign(string alias); +} diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/Tree/EntityTreeItemResponseModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/Tree/EntityTreeItemResponseModel.cs index a67fa87c8126..ac1057e29ee5 100644 --- a/src/Umbraco.Cms.Api.Management/ViewModels/Tree/EntityTreeItemResponseModel.cs +++ b/src/Umbraco.Cms.Api.Management/ViewModels/Tree/EntityTreeItemResponseModel.cs @@ -1,6 +1,6 @@ namespace Umbraco.Cms.Api.Management.ViewModels.Tree; -public class EntityTreeItemResponseModel : TreeItemPresentationModel +public class EntityTreeItemResponseModel : TreeItemPresentationModel, IHasSigns { private readonly List _signs = []; diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Cms.Api.Management/Services/Signs/HasScheduleSignProviderTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Cms.Api.Management/Services/Signs/HasScheduleSignProviderTests.cs index 001f525de8b4..4de19ee59f26 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Cms.Api.Management/Services/Signs/HasScheduleSignProviderTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Cms.Api.Management/Services/Signs/HasScheduleSignProviderTests.cs @@ -34,7 +34,7 @@ public async Task HasScheduleSignProvider_Should_Populate_Tree_Signs() new() { Id = entities[1].Key }, }; - await sut.PopulateTreeSignsAsync(viewModels.ToArray(), entities); + await sut.PopulateTreeSignsAsync(viewModels); Assert.AreEqual(viewModels[0].Signs.Count(), 0); Assert.AreEqual(viewModels[1].Signs.Count(), 1); @@ -66,7 +66,7 @@ public async Task HasScheduleSignProvider_Should_Populate_Collection_Signs() new() { Id = entities[1].Key }, }; - await sut.PopulateCollectionSignsAsync(viewModels.ToArray()); + await sut.PopulateCollectionSignsAsync(viewModels); Assert.AreEqual(viewModels[0].Signs.Count(), 0); Assert.AreEqual(viewModels[1].Signs.Count(), 1); diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Cms.Api.Management/Services/Signs/IsProtectedSignProviderTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Cms.Api.Management/Services/Signs/IsProtectedSignProviderTests.cs index f3d272ae878d..da177019b8de 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Cms.Api.Management/Services/Signs/IsProtectedSignProviderTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Cms.Api.Management/Services/Signs/IsProtectedSignProviderTests.cs @@ -1,4 +1,4 @@ -using NUnit.Framework; +using NUnit.Framework; using Umbraco.Cms.Api.Management.Services.Signs; using Umbraco.Cms.Api.Management.ViewModels.Document.Collection; using Umbraco.Cms.Api.Management.ViewModels.Tree; @@ -28,7 +28,7 @@ public async Task IsProtectedSignProvider_Should_Populate_Tree_Signs() new() { IsProtected = true }, }; - await sut.PopulateTreeSignsAsync(viewModels.ToArray(), entities); + await sut.PopulateTreeSignsAsync(viewModels); Assert.AreEqual(viewModels[0].Signs.Count(), 0); Assert.AreEqual(viewModels[1].Signs.Count(), 1); @@ -56,7 +56,7 @@ public async Task IsProtectedSignProvider_Should_Populate_Collection_Signs() new() { IsProtected = true }, }; - await sut.PopulateCollectionSignsAsync(viewModels.ToArray()); + await sut.PopulateCollectionSignsAsync(viewModels); Assert.AreEqual(viewModels[0].Signs.Count(), 0); Assert.AreEqual(viewModels[1].Signs.Count(), 1); From 6b4bc7c274a700e581d2e501063cee52e4604fd0 Mon Sep 17 00:00:00 2001 From: NillasKA Date: Wed, 13 Aug 2025 12:28:22 +0200 Subject: [PATCH 14/22] Refactoring a bit to make existing code less duplicated and fixing some constructor obsoletion --- .../ContentCollectionControllerBase.cs | 2 +- .../ByKeyDocumentCollectionController.cs | 24 +++++++++++++++++-- .../DocumentCollectionControllerBase.cs | 11 ++++++++- .../ByKeyMediaCollectionController.cs | 24 +++++++++++++++++-- .../MediaCollectionControllerBase.cs | 15 +++++++++++- .../Tree/EntityTreeControllerBase.cs | 6 ++--- .../Services/Signs/HasScheduleSignProvider.cs | 9 +------ .../Services/Signs/ISignProvider.cs | 16 ++++--------- .../Services/Signs/IsProtectedSignProvider.cs | 15 +++--------- .../DocumentCollectionResponseModel.cs | 2 +- .../Item/DocumentItemResponseModel.cs | 2 +- .../ViewModels/IIsProtected.cs | 12 ++++++++++ .../Tree/DocumentTreeItemResponseModel.cs | 2 +- .../Signs/HasScheduleSignProviderTests.cs | 4 ++-- .../Signs/IsProtectedSignProviderTests.cs | 18 +++----------- 15 files changed, 100 insertions(+), 62 deletions(-) create mode 100644 src/Umbraco.Cms.Api.Management/ViewModels/IIsProtected.cs diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Content/ContentCollectionControllerBase.cs b/src/Umbraco.Cms.Api.Management/Controllers/Content/ContentCollectionControllerBase.cs index cb981fea74b1..fa53f6959f6e 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Content/ContentCollectionControllerBase.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Content/ContentCollectionControllerBase.cs @@ -131,7 +131,7 @@ protected async Task PopulateSigns(IEnumerable itemVie { foreach (ISignProvider signProvider in _signProviders.Where(x => x.CanProvideSigns())) { - await signProvider.PopulateCollectionSignsAsync(itemViewModels); + await signProvider.PopulateSignsAsync(itemViewModels); } } } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Document/Collection/ByKeyDocumentCollectionController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Document/Collection/ByKeyDocumentCollectionController.cs index 4a3e7db8e87d..5ab89ba2748c 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Document/Collection/ByKeyDocumentCollectionController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Document/Collection/ByKeyDocumentCollectionController.cs @@ -1,10 +1,13 @@ using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Api.Common.ViewModels.Pagination; using Umbraco.Cms.Api.Management.Factories; +using Umbraco.Cms.Api.Management.Services.Signs; using Umbraco.Cms.Api.Management.ViewModels.Document.Collection; using Umbraco.Cms.Core; +using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Mapping; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Security; @@ -20,18 +23,35 @@ public class ByKeyDocumentCollectionController : DocumentCollectionControllerBas private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor; private readonly IDocumentCollectionPresentationFactory _documentCollectionPresentationFactory; + [ActivatorUtilitiesConstructor] public ByKeyDocumentCollectionController( IContentListViewService contentListViewService, IBackOfficeSecurityAccessor backOfficeSecurityAccessor, IUmbracoMapper mapper, - IDocumentCollectionPresentationFactory documentCollectionPresentationFactory) - : base(mapper) + IDocumentCollectionPresentationFactory documentCollectionPresentationFactory, + SignProviderCollection signProviders) + : base(mapper, signProviders) { _contentListViewService = contentListViewService; _backOfficeSecurityAccessor = backOfficeSecurityAccessor; _documentCollectionPresentationFactory = documentCollectionPresentationFactory; } + [Obsolete("Please use the constructor with all parameters. Scheduled to be removed in V18")] + public ByKeyDocumentCollectionController( + IContentListViewService contentListViewService, + IBackOfficeSecurityAccessor backOfficeSecurityAccessor, + IUmbracoMapper mapper, + IDocumentCollectionPresentationFactory documentCollectionPresentationFactory) + : this( + contentListViewService, + backOfficeSecurityAccessor, + mapper, + documentCollectionPresentationFactory, + StaticServiceProvider.Instance.GetRequiredService()) + { + } + [HttpGet("{id:guid}")] [MapToApiVersion("1.0")] [ProducesResponseType(typeof(PagedViewModel), StatusCodes.Status200OK)] diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Document/Collection/DocumentCollectionControllerBase.cs b/src/Umbraco.Cms.Api.Management/Controllers/Document/Collection/DocumentCollectionControllerBase.cs index cf5358fc40c6..9bc521a6a4a9 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Document/Collection/DocumentCollectionControllerBase.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Document/Collection/DocumentCollectionControllerBase.cs @@ -1,10 +1,13 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Api.Management.Controllers.Content; using Umbraco.Cms.Api.Management.Routing; +using Umbraco.Cms.Api.Management.Services.Signs; using Umbraco.Cms.Api.Management.ViewModels.Document; using Umbraco.Cms.Api.Management.ViewModels.Document.Collection; using Umbraco.Cms.Core; +using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Mapping; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Services.OperationStatus; @@ -17,8 +20,14 @@ namespace Umbraco.Cms.Api.Management.Controllers.Document.Collection; [Authorize(Policy = AuthorizationPolicies.TreeAccessDocuments)] public abstract class DocumentCollectionControllerBase : ContentCollectionControllerBase { + protected DocumentCollectionControllerBase(IUmbracoMapper mapper, SignProviderCollection signProviders) + : base(mapper, signProviders) + { + } + + [Obsolete("Please use the constructor with all parameters. Scheduled to be removed in V18")] protected DocumentCollectionControllerBase(IUmbracoMapper mapper) - : base(mapper) + : this(mapper, StaticServiceProvider.Instance.GetRequiredService()) { } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Media/Collection/ByKeyMediaCollectionController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Media/Collection/ByKeyMediaCollectionController.cs index 0af7c9eac96d..f2f55e9242ca 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Media/Collection/ByKeyMediaCollectionController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Media/Collection/ByKeyMediaCollectionController.cs @@ -1,10 +1,13 @@ using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Api.Common.ViewModels.Pagination; using Umbraco.Cms.Api.Management.Factories; +using Umbraco.Cms.Api.Management.Services.Signs; using Umbraco.Cms.Api.Management.ViewModels.Media.Collection; using Umbraco.Cms.Core; +using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Mapping; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Security; @@ -20,18 +23,35 @@ public class ByKeyMediaCollectionController : MediaCollectionControllerBase private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor; private readonly IMediaCollectionPresentationFactory _mediaCollectionPresentationFactory; + [ActivatorUtilitiesConstructor] public ByKeyMediaCollectionController( IMediaListViewService mediaListViewService, IBackOfficeSecurityAccessor backOfficeSecurityAccessor, IUmbracoMapper mapper, - IMediaCollectionPresentationFactory mediaCollectionPresentationFactory) - : base(mapper) + IMediaCollectionPresentationFactory mediaCollectionPresentationFactory, + SignProviderCollection signProviders) + : base(mapper, signProviders) { _mediaListViewService = mediaListViewService; _backOfficeSecurityAccessor = backOfficeSecurityAccessor; _mediaCollectionPresentationFactory = mediaCollectionPresentationFactory; } + [Obsolete("Please use the constructors with all the parameters. Scheduled to be removed in Umbraco 18")] + public ByKeyMediaCollectionController( + IMediaListViewService mediaListViewService, + IBackOfficeSecurityAccessor backOfficeSecurityAccessor, + IUmbracoMapper mapper, + IMediaCollectionPresentationFactory mediaCollectionPresentationFactory) + : this( + mediaListViewService, + backOfficeSecurityAccessor, + mapper, + mediaCollectionPresentationFactory, + StaticServiceProvider.Instance.GetRequiredService()) + { + } + [HttpGet] [MapToApiVersion("1.0")] [ProducesResponseType(typeof(PagedViewModel), StatusCodes.Status200OK)] diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Media/Collection/MediaCollectionControllerBase.cs b/src/Umbraco.Cms.Api.Management/Controllers/Media/Collection/MediaCollectionControllerBase.cs index 44c0616cd939..242d473dabee 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Media/Collection/MediaCollectionControllerBase.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Media/Collection/MediaCollectionControllerBase.cs @@ -1,11 +1,14 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Api.Management.Controllers.Content; using Umbraco.Cms.Api.Management.Routing; +using Umbraco.Cms.Api.Management.Services.Signs; using Umbraco.Cms.Api.Management.ViewModels.Document.Collection; using Umbraco.Cms.Api.Management.ViewModels.Media; using Umbraco.Cms.Api.Management.ViewModels.Media.Collection; using Umbraco.Cms.Core; +using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Mapping; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Services.OperationStatus; @@ -18,8 +21,18 @@ namespace Umbraco.Cms.Api.Management.Controllers.Media.Collection; [Authorize(Policy = AuthorizationPolicies.SectionAccessMedia)] public abstract class MediaCollectionControllerBase : ContentCollectionControllerBase { + private readonly SignProviderCollection _signProviders; + + [ActivatorUtilitiesConstructor] + protected MediaCollectionControllerBase(IUmbracoMapper mapper, SignProviderCollection signProviders) + : base(mapper, signProviders) + { + _signProviders = signProviders; + } + + [Obsolete("Please use the constructors with all the parameters. Scheduled to be removed in Umbraco 18")] protected MediaCollectionControllerBase(IUmbracoMapper mapper) - : base(mapper) + : this(mapper, StaticServiceProvider.Instance.GetRequiredService()) { } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Tree/EntityTreeControllerBase.cs b/src/Umbraco.Cms.Api.Management/Controllers/Tree/EntityTreeControllerBase.cs index e69561877eeb..8a5cc27ece0d 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Tree/EntityTreeControllerBase.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Tree/EntityTreeControllerBase.cs @@ -166,7 +166,7 @@ protected virtual async Task PopulateSigns(TItem[] treeItemViewModels) { foreach (ISignProvider signProvider in _signProviders.Where(x => x.CanProvideSigns())) { - await signProvider.PopulateTreeSignsAsync(treeItemViewModels); + await signProvider.PopulateSignsAsync(treeItemViewModels); } } @@ -179,9 +179,9 @@ protected virtual TItem MapTreeItemViewModel(Guid? parentKey, IEntitySlim entity Parent = parentKey.HasValue ? new ReferenceByIdModel { - Id = parentKey.Value + Id = parentKey.Value, } - : null + : null, }; return viewModel; diff --git a/src/Umbraco.Cms.Api.Management/Services/Signs/HasScheduleSignProvider.cs b/src/Umbraco.Cms.Api.Management/Services/Signs/HasScheduleSignProvider.cs index 6625e2919be8..3a027743f62d 100644 --- a/src/Umbraco.Cms.Api.Management/Services/Signs/HasScheduleSignProvider.cs +++ b/src/Umbraco.Cms.Api.Management/Services/Signs/HasScheduleSignProvider.cs @@ -27,14 +27,7 @@ public bool CanProvideSigns() typeof(TItem) == typeof(DocumentCollectionResponseModel); /// - public Task PopulateTreeSignsAsync(IEnumerable itemViewModels) - where TItem : EntityTreeItemResponseModel, IHasSigns => PopulateSigns(itemViewModels); - - /// - public Task PopulateCollectionSignsAsync(IEnumerable itemViewModels) - where TItem : IHasSigns => PopulateSigns(itemViewModels); - - private Task PopulateSigns(IEnumerable itemViewModels) + public Task PopulateSignsAsync(IEnumerable itemViewModels) where TItem : IHasSigns { IEnumerable contentKeysScheduledForPublishing = _contentService.GetScheduledContentKeys(itemViewModels.Select(x => x.Id)); diff --git a/src/Umbraco.Cms.Api.Management/Services/Signs/ISignProvider.cs b/src/Umbraco.Cms.Api.Management/Services/Signs/ISignProvider.cs index 9574709f9fd1..f485fd389f54 100644 --- a/src/Umbraco.Cms.Api.Management/Services/Signs/ISignProvider.cs +++ b/src/Umbraco.Cms.Api.Management/Services/Signs/ISignProvider.cs @@ -16,18 +16,10 @@ bool CanProvideSigns() where TItem : IHasSigns; /// - /// Populates the provided tree item view models with signs. + /// Populates the provided item view models with signs. /// - /// Type of tree item view model supporting signs. - /// The collection of tree item view models to be populated with signs. - Task PopulateTreeSignsAsync(IEnumerable itemViewModels) - where TItem : EntityTreeItemResponseModel, IHasSigns; - - /// - /// Populates the provided collection view models with signs. - /// - /// Type of collection view model supporting signs. - /// The collection of view models to be populated with signs. - Task PopulateCollectionSignsAsync(IEnumerable itemViewModels) + /// Type of item view model supporting signs. + /// The collection of item view models to be populated with signs. + Task PopulateSignsAsync(IEnumerable itemViewModels) where TItem : IHasSigns; } diff --git a/src/Umbraco.Cms.Api.Management/Services/Signs/IsProtectedSignProvider.cs b/src/Umbraco.Cms.Api.Management/Services/Signs/IsProtectedSignProvider.cs index b2e9c8e9ea82..ff5df286bb4e 100644 --- a/src/Umbraco.Cms.Api.Management/Services/Signs/IsProtectedSignProvider.cs +++ b/src/Umbraco.Cms.Api.Management/Services/Signs/IsProtectedSignProvider.cs @@ -18,22 +18,13 @@ public bool CanProvideSigns() typeof(TItem) == typeof(DocumentTreeItemResponseModel) || typeof(TItem) == typeof(DocumentCollectionResponseModel); - /// - public Task PopulateTreeSignsAsync(IEnumerable itemViewModels) - where TItem : EntityTreeItemResponseModel, IHasSigns - => PopulateSigns(itemViewModels, x => x is DocumentTreeItemResponseModel { IsProtected: true }); - - /// - public Task PopulateCollectionSignsAsync(IEnumerable itemViewModels) - where TItem : IHasSigns - => PopulateSigns(itemViewModels, x => x is DocumentCollectionResponseModel { IsProtected: true }); - - private static Task PopulateSigns(IEnumerable itemViewModels, Func discrimator) + /// > + public Task PopulateSignsAsync(IEnumerable itemViewModels) where TItem : IHasSigns { foreach (TItem item in itemViewModels) { - if (discrimator(item)) + if (item is IIsProtected { IsProtected: true }) { item.AddSign(Alias); } diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/Document/Collection/DocumentCollectionResponseModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/Document/Collection/DocumentCollectionResponseModel.cs index f1926587ce1d..107ec0c8917f 100644 --- a/src/Umbraco.Cms.Api.Management/ViewModels/Document/Collection/DocumentCollectionResponseModel.cs +++ b/src/Umbraco.Cms.Api.Management/ViewModels/Document/Collection/DocumentCollectionResponseModel.cs @@ -3,7 +3,7 @@ namespace Umbraco.Cms.Api.Management.ViewModels.Document.Collection; -public class DocumentCollectionResponseModel : ContentCollectionResponseModelBase, IHasSigns +public class DocumentCollectionResponseModel : ContentCollectionResponseModelBase, IHasSigns, IIsProtected { public DocumentTypeCollectionReferenceResponseModel DocumentType { get; set; } = new(); diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/Document/Item/DocumentItemResponseModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/Document/Item/DocumentItemResponseModel.cs index 25f4975b9f74..264d466598ee 100644 --- a/src/Umbraco.Cms.Api.Management/ViewModels/Document/Item/DocumentItemResponseModel.cs +++ b/src/Umbraco.Cms.Api.Management/ViewModels/Document/Item/DocumentItemResponseModel.cs @@ -4,7 +4,7 @@ namespace Umbraco.Cms.Api.Management.ViewModels.Document.Item; -public class DocumentItemResponseModel : ItemResponseModelBase +public class DocumentItemResponseModel : ItemResponseModelBase, IIsProtected { public bool IsTrashed { get; set; } diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/IIsProtected.cs b/src/Umbraco.Cms.Api.Management/ViewModels/IIsProtected.cs new file mode 100644 index 000000000000..94679b2ceb86 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/ViewModels/IIsProtected.cs @@ -0,0 +1,12 @@ +namespace Umbraco.Cms.Api.Management.ViewModels; + +/// +/// Specifies if the implementation of this interface is protected or not. +/// +public interface IIsProtected +{ + /// + /// Specifies if the implementation of this interface is protected or not. + /// + bool IsProtected { get; set; } +} diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/Tree/DocumentTreeItemResponseModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/Tree/DocumentTreeItemResponseModel.cs index 1bde7631025b..f784665657ac 100644 --- a/src/Umbraco.Cms.Api.Management/ViewModels/Tree/DocumentTreeItemResponseModel.cs +++ b/src/Umbraco.Cms.Api.Management/ViewModels/Tree/DocumentTreeItemResponseModel.cs @@ -3,7 +3,7 @@ namespace Umbraco.Cms.Api.Management.ViewModels.Tree; -public class DocumentTreeItemResponseModel : ContentTreeItemResponseModel +public class DocumentTreeItemResponseModel : ContentTreeItemResponseModel, IIsProtected { public bool IsProtected { get; set; } diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Cms.Api.Management/Services/Signs/HasScheduleSignProviderTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Cms.Api.Management/Services/Signs/HasScheduleSignProviderTests.cs index 4de19ee59f26..4648728d98b6 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Cms.Api.Management/Services/Signs/HasScheduleSignProviderTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Cms.Api.Management/Services/Signs/HasScheduleSignProviderTests.cs @@ -34,7 +34,7 @@ public async Task HasScheduleSignProvider_Should_Populate_Tree_Signs() new() { Id = entities[1].Key }, }; - await sut.PopulateTreeSignsAsync(viewModels); + await sut.PopulateSignsAsync(viewModels); Assert.AreEqual(viewModels[0].Signs.Count(), 0); Assert.AreEqual(viewModels[1].Signs.Count(), 1); @@ -66,7 +66,7 @@ public async Task HasScheduleSignProvider_Should_Populate_Collection_Signs() new() { Id = entities[1].Key }, }; - await sut.PopulateCollectionSignsAsync(viewModels); + await sut.PopulateSignsAsync(viewModels); Assert.AreEqual(viewModels[0].Signs.Count(), 0); Assert.AreEqual(viewModels[1].Signs.Count(), 1); diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Cms.Api.Management/Services/Signs/IsProtectedSignProviderTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Cms.Api.Management/Services/Signs/IsProtectedSignProviderTests.cs index da177019b8de..e3760f2e2029 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Cms.Api.Management/Services/Signs/IsProtectedSignProviderTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Cms.Api.Management/Services/Signs/IsProtectedSignProviderTests.cs @@ -1,4 +1,4 @@ -using NUnit.Framework; +using NUnit.Framework; using Umbraco.Cms.Api.Management.Services.Signs; using Umbraco.Cms.Api.Management.ViewModels.Document.Collection; using Umbraco.Cms.Api.Management.ViewModels.Tree; @@ -12,12 +12,6 @@ internal class IsProtectedSignProviderTests [Test] public async Task IsProtectedSignProvider_Should_Populate_Tree_Signs() { - var entities = new List - { - new() { Name = "Item 1" }, - new() { Name = "Item 2" }, - }; - var sut = new IsProtectedSignProvider(); Assert.IsTrue(sut.CanProvideSigns()); @@ -28,7 +22,7 @@ public async Task IsProtectedSignProvider_Should_Populate_Tree_Signs() new() { IsProtected = true }, }; - await sut.PopulateTreeSignsAsync(viewModels); + await sut.PopulateSignsAsync(viewModels); Assert.AreEqual(viewModels[0].Signs.Count(), 0); Assert.AreEqual(viewModels[1].Signs.Count(), 1); @@ -40,12 +34,6 @@ public async Task IsProtectedSignProvider_Should_Populate_Tree_Signs() [Test] public async Task IsProtectedSignProvider_Should_Populate_Collection_Signs() { - var entities = new List - { - new() { Name = "Item 1" }, - new() { Name = "Item 2" }, - }; - var sut = new IsProtectedSignProvider(); Assert.IsTrue(sut.CanProvideSigns()); @@ -56,7 +44,7 @@ public async Task IsProtectedSignProvider_Should_Populate_Collection_Signs() new() { IsProtected = true }, }; - await sut.PopulateCollectionSignsAsync(viewModels); + await sut.PopulateSignsAsync(viewModels); Assert.AreEqual(viewModels[0].Signs.Count(), 0); Assert.AreEqual(viewModels[1].Signs.Count(), 1); From f536819323a81cce5cdb4e97a467a5f2939aeac1 Mon Sep 17 00:00:00 2001 From: NillasKA Date: Wed, 13 Aug 2025 15:41:24 +0200 Subject: [PATCH 15/22] Introducing a has pending changes sign. --- .../UmbracoBuilder.Collections.cs | 3 +- .../Signs/HasPendingChangesSignProvider.cs | 49 +++++++++++++++++++ 2 files changed, 51 insertions(+), 1 deletion(-) create mode 100644 src/Umbraco.Cms.Api.Management/Services/Signs/HasPendingChangesSignProvider.cs diff --git a/src/Umbraco.Cms.Api.Management/DependencyInjection/UmbracoBuilder.Collections.cs b/src/Umbraco.Cms.Api.Management/DependencyInjection/UmbracoBuilder.Collections.cs index ac7082ba0309..c2667c22bab9 100644 --- a/src/Umbraco.Cms.Api.Management/DependencyInjection/UmbracoBuilder.Collections.cs +++ b/src/Umbraco.Cms.Api.Management/DependencyInjection/UmbracoBuilder.Collections.cs @@ -12,7 +12,8 @@ internal static void AddCollectionBuilders(this IUmbracoBuilder builder) { builder.SignProviders() .Append() - .Append(); + .Append() + .Append(); } /// diff --git a/src/Umbraco.Cms.Api.Management/Services/Signs/HasPendingChangesSignProvider.cs b/src/Umbraco.Cms.Api.Management/Services/Signs/HasPendingChangesSignProvider.cs new file mode 100644 index 000000000000..14067bffdf88 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Services/Signs/HasPendingChangesSignProvider.cs @@ -0,0 +1,49 @@ +using Umbraco.Cms.Api.Management.Mapping.Content; +using Umbraco.Cms.Api.Management.ViewModels; +using Umbraco.Cms.Api.Management.ViewModels.Document; +using Umbraco.Cms.Api.Management.ViewModels.Document.Collection; +using Umbraco.Cms.Api.Management.ViewModels.Tree; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Services; + +namespace Umbraco.Cms.Api.Management.Services.Signs; + +public class HasPendingChangesSignProvider : ISignProvider +{ + private const string Alias = Constants.Conventions.Signs.Prefix + "PendingChanges"; + + private readonly IContentService _contentService; + + /// + /// Initializes a new instance of the class. + /// + public HasPendingChangesSignProvider(IContentService contentService) => _contentService = contentService; + + public bool CanProvideSigns() + where TItem : IHasSigns => + typeof(TItem) == typeof(DocumentTreeItemResponseModel) || + typeof(TItem) == typeof(DocumentCollectionResponseModel); + + public Task PopulateSignsAsync(IEnumerable itemViewModels) + where TItem : IHasSigns + { + foreach (TItem item in itemViewModels) + { + IContent? content = _contentService.GetById(item.Id); + if (content == null) + { + continue; + } + + DocumentVariantState state = DocumentVariantStateHelper.GetState(content, null); + + if (state == DocumentVariantState.PublishedPendingChanges) + { + item.AddSign(Alias); + } + } + + return Task.CompletedTask; + } +} From a864cd58ff6b68a7d706c3e1455f59aed1ec92ff Mon Sep 17 00:00:00 2001 From: NillasKA Date: Thu, 14 Aug 2025 14:05:50 +0200 Subject: [PATCH 16/22] Applying changes based on CR --- .../Controllers/Content/ContentCollectionControllerBase.cs | 4 +--- .../Document/Collection/DocumentCollectionControllerBase.cs | 2 +- .../Media/Collection/MediaCollectionControllerBase.cs | 3 +-- src/Umbraco.Cms.Api.Management/ViewModels/IHasSigns.cs | 2 +- src/Umbraco.Cms.Api.Management/ViewModels/IIsProtected.cs | 4 ++-- 5 files changed, 6 insertions(+), 9 deletions(-) diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Content/ContentCollectionControllerBase.cs b/src/Umbraco.Cms.Api.Management/Controllers/Content/ContentCollectionControllerBase.cs index fa53f6959f6e..a38ade606019 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Content/ContentCollectionControllerBase.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Content/ContentCollectionControllerBase.cs @@ -14,7 +14,7 @@ namespace Umbraco.Cms.Api.Management.Controllers.Content; -public abstract class ContentCollectionControllerBase : ManagementApiControllerBase +public abstract class ContentCollectionControllerBase : ManagementApiControllerBase where TContent : class, IContentBase where TCollectionResponseModel : ContentResponseModelBase where TValueResponseModelBase : ValueResponseModelBase @@ -67,8 +67,6 @@ protected IActionResult CollectionResult(ListViewPagedModel result) /// protected IActionResult CollectionResult(List collectionResponseModels, long totalNumberOfItems) { - TItem[] signTargets = collectionResponseModels.OfType().ToArray(); - var pageViewModel = new PagedViewModel { Items = collectionResponseModels, diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Document/Collection/DocumentCollectionControllerBase.cs b/src/Umbraco.Cms.Api.Management/Controllers/Document/Collection/DocumentCollectionControllerBase.cs index 9bc521a6a4a9..97d36434c9b2 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Document/Collection/DocumentCollectionControllerBase.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Document/Collection/DocumentCollectionControllerBase.cs @@ -18,7 +18,7 @@ namespace Umbraco.Cms.Api.Management.Controllers.Document.Collection; [VersionedApiBackOfficeRoute($"{Constants.Web.RoutePath.Collection}/{Constants.UdiEntityType.Document}")] [ApiExplorerSettings(GroupName = nameof(Constants.UdiEntityType.Document))] [Authorize(Policy = AuthorizationPolicies.TreeAccessDocuments)] -public abstract class DocumentCollectionControllerBase : ContentCollectionControllerBase +public abstract class DocumentCollectionControllerBase : ContentCollectionControllerBase { protected DocumentCollectionControllerBase(IUmbracoMapper mapper, SignProviderCollection signProviders) : base(mapper, signProviders) diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Media/Collection/MediaCollectionControllerBase.cs b/src/Umbraco.Cms.Api.Management/Controllers/Media/Collection/MediaCollectionControllerBase.cs index 242d473dabee..b7269c603bcf 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Media/Collection/MediaCollectionControllerBase.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Media/Collection/MediaCollectionControllerBase.cs @@ -19,11 +19,10 @@ namespace Umbraco.Cms.Api.Management.Controllers.Media.Collection; [VersionedApiBackOfficeRoute($"{Constants.Web.RoutePath.Collection}/{Constants.UdiEntityType.Media}")] [ApiExplorerSettings(GroupName = nameof(Constants.UdiEntityType.Media))] [Authorize(Policy = AuthorizationPolicies.SectionAccessMedia)] -public abstract class MediaCollectionControllerBase : ContentCollectionControllerBase +public abstract class MediaCollectionControllerBase : ContentCollectionControllerBase { private readonly SignProviderCollection _signProviders; - [ActivatorUtilitiesConstructor] protected MediaCollectionControllerBase(IUmbracoMapper mapper, SignProviderCollection signProviders) : base(mapper, signProviders) { diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/IHasSigns.cs b/src/Umbraco.Cms.Api.Management/ViewModels/IHasSigns.cs index 4f17a143bd03..23aafd1e2bd8 100644 --- a/src/Umbraco.Cms.Api.Management/ViewModels/IHasSigns.cs +++ b/src/Umbraco.Cms.Api.Management/ViewModels/IHasSigns.cs @@ -7,7 +7,7 @@ namespace Umbraco.Cms.Api.Management.ViewModels; public interface IHasSigns { /// - /// Gets or sets the unique identifier for the entity. + /// Gets the unique identifier for the entity. /// Guid Id { get; } diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/IIsProtected.cs b/src/Umbraco.Cms.Api.Management/ViewModels/IIsProtected.cs index 94679b2ceb86..3babb19b82f1 100644 --- a/src/Umbraco.Cms.Api.Management/ViewModels/IIsProtected.cs +++ b/src/Umbraco.Cms.Api.Management/ViewModels/IIsProtected.cs @@ -1,12 +1,12 @@ namespace Umbraco.Cms.Api.Management.ViewModels; /// -/// Specifies if the implementation of this interface is protected or not. +/// Marker interface that indicates the type can represent the state of protected content. /// public interface IIsProtected { /// - /// Specifies if the implementation of this interface is protected or not. + /// Gets or sets a value indicating whether the model represents content that is protected. /// bool IsProtected { get; set; } } From c5476e5da5b09dcb20e48256c754ca73eb5cca67 Mon Sep 17 00:00:00 2001 From: NillasKA Date: Thu, 14 Aug 2025 14:06:17 +0200 Subject: [PATCH 17/22] Introducing tests for HasPendingChangesSignProvider.cs and stopped the use of contentService --- .../Signs/HasPendingChangesSignProvider.cs | 45 +++++++++++-------- .../HasPendingChangesSignProviderTests.cs | 45 +++++++++++++++++++ 2 files changed, 72 insertions(+), 18 deletions(-) create mode 100644 tests/Umbraco.Tests.UnitTests/Umbraco.Cms.Api.Management/Services/Signs/HasPendingChangesSignProviderTests.cs diff --git a/src/Umbraco.Cms.Api.Management/Services/Signs/HasPendingChangesSignProvider.cs b/src/Umbraco.Cms.Api.Management/Services/Signs/HasPendingChangesSignProvider.cs index 14067bffdf88..ebed6c18691a 100644 --- a/src/Umbraco.Cms.Api.Management/Services/Signs/HasPendingChangesSignProvider.cs +++ b/src/Umbraco.Cms.Api.Management/Services/Signs/HasPendingChangesSignProvider.cs @@ -1,4 +1,6 @@ -using Umbraco.Cms.Api.Management.Mapping.Content; +using NPoco; +using Org.BouncyCastle.Asn1.X509.Qualified; +using Umbraco.Cms.Api.Management.Mapping.Content; using Umbraco.Cms.Api.Management.ViewModels; using Umbraco.Cms.Api.Management.ViewModels.Document; using Umbraco.Cms.Api.Management.ViewModels.Document.Collection; @@ -13,13 +15,6 @@ public class HasPendingChangesSignProvider : ISignProvider { private const string Alias = Constants.Conventions.Signs.Prefix + "PendingChanges"; - private readonly IContentService _contentService; - - /// - /// Initializes a new instance of the class. - /// - public HasPendingChangesSignProvider(IContentService contentService) => _contentService = contentService; - public bool CanProvideSigns() where TItem : IHasSigns => typeof(TItem) == typeof(DocumentTreeItemResponseModel) || @@ -30,17 +25,31 @@ public Task PopulateSignsAsync(IEnumerable itemViewModels) { foreach (TItem item in itemViewModels) { - IContent? content = _contentService.GetById(item.Id); - if (content == null) - { - continue; - } - - DocumentVariantState state = DocumentVariantStateHelper.GetState(content, null); - - if (state == DocumentVariantState.PublishedPendingChanges) + switch (item) { - item.AddSign(Alias); + case DocumentTreeItemResponseModel treeItem: + foreach (DocumentVariantItemResponseModel variant in treeItem.Variants) + { + DocumentVariantState state = variant.State; + if (state == DocumentVariantState.PublishedPendingChanges) + { + item.AddSign(Alias); + } + } + + break; + + case DocumentCollectionResponseModel collectionItem: + foreach (DocumentVariantItemResponseModel variant in collectionItem.Variants.OfType()) + { + DocumentVariantState state = variant.State; + if (state == DocumentVariantState.PublishedPendingChanges) + { + item.AddSign(Alias); + } + } + + break; } } diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Cms.Api.Management/Services/Signs/HasPendingChangesSignProviderTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Cms.Api.Management/Services/Signs/HasPendingChangesSignProviderTests.cs new file mode 100644 index 000000000000..780458e9fe78 --- /dev/null +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Cms.Api.Management/Services/Signs/HasPendingChangesSignProviderTests.cs @@ -0,0 +1,45 @@ +using NUnit.Framework; +using Umbraco.Cms.Api.Management.Services.Signs; +using Umbraco.Cms.Api.Management.ViewModels.Document; +using Umbraco.Cms.Api.Management.ViewModels.Tree; + + + +namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Cms.Api.Management.Services.Signs; + +[TestFixture] +internal class HasPendingChangesSignProviderTests +{ + [Test] + public async Task HasPendingChangesSignProvider_Should_Populate_Tree_Signs() + { + var sut = new HasPendingChangesSignProvider(); + + Assert.IsTrue(sut.CanProvideSigns()); + + var viewModels = new List + { + new() { Id = Guid.NewGuid() }, + new() + { + Id = Guid.NewGuid(), Variants = new List + { + new() + { + State = DocumentVariantState.PublishedPendingChanges, + Culture = null, + Name = "Test", + }, + }, + }, + }; + + await sut.PopulateSignsAsync(viewModels); + + Assert.AreEqual(viewModels[0].Signs.Count(), 0); + Assert.AreEqual(viewModels[1].Signs.Count(), 1); + + var signModel = viewModels[1].Signs.First(); + Assert.AreEqual("Umb.PendingChanges", signModel.Alias); + } +} From e974521ccd567ca89da29d1ff6769f76a6ef6ec2 Mon Sep 17 00:00:00 2001 From: NillasKA Date: Thu, 14 Aug 2025 14:35:41 +0200 Subject: [PATCH 18/22] Introducing tests for HasPendingChangesSignProvider.cs and slight logic change --- .../Signs/HasPendingChangesSignProvider.cs | 14 +++--- .../HasPendingChangesSignProviderTests.cs | 48 ++++++++++++++++++- 2 files changed, 52 insertions(+), 10 deletions(-) diff --git a/src/Umbraco.Cms.Api.Management/Services/Signs/HasPendingChangesSignProvider.cs b/src/Umbraco.Cms.Api.Management/Services/Signs/HasPendingChangesSignProvider.cs index ebed6c18691a..cc2a492855ec 100644 --- a/src/Umbraco.Cms.Api.Management/Services/Signs/HasPendingChangesSignProvider.cs +++ b/src/Umbraco.Cms.Api.Management/Services/Signs/HasPendingChangesSignProvider.cs @@ -1,13 +1,9 @@ -using NPoco; -using Org.BouncyCastle.Asn1.X509.Qualified; -using Umbraco.Cms.Api.Management.Mapping.Content; -using Umbraco.Cms.Api.Management.ViewModels; +using Umbraco.Cms.Api.Management.ViewModels; using Umbraco.Cms.Api.Management.ViewModels.Document; using Umbraco.Cms.Api.Management.ViewModels.Document.Collection; using Umbraco.Cms.Api.Management.ViewModels.Tree; using Umbraco.Cms.Core; -using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Services; + namespace Umbraco.Cms.Api.Management.Services.Signs; @@ -34,18 +30,20 @@ public Task PopulateSignsAsync(IEnumerable itemViewModels) if (state == DocumentVariantState.PublishedPendingChanges) { item.AddSign(Alias); + break; } } break; case DocumentCollectionResponseModel collectionItem: - foreach (DocumentVariantItemResponseModel variant in collectionItem.Variants.OfType()) + foreach (DocumentVariantResponseModel variant in collectionItem.Variants) { - DocumentVariantState state = variant.State; + var state = variant.State; if (state == DocumentVariantState.PublishedPendingChanges) { item.AddSign(Alias); + break; } } diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Cms.Api.Management/Services/Signs/HasPendingChangesSignProviderTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Cms.Api.Management/Services/Signs/HasPendingChangesSignProviderTests.cs index 780458e9fe78..4ab7642edf14 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Cms.Api.Management/Services/Signs/HasPendingChangesSignProviderTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Cms.Api.Management/Services/Signs/HasPendingChangesSignProviderTests.cs @@ -1,6 +1,7 @@ using NUnit.Framework; using Umbraco.Cms.Api.Management.Services.Signs; using Umbraco.Cms.Api.Management.ViewModels.Document; +using Umbraco.Cms.Api.Management.ViewModels.Document.Collection; using Umbraco.Cms.Api.Management.ViewModels.Tree; @@ -11,11 +12,23 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Cms.Api.Management.Services.Signs; internal class HasPendingChangesSignProviderTests { [Test] - public async Task HasPendingChangesSignProvider_Should_Populate_Tree_Signs() + public async Task HasPendingChangesSignProvider_Can_Provide_Tree_Signs() { var sut = new HasPendingChangesSignProvider(); - Assert.IsTrue(sut.CanProvideSigns()); + } + + [Test] + public async Task HasPendingChangesSignProvider_Can_Provide_Collection_Signs() + { + var sut = new HasPendingChangesSignProvider(); + Assert.IsTrue(sut.CanProvideSigns()); + } + + [Test] + public async Task HasPendingChangesSignProvider_Should_Populate_Tree_Signs() + { + var sut = new HasPendingChangesSignProvider(); var viewModels = new List { @@ -42,4 +55,35 @@ public async Task HasPendingChangesSignProvider_Should_Populate_Tree_Signs() var signModel = viewModels[1].Signs.First(); Assert.AreEqual("Umb.PendingChanges", signModel.Alias); } + + [Test] + public async Task HasPendingChangesSignProvider_Should_Populate_Collection_Signs() + { + var sut = new HasPendingChangesSignProvider(); + + var viewModels = new List + { + new() { Id = Guid.NewGuid() }, + new() + { + Id = Guid.NewGuid(), Variants = new List + { + new() + { + State = DocumentVariantState.PublishedPendingChanges, + Culture = null, + Name = "Test", + }, + }, + }, + }; + + await sut.PopulateSignsAsync(viewModels); + + Assert.AreEqual(viewModels[0].Signs.Count(), 0); + Assert.AreEqual(viewModels[1].Signs.Count(), 1); + + var signModel = viewModels[1].Signs.First(); + Assert.AreEqual("Umb.PendingChanges", signModel.Alias); + } } From efc3bebfc8826de7ac44c2d5e63ea5f0d5d0e5ab Mon Sep 17 00:00:00 2001 From: NillasKA Date: Thu, 14 Aug 2025 15:19:15 +0200 Subject: [PATCH 19/22] Introduced HasCollectionSignProvider.cs and tests. --- .../UmbracoBuilder.Collections.cs | 3 +- .../Signs/HasCollectionSignProvider.cs | 29 +++++++++++++ .../Signs/HasCollectionSignProviderTests.cs | 41 +++++++++++++++++++ 3 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 src/Umbraco.Cms.Api.Management/Services/Signs/HasCollectionSignProvider.cs create mode 100644 tests/Umbraco.Tests.UnitTests/Umbraco.Cms.Api.Management/Services/Signs/HasCollectionSignProviderTests.cs diff --git a/src/Umbraco.Cms.Api.Management/DependencyInjection/UmbracoBuilder.Collections.cs b/src/Umbraco.Cms.Api.Management/DependencyInjection/UmbracoBuilder.Collections.cs index c2667c22bab9..e93fb98dfcf8 100644 --- a/src/Umbraco.Cms.Api.Management/DependencyInjection/UmbracoBuilder.Collections.cs +++ b/src/Umbraco.Cms.Api.Management/DependencyInjection/UmbracoBuilder.Collections.cs @@ -13,7 +13,8 @@ internal static void AddCollectionBuilders(this IUmbracoBuilder builder) builder.SignProviders() .Append() .Append() - .Append(); + .Append() + .Append(); } /// diff --git a/src/Umbraco.Cms.Api.Management/Services/Signs/HasCollectionSignProvider.cs b/src/Umbraco.Cms.Api.Management/Services/Signs/HasCollectionSignProvider.cs new file mode 100644 index 000000000000..1b571e56927a --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Services/Signs/HasCollectionSignProvider.cs @@ -0,0 +1,29 @@ +using Umbraco.Cms.Api.Management.ViewModels; +using Umbraco.Cms.Api.Management.ViewModels.Tree; +using Umbraco.Cms.Core; + +namespace Umbraco.Cms.Api.Management.Services.Signs; + +public class HasCollectionSignProvider : ISignProvider +{ + private const string Alias = Constants.Conventions.Signs.Prefix + "HasCollection"; + + public bool CanProvideSigns() + where TItem : IHasSigns => + typeof(TItem) == typeof(DocumentTreeItemResponseModel); + + public Task PopulateSignsAsync(IEnumerable itemViewModels) + where TItem : IHasSigns + { + foreach (TItem item in itemViewModels) + { + if (item is DocumentTreeItemResponseModel treeItem && + treeItem.DocumentType?.Collection?.Id != Guid.Empty) + { + item.AddSign(Alias); + } + } + + return Task.CompletedTask; + } +} diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Cms.Api.Management/Services/Signs/HasCollectionSignProviderTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Cms.Api.Management/Services/Signs/HasCollectionSignProviderTests.cs new file mode 100644 index 000000000000..a29d22521a6f --- /dev/null +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Cms.Api.Management/Services/Signs/HasCollectionSignProviderTests.cs @@ -0,0 +1,41 @@ +using NUnit.Framework; +using Umbraco.Cms.Api.Management.Services.Signs; +using Umbraco.Cms.Api.Management.ViewModels; +using Umbraco.Cms.Api.Management.ViewModels.DocumentType; +using Umbraco.Cms.Api.Management.ViewModels.Tree; + + + +namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Cms.Api.Management.Services.Signs; + +[TestFixture] +internal class HasCollectionSignProviderTests +{ + [Test] + public async Task HasCollectionSignProvider_Can_Provide_Tree_Signs() + { + var sut = new HasPendingChangesSignProvider(); + Assert.IsTrue(sut.CanProvideSigns()); + } + + [Test] + public async Task HasCollectionSignProvider_Should_Populate_Tree_Signs() + { + var sut = new HasCollectionSignProvider(); + + var viewModels = new List + { + new() + { + Id = Guid.NewGuid(), DocumentType = new DocumentTypeReferenceResponseModel() { Collection = new ReferenceByIdModel(Guid.NewGuid()) }, + }, + }; + + await sut.PopulateSignsAsync(viewModels); + + Assert.AreEqual(viewModels[0].Signs.Count(), 1); + + var signModel = viewModels[0].Signs.First(); + Assert.AreEqual("Umb.HasCollection", signModel.Alias); + } +} From 907063f58c33308ab99c99559f330ade99108cd8 Mon Sep 17 00:00:00 2001 From: NillasKA Date: Fri, 15 Aug 2025 11:37:44 +0200 Subject: [PATCH 20/22] Introducing collection signs to Media Tree & Media Collection items --- .../DocumentType/DocumentTypeMapDefinition.cs | 1 + .../MediaType/MediaTypeMapDefinition.cs | 1 + .../Signs/HasCollectionSignProvider.cs | 47 ++++++++- .../Signs/HasPendingChangesSignProvider.cs | 24 ++--- ...ypeCollectionReferenceResponseModelBase.cs | 2 + .../Signs/HasCollectionSignProviderTests.cs | 99 ++++++++++++++++++- 6 files changed, 152 insertions(+), 22 deletions(-) diff --git a/src/Umbraco.Cms.Api.Management/Mapping/DocumentType/DocumentTypeMapDefinition.cs b/src/Umbraco.Cms.Api.Management/Mapping/DocumentType/DocumentTypeMapDefinition.cs index 0e9aeba492f5..7aeb4f1b15a9 100644 --- a/src/Umbraco.Cms.Api.Management/Mapping/DocumentType/DocumentTypeMapDefinition.cs +++ b/src/Umbraco.Cms.Api.Management/Mapping/DocumentType/DocumentTypeMapDefinition.cs @@ -115,6 +115,7 @@ private void Map(ISimpleContentType source, DocumentTypeCollectionReferenceRespo target.Id = source.Key; target.Alias = source.Alias; target.Icon = source.Icon ?? string.Empty; + target.Collection = ReferenceByIdModel.ReferenceOrNull(source.ListView); } // Umbraco.Code.MapAll diff --git a/src/Umbraco.Cms.Api.Management/Mapping/MediaType/MediaTypeMapDefinition.cs b/src/Umbraco.Cms.Api.Management/Mapping/MediaType/MediaTypeMapDefinition.cs index af1a0fff090d..c789b9cbc714 100644 --- a/src/Umbraco.Cms.Api.Management/Mapping/MediaType/MediaTypeMapDefinition.cs +++ b/src/Umbraco.Cms.Api.Management/Mapping/MediaType/MediaTypeMapDefinition.cs @@ -99,5 +99,6 @@ private void Map(ISimpleContentType source, MediaTypeCollectionReferenceResponse target.Id = source.Key; target.Alias = source.Alias; target.Icon = source.Icon ?? string.Empty; + target.Collection = ReferenceByIdModel.ReferenceOrNull(source.ListView); } } diff --git a/src/Umbraco.Cms.Api.Management/Services/Signs/HasCollectionSignProvider.cs b/src/Umbraco.Cms.Api.Management/Services/Signs/HasCollectionSignProvider.cs index 1b571e56927a..759216c56ec1 100644 --- a/src/Umbraco.Cms.Api.Management/Services/Signs/HasCollectionSignProvider.cs +++ b/src/Umbraco.Cms.Api.Management/Services/Signs/HasCollectionSignProvider.cs @@ -1,26 +1,65 @@ using Umbraco.Cms.Api.Management.ViewModels; +using Umbraco.Cms.Api.Management.ViewModels.Document.Collection; +using Umbraco.Cms.Api.Management.ViewModels.Media.Collection; using Umbraco.Cms.Api.Management.ViewModels.Tree; using Umbraco.Cms.Core; namespace Umbraco.Cms.Api.Management.Services.Signs; +/// +/// Implements a that provides signs for documents that has a collection. +/// public class HasCollectionSignProvider : ISignProvider { private const string Alias = Constants.Conventions.Signs.Prefix + "HasCollection"; + /// public bool CanProvideSigns() where TItem : IHasSigns => - typeof(TItem) == typeof(DocumentTreeItemResponseModel); + typeof(TItem) == typeof(DocumentTreeItemResponseModel) || + typeof(TItem) == typeof(DocumentCollectionResponseModel) || + typeof(TItem) == typeof(MediaTreeItemResponseModel) || + typeof(TItem) == typeof(MediaCollectionResponseModel); +/// public Task PopulateSignsAsync(IEnumerable itemViewModels) where TItem : IHasSigns { foreach (TItem item in itemViewModels) { - if (item is DocumentTreeItemResponseModel treeItem && - treeItem.DocumentType?.Collection?.Id != Guid.Empty) + switch (item) { - item.AddSign(Alias); + case DocumentTreeItemResponseModel response: + if (response.DocumentType.Collection != null) + { + item.AddSign(Alias); + } + + break; + + case DocumentCollectionResponseModel response: + if (response.DocumentType.Collection != null) + { + item.AddSign(Alias); + } + + break; + + case MediaTreeItemResponseModel response: + if (response.MediaType.Collection != null) + { + item.AddSign(Alias); + } + + break; + + case MediaCollectionResponseModel response: + if (response.MediaType.Collection != null) + { + item.AddSign(Alias); + } + + break; } } diff --git a/src/Umbraco.Cms.Api.Management/Services/Signs/HasPendingChangesSignProvider.cs b/src/Umbraco.Cms.Api.Management/Services/Signs/HasPendingChangesSignProvider.cs index cc2a492855ec..af083f5b826b 100644 --- a/src/Umbraco.Cms.Api.Management/Services/Signs/HasPendingChangesSignProvider.cs +++ b/src/Umbraco.Cms.Api.Management/Services/Signs/HasPendingChangesSignProvider.cs @@ -4,18 +4,22 @@ using Umbraco.Cms.Api.Management.ViewModels.Tree; using Umbraco.Cms.Core; - namespace Umbraco.Cms.Api.Management.Services.Signs; +/// +/// Implements a that provides signs for documents that have pending changes. +/// public class HasPendingChangesSignProvider : ISignProvider { private const string Alias = Constants.Conventions.Signs.Prefix + "PendingChanges"; + /// public bool CanProvideSigns() where TItem : IHasSigns => typeof(TItem) == typeof(DocumentTreeItemResponseModel) || typeof(TItem) == typeof(DocumentCollectionResponseModel); + /// public Task PopulateSignsAsync(IEnumerable itemViewModels) where TItem : IHasSigns { @@ -24,27 +28,17 @@ public Task PopulateSignsAsync(IEnumerable itemViewModels) switch (item) { case DocumentTreeItemResponseModel treeItem: - foreach (DocumentVariantItemResponseModel variant in treeItem.Variants) + if (treeItem.Variants.Any(variant => variant.State == DocumentVariantState.PublishedPendingChanges)) { - DocumentVariantState state = variant.State; - if (state == DocumentVariantState.PublishedPendingChanges) - { - item.AddSign(Alias); - break; - } + item.AddSign(Alias); } break; case DocumentCollectionResponseModel collectionItem: - foreach (DocumentVariantResponseModel variant in collectionItem.Variants) + if (collectionItem.Variants.Any(variant => variant.State == DocumentVariantState.PublishedPendingChanges)) { - var state = variant.State; - if (state == DocumentVariantState.PublishedPendingChanges) - { - item.AddSign(Alias); - break; - } + item.AddSign(Alias); } break; diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/ContentType/ContentTypeCollectionReferenceResponseModelBase.cs b/src/Umbraco.Cms.Api.Management/ViewModels/ContentType/ContentTypeCollectionReferenceResponseModelBase.cs index 70d3b992ad15..1760b8d36441 100644 --- a/src/Umbraco.Cms.Api.Management/ViewModels/ContentType/ContentTypeCollectionReferenceResponseModelBase.cs +++ b/src/Umbraco.Cms.Api.Management/ViewModels/ContentType/ContentTypeCollectionReferenceResponseModelBase.cs @@ -7,4 +7,6 @@ public abstract class ContentTypeCollectionReferenceResponseModelBase public string Alias { get; set; } = string.Empty; public string Icon { get; set; } = string.Empty; + + public ReferenceByIdModel? Collection { get; set; } } diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Cms.Api.Management/Services/Signs/HasCollectionSignProviderTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Cms.Api.Management/Services/Signs/HasCollectionSignProviderTests.cs index a29d22521a6f..2e9c2242bbc8 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Cms.Api.Management/Services/Signs/HasCollectionSignProviderTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Cms.Api.Management/Services/Signs/HasCollectionSignProviderTests.cs @@ -1,11 +1,12 @@ using NUnit.Framework; using Umbraco.Cms.Api.Management.Services.Signs; using Umbraco.Cms.Api.Management.ViewModels; +using Umbraco.Cms.Api.Management.ViewModels.Document.Collection; using Umbraco.Cms.Api.Management.ViewModels.DocumentType; +using Umbraco.Cms.Api.Management.ViewModels.Media.Collection; +using Umbraco.Cms.Api.Management.ViewModels.MediaType; using Umbraco.Cms.Api.Management.ViewModels.Tree; - - namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Cms.Api.Management.Services.Signs; [TestFixture] @@ -14,10 +15,31 @@ internal class HasCollectionSignProviderTests [Test] public async Task HasCollectionSignProvider_Can_Provide_Tree_Signs() { - var sut = new HasPendingChangesSignProvider(); + var sut = new HasCollectionSignProvider(); Assert.IsTrue(sut.CanProvideSigns()); } + [Test] + public async Task HasCollectionSignProvider_Can_Provide_Collection_Signs() + { + var sut = new HasCollectionSignProvider(); + Assert.IsTrue(sut.CanProvideSigns()); + } + + [Test] + public async Task HasCollectionSignProvider_Can_Provide_Media_Tree_Signs() + { + var sut = new HasCollectionSignProvider(); + Assert.IsTrue(sut.CanProvideSigns()); + } + + [Test] + public async Task HasCollectionSignProvider_Can_Provide_Media_Collection_Signs() + { + var sut = new HasCollectionSignProvider(); + Assert.IsTrue(sut.CanProvideSigns()); + } + [Test] public async Task HasCollectionSignProvider_Should_Populate_Tree_Signs() { @@ -29,11 +51,82 @@ public async Task HasCollectionSignProvider_Should_Populate_Tree_Signs() { Id = Guid.NewGuid(), DocumentType = new DocumentTypeReferenceResponseModel() { Collection = new ReferenceByIdModel(Guid.NewGuid()) }, }, + new() { Id = Guid.NewGuid() }, + }; + + await sut.PopulateSignsAsync(viewModels); + + Assert.AreEqual(viewModels[0].Signs.Count(), 1); + Assert.AreEqual(viewModels[1].Signs.Count(), 0); + + var signModel = viewModels[0].Signs.First(); + Assert.AreEqual("Umb.HasCollection", signModel.Alias); + } + + [Test] + public async Task HasCollectionSignProvider_Should_Populate_Collection_Signs() + { + var sut = new HasCollectionSignProvider(); + + var viewModels = new List + { + new() + { + Id = Guid.NewGuid(), DocumentType = new DocumentTypeCollectionReferenceResponseModel() { Collection = new ReferenceByIdModel(Guid.NewGuid()) }, + }, + new() { Id = Guid.NewGuid() }, + }; + + await sut.PopulateSignsAsync(viewModels); + + Assert.AreEqual(viewModels[0].Signs.Count(), 1); + Assert.AreEqual(viewModels[1].Signs.Count(), 0); + + var signModel = viewModels[0].Signs.First(); + Assert.AreEqual("Umb.HasCollection", signModel.Alias); + } + + [Test] + public async Task HasCollectionSignProvider_Should_Populate_Media_Tree_Signs() + { + var sut = new HasCollectionSignProvider(); + + var viewModels = new List + { + new() + { + Id = Guid.NewGuid(), MediaType = new MediaTypeReferenceResponseModel() { Collection = new ReferenceByIdModel(Guid.NewGuid()) }, + }, + new() { Id = Guid.NewGuid() }, + }; + + await sut.PopulateSignsAsync(viewModels); + + Assert.AreEqual(viewModels[0].Signs.Count(), 1); + Assert.AreEqual(viewModels[1].Signs.Count(), 0); + + var signModel = viewModels[0].Signs.First(); + Assert.AreEqual("Umb.HasCollection", signModel.Alias); + } + + [Test] + public async Task HasCollectionSignProvider_Should_Populate_Media_Collection_Signs() + { + var sut = new HasCollectionSignProvider(); + + var viewModels = new List + { + new() + { + Id = Guid.NewGuid(), MediaType = new MediaTypeCollectionReferenceResponseModel() { Collection = new ReferenceByIdModel(Guid.NewGuid()) }, + }, + new() { Id = Guid.NewGuid() }, }; await sut.PopulateSignsAsync(viewModels); Assert.AreEqual(viewModels[0].Signs.Count(), 1); + Assert.AreEqual(viewModels[1].Signs.Count(), 0); var signModel = viewModels[0].Signs.First(); Assert.AreEqual("Umb.HasCollection", signModel.Alias); From b016c0191701dffd9501babd66bf0890efd30e02 Mon Sep 17 00:00:00 2001 From: NillasKA Date: Fri, 15 Aug 2025 13:37:20 +0200 Subject: [PATCH 21/22] Introducing Plain Items and tests. Refactoring tests as well --- .../Item/ItemDocumentItemController.cs | 27 ++++++- .../Signs/HasCollectionSignProvider.cs | 12 +++- .../Signs/HasPendingChangesSignProvider.cs | 20 ++++-- .../Services/Signs/HasScheduleSignProvider.cs | 4 +- .../Services/Signs/IsProtectedSignProvider.cs | 4 +- .../Item/DocumentItemResponseModel.cs | 1 - .../ViewModels/Item/ItemResponseModelBase.cs | 10 ++- .../Signs/HasCollectionSignProviderTests.cs | 31 ++++++++ .../HasPendingChangesSignProviderTests.cs | 39 ++++++++++ .../Signs/HasScheduleSignProviderTests.cs | 72 +++++++++++++++---- .../Signs/IsProtectedSignProviderTests.cs | 47 ++++++++++-- 11 files changed, 239 insertions(+), 28 deletions(-) diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Document/Item/ItemDocumentItemController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Document/Item/ItemDocumentItemController.cs index ebefb5bfb283..afb6575c42aa 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Document/Item/ItemDocumentItemController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Document/Item/ItemDocumentItemController.cs @@ -1,8 +1,11 @@ using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Api.Management.Factories; +using Umbraco.Cms.Api.Management.Services.Signs; using Umbraco.Cms.Api.Management.ViewModels.Document.Item; +using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Entities; using Umbraco.Cms.Core.Services; @@ -14,11 +17,24 @@ public class ItemDocumentItemController : DocumentItemControllerBase { private readonly IEntityService _entityService; private readonly IDocumentPresentationFactory _documentPresentationFactory; + private readonly SignProviderCollection _signProviders; - public ItemDocumentItemController(IEntityService entityService, IDocumentPresentationFactory documentPresentationFactory) + public ItemDocumentItemController( + IEntityService entityService, + IDocumentPresentationFactory documentPresentationFactory, + SignProviderCollection signProvider) { _entityService = entityService; _documentPresentationFactory = documentPresentationFactory; + _signProviders = signProvider; + } + + [Obsolete("Please use the constructor with all parameters. Scheduled for removal in V18")] + public ItemDocumentItemController( + IEntityService entityService, + IDocumentPresentationFactory documentPresentationFactory) + : this(entityService, documentPresentationFactory, StaticServiceProvider.Instance.GetRequiredService()) + { } [HttpGet] @@ -38,6 +54,15 @@ public Task Item( .OfType(); IEnumerable documentItemResponseModels = documents.Select(_documentPresentationFactory.CreateItemResponseModel); + PopulateSigns(documentItemResponseModels).GetAwaiter().GetResult(); return Task.FromResult(Ok(documentItemResponseModels)); } + + protected async Task PopulateSigns(IEnumerable itemViewModels) + { + foreach (ISignProvider signProvider in _signProviders.Where(x => x.CanProvideSigns())) + { + await signProvider.PopulateSignsAsync(itemViewModels); + } + } } diff --git a/src/Umbraco.Cms.Api.Management/Services/Signs/HasCollectionSignProvider.cs b/src/Umbraco.Cms.Api.Management/Services/Signs/HasCollectionSignProvider.cs index 759216c56ec1..9a64bdc7da7d 100644 --- a/src/Umbraco.Cms.Api.Management/Services/Signs/HasCollectionSignProvider.cs +++ b/src/Umbraco.Cms.Api.Management/Services/Signs/HasCollectionSignProvider.cs @@ -1,5 +1,6 @@ using Umbraco.Cms.Api.Management.ViewModels; using Umbraco.Cms.Api.Management.ViewModels.Document.Collection; +using Umbraco.Cms.Api.Management.ViewModels.Document.Item; using Umbraco.Cms.Api.Management.ViewModels.Media.Collection; using Umbraco.Cms.Api.Management.ViewModels.Tree; using Umbraco.Cms.Core; @@ -19,7 +20,8 @@ public bool CanProvideSigns() typeof(TItem) == typeof(DocumentTreeItemResponseModel) || typeof(TItem) == typeof(DocumentCollectionResponseModel) || typeof(TItem) == typeof(MediaTreeItemResponseModel) || - typeof(TItem) == typeof(MediaCollectionResponseModel); + typeof(TItem) == typeof(MediaCollectionResponseModel) || + typeof(TItem) == typeof(DocumentItemResponseModel); /// public Task PopulateSignsAsync(IEnumerable itemViewModels) @@ -59,6 +61,14 @@ public Task PopulateSignsAsync(IEnumerable itemViewModels) item.AddSign(Alias); } + break; + + case DocumentItemResponseModel response: + if (response.DocumentType.Collection != null) + { + item.AddSign(Alias); + } + break; } } diff --git a/src/Umbraco.Cms.Api.Management/Services/Signs/HasPendingChangesSignProvider.cs b/src/Umbraco.Cms.Api.Management/Services/Signs/HasPendingChangesSignProvider.cs index af083f5b826b..b4c33b5a0caa 100644 --- a/src/Umbraco.Cms.Api.Management/Services/Signs/HasPendingChangesSignProvider.cs +++ b/src/Umbraco.Cms.Api.Management/Services/Signs/HasPendingChangesSignProvider.cs @@ -1,6 +1,7 @@ using Umbraco.Cms.Api.Management.ViewModels; using Umbraco.Cms.Api.Management.ViewModels.Document; using Umbraco.Cms.Api.Management.ViewModels.Document.Collection; +using Umbraco.Cms.Api.Management.ViewModels.Document.Item; using Umbraco.Cms.Api.Management.ViewModels.Tree; using Umbraco.Cms.Core; @@ -17,7 +18,8 @@ public class HasPendingChangesSignProvider : ISignProvider public bool CanProvideSigns() where TItem : IHasSigns => typeof(TItem) == typeof(DocumentTreeItemResponseModel) || - typeof(TItem) == typeof(DocumentCollectionResponseModel); + typeof(TItem) == typeof(DocumentCollectionResponseModel) || + typeof(TItem) == typeof(DocumentItemResponseModel); /// public Task PopulateSignsAsync(IEnumerable itemViewModels) @@ -27,16 +29,24 @@ public Task PopulateSignsAsync(IEnumerable itemViewModels) { switch (item) { - case DocumentTreeItemResponseModel treeItem: - if (treeItem.Variants.Any(variant => variant.State == DocumentVariantState.PublishedPendingChanges)) + case DocumentTreeItemResponseModel response: + if (response.Variants.Any(variant => variant.State == DocumentVariantState.PublishedPendingChanges)) { item.AddSign(Alias); } break; - case DocumentCollectionResponseModel collectionItem: - if (collectionItem.Variants.Any(variant => variant.State == DocumentVariantState.PublishedPendingChanges)) + case DocumentCollectionResponseModel response: + if (response.Variants.Any(variant => variant.State == DocumentVariantState.PublishedPendingChanges)) + { + item.AddSign(Alias); + } + + break; + + case DocumentItemResponseModel response: + if (response.Variants.Any(variant => variant.State == DocumentVariantState.PublishedPendingChanges)) { item.AddSign(Alias); } diff --git a/src/Umbraco.Cms.Api.Management/Services/Signs/HasScheduleSignProvider.cs b/src/Umbraco.Cms.Api.Management/Services/Signs/HasScheduleSignProvider.cs index 3a027743f62d..599d10ae67e5 100644 --- a/src/Umbraco.Cms.Api.Management/Services/Signs/HasScheduleSignProvider.cs +++ b/src/Umbraco.Cms.Api.Management/Services/Signs/HasScheduleSignProvider.cs @@ -1,5 +1,6 @@ using Umbraco.Cms.Api.Management.ViewModels; using Umbraco.Cms.Api.Management.ViewModels.Document.Collection; +using Umbraco.Cms.Api.Management.ViewModels.Document.Item; using Umbraco.Cms.Api.Management.ViewModels.Tree; using Umbraco.Cms.Core.Services; using Constants = Umbraco.Cms.Core.Constants; @@ -24,7 +25,8 @@ internal class HasScheduleSignProvider : ISignProvider public bool CanProvideSigns() where TItem : IHasSigns => typeof(TItem) == typeof(DocumentTreeItemResponseModel) || - typeof(TItem) == typeof(DocumentCollectionResponseModel); + typeof(TItem) == typeof(DocumentCollectionResponseModel) || + typeof(TItem) == typeof(DocumentItemResponseModel); /// public Task PopulateSignsAsync(IEnumerable itemViewModels) diff --git a/src/Umbraco.Cms.Api.Management/Services/Signs/IsProtectedSignProvider.cs b/src/Umbraco.Cms.Api.Management/Services/Signs/IsProtectedSignProvider.cs index ff5df286bb4e..fd649a7cb7e1 100644 --- a/src/Umbraco.Cms.Api.Management/Services/Signs/IsProtectedSignProvider.cs +++ b/src/Umbraco.Cms.Api.Management/Services/Signs/IsProtectedSignProvider.cs @@ -1,5 +1,6 @@ using Umbraco.Cms.Api.Management.ViewModels; using Umbraco.Cms.Api.Management.ViewModels.Document.Collection; +using Umbraco.Cms.Api.Management.ViewModels.Document.Item; using Umbraco.Cms.Api.Management.ViewModels.Tree; using Umbraco.Cms.Core; @@ -16,7 +17,8 @@ internal class IsProtectedSignProvider : ISignProvider public bool CanProvideSigns() where TItem : IHasSigns => typeof(TItem) == typeof(DocumentTreeItemResponseModel) || - typeof(TItem) == typeof(DocumentCollectionResponseModel); + typeof(TItem) == typeof(DocumentCollectionResponseModel) || + typeof(TItem) == typeof(DocumentItemResponseModel); /// > public Task PopulateSignsAsync(IEnumerable itemViewModels) diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/Document/Item/DocumentItemResponseModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/Document/Item/DocumentItemResponseModel.cs index 264d466598ee..4301b8096d55 100644 --- a/src/Umbraco.Cms.Api.Management/ViewModels/Document/Item/DocumentItemResponseModel.cs +++ b/src/Umbraco.Cms.Api.Management/ViewModels/Document/Item/DocumentItemResponseModel.cs @@ -1,4 +1,3 @@ -using Umbraco.Cms.Api.Management.ViewModels.Content; using Umbraco.Cms.Api.Management.ViewModels.DocumentType; using Umbraco.Cms.Api.Management.ViewModels.Item; diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/Item/ItemResponseModelBase.cs b/src/Umbraco.Cms.Api.Management/ViewModels/Item/ItemResponseModelBase.cs index f39ee8c57829..01622557fd9b 100644 --- a/src/Umbraco.Cms.Api.Management/ViewModels/Item/ItemResponseModelBase.cs +++ b/src/Umbraco.Cms.Api.Management/ViewModels/Item/ItemResponseModelBase.cs @@ -1,6 +1,14 @@ namespace Umbraco.Cms.Api.Management.ViewModels.Item; -public abstract class ItemResponseModelBase +public abstract class ItemResponseModelBase : IHasSigns { + private readonly List _signs = []; + public Guid Id { get; set; } + + public IEnumerable Signs => _signs.AsEnumerable(); + + public void AddSign(string alias) => _signs.Add(new SignModel { Alias = alias }); + + public void RemoveSign(string alias) => _signs.RemoveAll(x => x.Alias == alias); } diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Cms.Api.Management/Services/Signs/HasCollectionSignProviderTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Cms.Api.Management/Services/Signs/HasCollectionSignProviderTests.cs index 2e9c2242bbc8..b9e72c25dc10 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Cms.Api.Management/Services/Signs/HasCollectionSignProviderTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Cms.Api.Management/Services/Signs/HasCollectionSignProviderTests.cs @@ -2,6 +2,7 @@ using Umbraco.Cms.Api.Management.Services.Signs; using Umbraco.Cms.Api.Management.ViewModels; using Umbraco.Cms.Api.Management.ViewModels.Document.Collection; +using Umbraco.Cms.Api.Management.ViewModels.Document.Item; using Umbraco.Cms.Api.Management.ViewModels.DocumentType; using Umbraco.Cms.Api.Management.ViewModels.Media.Collection; using Umbraco.Cms.Api.Management.ViewModels.MediaType; @@ -26,6 +27,13 @@ public async Task HasCollectionSignProvider_Can_Provide_Collection_Signs() Assert.IsTrue(sut.CanProvideSigns()); } + [Test] + public async Task HasCollectionSignProvider_Can_Provide_Plain_Signs() + { + var sut = new HasCollectionSignProvider(); + Assert.IsTrue(sut.CanProvideSigns()); + } + [Test] public async Task HasCollectionSignProvider_Can_Provide_Media_Tree_Signs() { @@ -86,6 +94,29 @@ public async Task HasCollectionSignProvider_Should_Populate_Collection_Signs() Assert.AreEqual("Umb.HasCollection", signModel.Alias); } + [Test] + public async Task HasCollectionSignProvider_Should_Populate_Plain_Signs() + { + var sut = new HasCollectionSignProvider(); + + var viewModels = new List + { + new() + { + Id = Guid.NewGuid(), DocumentType = new DocumentTypeReferenceResponseModel() { Collection = new ReferenceByIdModel(Guid.NewGuid()) }, + }, + new() { Id = Guid.NewGuid() }, + }; + + await sut.PopulateSignsAsync(viewModels); + + Assert.AreEqual(viewModels[0].Signs.Count(), 1); + Assert.AreEqual(viewModels[1].Signs.Count(), 0); + + var signModel = viewModels[0].Signs.First(); + Assert.AreEqual("Umb.HasCollection", signModel.Alias); + } + [Test] public async Task HasCollectionSignProvider_Should_Populate_Media_Tree_Signs() { diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Cms.Api.Management/Services/Signs/HasPendingChangesSignProviderTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Cms.Api.Management/Services/Signs/HasPendingChangesSignProviderTests.cs index 4ab7642edf14..f694faa17d06 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Cms.Api.Management/Services/Signs/HasPendingChangesSignProviderTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Cms.Api.Management/Services/Signs/HasPendingChangesSignProviderTests.cs @@ -2,6 +2,7 @@ using Umbraco.Cms.Api.Management.Services.Signs; using Umbraco.Cms.Api.Management.ViewModels.Document; using Umbraco.Cms.Api.Management.ViewModels.Document.Collection; +using Umbraco.Cms.Api.Management.ViewModels.Document.Item; using Umbraco.Cms.Api.Management.ViewModels.Tree; @@ -25,6 +26,13 @@ public async Task HasPendingChangesSignProvider_Can_Provide_Collection_Signs() Assert.IsTrue(sut.CanProvideSigns()); } + [Test] + public async Task HasPendingChangesSignProvider_Can_Provide_Plain_Signs() + { + var sut = new HasPendingChangesSignProvider(); + Assert.IsTrue(sut.CanProvideSigns()); + } + [Test] public async Task HasPendingChangesSignProvider_Should_Populate_Tree_Signs() { @@ -86,4 +94,35 @@ public async Task HasPendingChangesSignProvider_Should_Populate_Collection_Signs var signModel = viewModels[1].Signs.First(); Assert.AreEqual("Umb.PendingChanges", signModel.Alias); } + + [Test] + public async Task HasPendingChangesSignProvider_Should_Populate_Plain_Signs() + { + var sut = new HasPendingChangesSignProvider(); + + var viewModels = new List + { + new() { Id = Guid.NewGuid() }, + new() + { + Id = Guid.NewGuid(), Variants = new List + { + new() + { + State = DocumentVariantState.PublishedPendingChanges, + Culture = null, + Name = "Test", + }, + }, + }, + }; + + await sut.PopulateSignsAsync(viewModels); + + Assert.AreEqual(viewModels[0].Signs.Count(), 0); + Assert.AreEqual(viewModels[1].Signs.Count(), 1); + + var signModel = viewModels[1].Signs.First(); + Assert.AreEqual("Umb.PendingChanges", signModel.Alias); + } } diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Cms.Api.Management/Services/Signs/HasScheduleSignProviderTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Cms.Api.Management/Services/Signs/HasScheduleSignProviderTests.cs index 4648728d98b6..07e93ebc792b 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Cms.Api.Management/Services/Signs/HasScheduleSignProviderTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Cms.Api.Management/Services/Signs/HasScheduleSignProviderTests.cs @@ -2,6 +2,7 @@ using NUnit.Framework; using Umbraco.Cms.Api.Management.Services.Signs; using Umbraco.Cms.Api.Management.ViewModels.Document.Collection; +using Umbraco.Cms.Api.Management.ViewModels.Document.Item; using Umbraco.Cms.Api.Management.ViewModels.Tree; using Umbraco.Cms.Core.Models.Entities; using Umbraco.Cms.Core.Services; @@ -11,13 +12,39 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Cms.Api.Management.Services.Signs; [TestFixture] internal class HasScheduleSignProviderTests { + [Test] + public async Task HasScheduleSignProvider_Can_Provide_Tree_Signs() + { + var contentServiceMock = new Mock(); + + var sut = new HasScheduleSignProvider(contentServiceMock.Object); + Assert.IsTrue(sut.CanProvideSigns()); + } + + [Test] + public async Task HasScheduleSignProvider_Can_Provide_Collection_Signs() + { + var contentServiceMock = new Mock(); + + var sut = new HasScheduleSignProvider(contentServiceMock.Object); + Assert.IsTrue(sut.CanProvideSigns()); + } + + [Test] + public async Task HasScheduleSignProvider_Can_Provide_Plain_Signs() + { + var contentServiceMock = new Mock(); + + var sut = new HasScheduleSignProvider(contentServiceMock.Object); + Assert.IsTrue(sut.CanProvideSigns()); + } + [Test] public async Task HasScheduleSignProvider_Should_Populate_Tree_Signs() { var entities = new List { - new() { Key = Guid.NewGuid(), Name = "Item 1" }, - new() { Key = Guid.NewGuid(), Name = "Item 2" }, + new() { Key = Guid.NewGuid(), Name = "Item 1" }, new() { Key = Guid.NewGuid(), Name = "Item 2" }, }; var contentServiceMock = new Mock(); @@ -26,12 +53,9 @@ public async Task HasScheduleSignProvider_Should_Populate_Tree_Signs() .Returns([entities[1].Key]); var sut = new HasScheduleSignProvider(contentServiceMock.Object); - Assert.IsTrue(sut.CanProvideSigns()); - var viewModels = new List { - new() { Id = entities[0].Key }, - new() { Id = entities[1].Key }, + new() { Id = entities[0].Key }, new() { Id = entities[1].Key }, }; await sut.PopulateSignsAsync(viewModels); @@ -48,8 +72,7 @@ public async Task HasScheduleSignProvider_Should_Populate_Collection_Signs() { var entities = new List { - new() { Key = Guid.NewGuid(), Name = "Item 1" }, - new() { Key = Guid.NewGuid(), Name = "Item 2" }, + new() { Key = Guid.NewGuid(), Name = "Item 1" }, new() { Key = Guid.NewGuid(), Name = "Item 2" }, }; var contentServiceMock = new Mock(); @@ -58,12 +81,37 @@ public async Task HasScheduleSignProvider_Should_Populate_Collection_Signs() .Returns([entities[1].Key]); var sut = new HasScheduleSignProvider(contentServiceMock.Object); - Assert.IsTrue(sut.CanProvideSigns()); - var viewModels = new List { - new() { Id = entities[0].Key }, - new() { Id = entities[1].Key }, + new() { Id = entities[0].Key }, new() { Id = entities[1].Key }, + }; + + await sut.PopulateSignsAsync(viewModels); + + Assert.AreEqual(viewModels[0].Signs.Count(), 0); + Assert.AreEqual(viewModels[1].Signs.Count(), 1); + + var signModel = viewModels[1].Signs.First(); + Assert.AreEqual("Umb.ScheduledForPublish", signModel.Alias); + } + + [Test] + public async Task HasScheduleSignProvider_Should_Populate_Plain_Signs() + { + var entities = new List + { + new() { Key = Guid.NewGuid(), Name = "Item 1" }, new() { Key = Guid.NewGuid(), Name = "Item 2" }, + }; + + var contentServiceMock = new Mock(); + contentServiceMock + .Setup(x => x.GetScheduledContentKeys(It.IsAny>())) + .Returns([entities[1].Key]); + var sut = new HasScheduleSignProvider(contentServiceMock.Object); + + var viewModels = new List + { + new() { Id = entities[0].Key }, new() { Id = entities[1].Key }, }; await sut.PopulateSignsAsync(viewModels); diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Cms.Api.Management/Services/Signs/IsProtectedSignProviderTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Cms.Api.Management/Services/Signs/IsProtectedSignProviderTests.cs index e3760f2e2029..1660db2723e6 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Cms.Api.Management/Services/Signs/IsProtectedSignProviderTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Cms.Api.Management/Services/Signs/IsProtectedSignProviderTests.cs @@ -1,8 +1,8 @@ using NUnit.Framework; using Umbraco.Cms.Api.Management.Services.Signs; using Umbraco.Cms.Api.Management.ViewModels.Document.Collection; +using Umbraco.Cms.Api.Management.ViewModels.Document.Item; using Umbraco.Cms.Api.Management.ViewModels.Tree; -using Umbraco.Cms.Core.Models.Entities; namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Cms.Api.Management.Services.Signs; @@ -10,11 +10,30 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Cms.Api.Management.Services.Signs; internal class IsProtectedSignProviderTests { [Test] - public async Task IsProtectedSignProvider_Should_Populate_Tree_Signs() + public async Task IsProtectedSignProvider_Can_Provide_Tree_Signs() { var sut = new IsProtectedSignProvider(); - Assert.IsTrue(sut.CanProvideSigns()); + } + + [Test] + public async Task IsProtectedSignProvider_Can_Provide_Collection_Signs() + { + var sut = new IsProtectedSignProvider(); + Assert.IsTrue(sut.CanProvideSigns()); + } + + [Test] + public async Task IsProtectedSignProvider_Can_Provide_Plain_Signs() + { + var sut = new IsProtectedSignProvider(); + Assert.IsTrue(sut.CanProvideSigns()); + } + + [Test] + public async Task IsProtectedSignProvider_Should_Populate_Tree_Signs() + { + var sut = new IsProtectedSignProvider(); var viewModels = new List { @@ -36,8 +55,6 @@ public async Task IsProtectedSignProvider_Should_Populate_Collection_Signs() { var sut = new IsProtectedSignProvider(); - Assert.IsTrue(sut.CanProvideSigns()); - var viewModels = new List { new(), @@ -52,4 +69,24 @@ public async Task IsProtectedSignProvider_Should_Populate_Collection_Signs() var signModel = viewModels[1].Signs.First(); Assert.AreEqual("Umb.IsProtected", signModel.Alias); } + + [Test] + public async Task IsProtectedSignProvider_Should_Populate_Plain_Signs() + { + var sut = new IsProtectedSignProvider(); + + var viewModels = new List + { + new(), + new() { IsProtected = true }, + }; + + await sut.PopulateSignsAsync(viewModels); + + Assert.AreEqual(viewModels[0].Signs.Count(), 0); + Assert.AreEqual(viewModels[1].Signs.Count(), 1); + + var signModel = viewModels[1].Signs.First(); + Assert.AreEqual("Umb.IsProtected", signModel.Alias); + } } From d7dd2c91e46b070de181395ca60fb0e9754a85ae Mon Sep 17 00:00:00 2001 From: NillasKA Date: Fri, 15 Aug 2025 14:37:03 +0200 Subject: [PATCH 22/22] Introduced alternative CanProvideSigns() implementation on IsProtectedSignProvider.cs --- .../Services/Signs/IsProtectedSignProvider.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Umbraco.Cms.Api.Management/Services/Signs/IsProtectedSignProvider.cs b/src/Umbraco.Cms.Api.Management/Services/Signs/IsProtectedSignProvider.cs index fd649a7cb7e1..844599e14fd3 100644 --- a/src/Umbraco.Cms.Api.Management/Services/Signs/IsProtectedSignProvider.cs +++ b/src/Umbraco.Cms.Api.Management/Services/Signs/IsProtectedSignProvider.cs @@ -16,9 +16,7 @@ internal class IsProtectedSignProvider : ISignProvider /// > public bool CanProvideSigns() where TItem : IHasSigns => - typeof(TItem) == typeof(DocumentTreeItemResponseModel) || - typeof(TItem) == typeof(DocumentCollectionResponseModel) || - typeof(TItem) == typeof(DocumentItemResponseModel); + typeof(IIsProtected).IsAssignableFrom(typeof(TItem)); /// > public Task PopulateSignsAsync(IEnumerable itemViewModels)