diff --git a/src/Umbraco.Cms.Api.Management/Controllers/DynamicRoot/DynamicRootControllerBase.cs b/src/Umbraco.Cms.Api.Management/Controllers/DynamicRoot/DynamicRootControllerBase.cs new file mode 100644 index 0000000000..149d62ced7 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Controllers/DynamicRoot/DynamicRootControllerBase.cs @@ -0,0 +1,11 @@ +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Api.Management.Routing; + +namespace Umbraco.Cms.Api.Management.Controllers.DynamicRoot; + +[ApiController] +[VersionedApiBackOfficeRoute("dynamic-root")] +[ApiExplorerSettings(GroupName = "Dynamic Root")] +public abstract class DynamicRootControllerBase : ManagementApiControllerBase +{ +} diff --git a/src/Umbraco.Cms.Api.Management/Controllers/DynamicRoot/GetDynamicRootController.cs b/src/Umbraco.Cms.Api.Management/Controllers/DynamicRoot/GetDynamicRootController.cs new file mode 100644 index 0000000000..37a1d681f2 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Controllers/DynamicRoot/GetDynamicRootController.cs @@ -0,0 +1,44 @@ +using Asp.Versioning; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Api.Management.ViewModels.DynamicRoot; +using Umbraco.Cms.Core.DynamicRoot; +using Umbraco.Cms.Core.Mapping; +using Umbraco.Cms.Core.Models.Context; +using Umbraco.Cms.Web.Common.Authorization; + +namespace Umbraco.Cms.Api.Management.Controllers.DynamicRoot; + +[Authorize(Policy = "New" + AuthorizationPolicies.SectionAccessContent)] +[ApiVersion("1.0")] +public class GetRootsController : DynamicRootControllerBase +{ + private readonly IDynamicRootService _dynamicRootService; + private readonly IUmbracoMapper _umbracoMapper; + private readonly IBackOfficeVariationContextAccessor _backOfficeVariationContextAccessor; + + public GetRootsController(IDynamicRootService dynamicRootService, IUmbracoMapper umbracoMapper, IBackOfficeVariationContextAccessor backOfficeVariationContextAccessor) + { + _dynamicRootService = dynamicRootService; + _umbracoMapper = umbracoMapper; + _backOfficeVariationContextAccessor = backOfficeVariationContextAccessor; + } + + [HttpPost("query")] + [MapToApiVersion("1.0")] + [ProducesResponseType(typeof(DynamicRootResponseModel), StatusCodes.Status200OK)] + public async Task GetRoots(DynamicRootRequestModel model) + { + _backOfficeVariationContextAccessor.VariationContext = new BackOfficeVariationContext(model.Context.Culture, model.Context.Segment); + + DynamicRootNodeQuery dynamicRootNodeQuery = _umbracoMapper.Map(model)!; + + IEnumerable roots = await _dynamicRootService.GetDynamicRootsAsync(dynamicRootNodeQuery); + + return Ok(new DynamicRootResponseModel() + { + Roots = roots + }); + } +} diff --git a/src/Umbraco.Cms.Api.Management/Controllers/DynamicRoot/GetQueryStepsController.cs b/src/Umbraco.Cms.Api.Management/Controllers/DynamicRoot/GetQueryStepsController.cs new file mode 100644 index 0000000000..9b97a29b96 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Controllers/DynamicRoot/GetQueryStepsController.cs @@ -0,0 +1,30 @@ +using Asp.Versioning; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Core.DynamicRoot.QuerySteps; +using Umbraco.Cms.Web.Common.Authorization; + +namespace Umbraco.Cms.Api.Management.Controllers.DynamicRoot; + +[Authorize(Policy = "New" + AuthorizationPolicies.SectionAccessSettings)] +[ApiVersion("1.0")] +public class GetQueryStepsController : DynamicRootControllerBase +{ + private readonly DynamicRootQueryStepCollection _dynamicRootQueryStepCollection; + + public GetQueryStepsController(DynamicRootQueryStepCollection dynamicRootQueryStepCollection) + { + _dynamicRootQueryStepCollection = dynamicRootQueryStepCollection; + } + + [HttpGet($"steps")] + [MapToApiVersion("1.0")] + [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] + public async Task GetQuerySteps() + { + IEnumerable querySteps = _dynamicRootQueryStepCollection.Select(x => x.SupportedDirectionAlias); + + return await Task.FromResult(Ok(querySteps)); + } +} diff --git a/src/Umbraco.Cms.Api.Management/DependencyInjection/DynamicRootBuilderExtensions.cs b/src/Umbraco.Cms.Api.Management/DependencyInjection/DynamicRootBuilderExtensions.cs new file mode 100644 index 0000000000..6241c6ebc6 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/DependencyInjection/DynamicRootBuilderExtensions.cs @@ -0,0 +1,18 @@ +using Umbraco.Cms.Api.Management.Mapping.Culture; +using Umbraco.Cms.Api.Management.Mapping.DynamicRoot; +using Umbraco.Cms.Api.Management.Mapping.Language; +using Umbraco.Cms.Core.DependencyInjection; +using Umbraco.Cms.Core.Mapping; + +namespace Umbraco.Cms.Api.Management.DependencyInjection; + +internal static class DynamicRootBuilderExtensions +{ + internal static IUmbracoBuilder AddDynamicRoot(this IUmbracoBuilder builder) + { + builder.WithCollectionBuilder() + .Add(); + + return builder; + } +} diff --git a/src/Umbraco.Cms.Api.Management/DependencyInjection/UmbracoBuilderExtensions.cs b/src/Umbraco.Cms.Api.Management/DependencyInjection/UmbracoBuilderExtensions.cs index 2346e3275c..938b9808f2 100644 --- a/src/Umbraco.Cms.Api.Management/DependencyInjection/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.Cms.Api.Management/DependencyInjection/UmbracoBuilderExtensions.cs @@ -39,6 +39,7 @@ public static class UmbracoBuilderExtensions .AddTags() .AddTrackedReferences() .AddTemporaryFiles() + .AddDynamicRoot() .AddDataTypes() .AddTemplates() .AddRelationTypes() diff --git a/src/Umbraco.Cms.Api.Management/Mapping/DynamicRoot/DynamicRootMapDefinition.cs b/src/Umbraco.Cms.Api.Management/Mapping/DynamicRoot/DynamicRootMapDefinition.cs new file mode 100644 index 0000000000..0c987a2045 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Mapping/DynamicRoot/DynamicRootMapDefinition.cs @@ -0,0 +1,30 @@ +using Umbraco.Cms.Api.Management.ViewModels.DynamicRoot; +using Umbraco.Cms.Core.DynamicRoot; +using Umbraco.Cms.Core.DynamicRoot.QuerySteps; +using Umbraco.Cms.Core.Mapping; + +namespace Umbraco.Cms.Api.Management.Mapping.DynamicRoot; + +/// +public class DynamicRootMapDefinition : IMapDefinition +{ + /// + public void DefineMaps(IUmbracoMapper mapper) => mapper.Define((source, context) => new DynamicRootNodeQuery { OriginAlias = null!, Context = default }, Map); + + // Umbraco.Code.MapAll + private static void Map(DynamicRootRequestModel source, DynamicRootNodeQuery target, MapperContext context) + { + target.Context = new DynamicRootContext() + { + CurrentKey = source.Context.Id, + ParentKey = source.Context.ParentId + }; + target.OriginKey = source.Query.Origin.Key; + target.OriginAlias = source.Query.Origin.Alias; + target.QuerySteps = source.Query.Steps.Select(x => new DynamicRootQueryStep() + { + Alias = x.Alias, + AnyOfDocTypeKeys = x.DocumentTypeIds + }); + } +} diff --git a/src/Umbraco.Cms.Api.Management/OpenApi.json b/src/Umbraco.Cms.Api.Management/OpenApi.json index 158433df74..f6f12cb1da 100644 --- a/src/Umbraco.Cms.Api.Management/OpenApi.json +++ b/src/Umbraco.Cms.Api.Management/OpenApi.json @@ -5752,6 +5752,134 @@ ] } }, + "/umbraco/management/api/v1/dynamic-root/query": { + "post": { + "tags": [ + "Dynamic Root" + ], + "operationId": "PostDynamicRootQuery", + "requestBody": { + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/DynamicRootRequestModel" + } + ] + } + }, + "text/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/DynamicRootRequestModel" + } + ] + } + }, + "application/*+json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/DynamicRootRequestModel" + } + ] + } + } + } + }, + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/DynamicRootResponseModel" + } + ] + } + }, + "text/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/DynamicRootResponseModel" + } + ] + } + }, + "text/plain": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/DynamicRootResponseModel" + } + ] + } + } + } + }, + "401": { + "description": "The resource is protected and requires an authentication token" + } + }, + "security": [ + { + "Backoffice User": [ ] + } + ] + } + }, + "/umbraco/management/api/v1/dynamic-root/steps": { + "get": { + "tags": [ + "Dynamic Root" + ], + "operationId": "GetDynamicRootSteps", + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "text/json": { + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "text/plain": { + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + }, + "401": { + "description": "The resource is protected and requires an authentication token" + } + }, + "security": [ + { + "Backoffice User": [ ] + } + ] + } + }, "/umbraco/management/api/v1/health-check-group": { "get": { "tags": [ @@ -13686,6 +13814,56 @@ ] } }, + "/umbraco/management/api/v1/security/configuration": { + "get": { + "tags": [ + "Security" + ], + "operationId": "GetSecurityConfiguration", + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/SecurityConfigurationResponseModel" + } + ] + } + }, + "text/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/SecurityConfigurationResponseModel" + } + ] + } + }, + "text/plain": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/SecurityConfigurationResponseModel" + } + ] + } + } + } + }, + "401": { + "description": "The resource is protected and requires an authentication token" + } + }, + "security": [ + { + "Backoffice User": [ ] + } + ] + } + }, "/umbraco/management/api/v1/security/forgot-password": { "post": { "tags": [ @@ -18295,6 +18473,56 @@ ] } }, + "/umbraco/management/api/v1/user/configuration": { + "get": { + "tags": [ + "User" + ], + "operationId": "GetUserConfiguration", + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/UserConfigurationResponseModel" + } + ] + } + }, + "text/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/UserConfigurationResponseModel" + } + ] + } + }, + "text/plain": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/UserConfigurationResponseModel" + } + ] + } + } + } + }, + "401": { + "description": "The resource is protected and requires an authentication token" + } + }, + "security": [ + { + "Backoffice User": [ ] + } + ] + } + }, "/umbraco/management/api/v1/user/current": { "get": { "tags": [ @@ -18455,6 +18683,56 @@ ] } }, + "/umbraco/management/api/v1/user/current/configuration": { + "get": { + "tags": [ + "User" + ], + "operationId": "GetUserCurrentConfiguration", + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/CurrenUserConfigurationResponseModel" + } + ] + } + }, + "text/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/CurrenUserConfigurationResponseModel" + } + ] + } + }, + "text/plain": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/CurrenUserConfigurationResponseModel" + } + ] + } + } + } + }, + "401": { + "description": "The resource is protected and requires an authentication token" + } + }, + "security": [ + { + "Backoffice User": [ ] + } + ] + } + }, "/umbraco/management/api/v1/user/current/logins": { "get": { "tags": [ @@ -20623,6 +20901,30 @@ }, "additionalProperties": false }, + "CurrenUserConfigurationResponseModel": { + "required": [ + "keepUserLoggedIn", + "passwordConfiguration", + "usernameIsEmail" + ], + "type": "object", + "properties": { + "keepUserLoggedIn": { + "type": "boolean" + }, + "usernameIsEmail": { + "type": "boolean" + }, + "passwordConfiguration": { + "oneOf": [ + { + "$ref": "#/components/schemas/PasswordConfigurationResponseModel" + } + ] + } + }, + "additionalProperties": false + }, "CurrentUserResponseModel": { "required": [ "avatarUrls", @@ -21438,6 +21740,136 @@ ], "additionalProperties": false }, + "DynamicRootContextRequestModel": { + "required": [ + "parentId" + ], + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "parentId": { + "type": "string", + "format": "uuid" + }, + "culture": { + "type": "string", + "nullable": true + }, + "segment": { + "type": "string", + "nullable": true + } + }, + "additionalProperties": false + }, + "DynamicRootQueryOriginRequestModel": { + "required": [ + "alias" + ], + "type": "object", + "properties": { + "alias": { + "type": "string" + }, + "key": { + "type": "string", + "format": "uuid", + "nullable": true + } + }, + "additionalProperties": false + }, + "DynamicRootQueryRequestModel": { + "required": [ + "origin", + "steps" + ], + "type": "object", + "properties": { + "origin": { + "oneOf": [ + { + "$ref": "#/components/schemas/DynamicRootQueryOriginRequestModel" + } + ] + }, + "steps": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#/components/schemas/DynamicRootQueryStepRequestModel" + } + ] + } + } + }, + "additionalProperties": false + }, + "DynamicRootQueryStepRequestModel": { + "required": [ + "alias", + "documentTypeIds" + ], + "type": "object", + "properties": { + "alias": { + "type": "string" + }, + "documentTypeIds": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + } + }, + "additionalProperties": false + }, + "DynamicRootRequestModel": { + "required": [ + "context", + "query" + ], + "type": "object", + "properties": { + "context": { + "oneOf": [ + { + "$ref": "#/components/schemas/DynamicRootContextRequestModel" + } + ] + }, + "query": { + "oneOf": [ + { + "$ref": "#/components/schemas/DynamicRootQueryRequestModel" + } + ] + } + }, + "additionalProperties": false + }, + "DynamicRootResponseModel": { + "required": [ + "roots" + ], + "type": "object", + "properties": { + "roots": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + } + }, + "additionalProperties": false + }, "EnableUserRequestModel": { "required": [ "userIds" @@ -23707,6 +24139,35 @@ ], "additionalProperties": false }, + "PasswordConfigurationResponseModel": { + "required": [ + "minimumPasswordLength", + "requireDigit", + "requireLowercase", + "requireNonLetterOrDigit", + "requireUppercase" + ], + "type": "object", + "properties": { + "minimumPasswordLength": { + "type": "integer", + "format": "int32" + }, + "requireNonLetterOrDigit": { + "type": "boolean" + }, + "requireDigit": { + "type": "boolean" + }, + "requireLowercase": { + "type": "boolean" + }, + "requireUppercase": { + "type": "boolean" + } + }, + "additionalProperties": false + }, "PathFolderModelBaseModel": { "type": "object", "allOf": [ @@ -24534,6 +24995,22 @@ }, "additionalProperties": false }, + "SecurityConfigurationResponseModel": { + "required": [ + "passwordConfiguration" + ], + "type": "object", + "properties": { + "passwordConfiguration": { + "oneOf": [ + { + "$ref": "#/components/schemas/PasswordConfigurationResponseModel" + } + ] + } + }, + "additionalProperties": false + }, "ServerConfigurationBaseModel": { "required": [ "items" @@ -25816,6 +26293,26 @@ }, "additionalProperties": false }, + "UserConfigurationResponseModel": { + "required": [ + "canInviteUsers", + "passwordConfiguration" + ], + "type": "object", + "properties": { + "canInviteUsers": { + "type": "boolean" + }, + "passwordConfiguration": { + "oneOf": [ + { + "$ref": "#/components/schemas/PasswordConfigurationResponseModel" + } + ] + } + }, + "additionalProperties": false + }, "UserGroupBaseModel": { "required": [ "documentRootAccess", diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/DynamicRoot/DynamicRootContextRequestModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/DynamicRoot/DynamicRootContextRequestModel.cs new file mode 100644 index 0000000000..e02c9d71bc --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/ViewModels/DynamicRoot/DynamicRootContextRequestModel.cs @@ -0,0 +1,12 @@ +namespace Umbraco.Cms.Api.Management.ViewModels.DynamicRoot; + +public class DynamicRootContextRequestModel +{ + public Guid? Id { get; set; } + + public required Guid ParentId { get; set; } + + public string? Culture { get; set; } + + public string? Segment { get; set; } +} diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/DynamicRoot/DynamicRootQueryOriginRequestModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/DynamicRoot/DynamicRootQueryOriginRequestModel.cs new file mode 100644 index 0000000000..f09976c12e --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/ViewModels/DynamicRoot/DynamicRootQueryOriginRequestModel.cs @@ -0,0 +1,8 @@ +namespace Umbraco.Cms.Api.Management.ViewModels.DynamicRoot; + +public class DynamicRootQueryOriginRequestModel +{ + public required string Alias { get; set; } + + public Guid? Key { get; set; } +} diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/DynamicRoot/DynamicRootQueryRequestModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/DynamicRoot/DynamicRootQueryRequestModel.cs new file mode 100644 index 0000000000..cc023c6c47 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/ViewModels/DynamicRoot/DynamicRootQueryRequestModel.cs @@ -0,0 +1,8 @@ +namespace Umbraco.Cms.Api.Management.ViewModels.DynamicRoot; + +public class DynamicRootQueryRequestModel +{ + public required DynamicRootQueryOriginRequestModel Origin { get; set; } + + public required IEnumerable Steps { get; set; } +} diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/DynamicRoot/DynamicRootQueryStepRequestModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/DynamicRoot/DynamicRootQueryStepRequestModel.cs new file mode 100644 index 0000000000..6548c62efc --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/ViewModels/DynamicRoot/DynamicRootQueryStepRequestModel.cs @@ -0,0 +1,8 @@ +namespace Umbraco.Cms.Api.Management.ViewModels.DynamicRoot; + +public class DynamicRootQueryStepRequestModel +{ + public required string Alias { get; set; } + + public required IEnumerable DocumentTypeIds { get; set; } +} diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/DynamicRoot/DynamicRootRequestModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/DynamicRoot/DynamicRootRequestModel.cs index cf0c171485..b39ac75c9c 100644 --- a/src/Umbraco.Cms.Api.Management/ViewModels/DynamicRoot/DynamicRootRequestModel.cs +++ b/src/Umbraco.Cms.Api.Management/ViewModels/DynamicRoot/DynamicRootRequestModel.cs @@ -2,5 +2,7 @@ namespace Umbraco.Cms.Api.Management.ViewModels.DynamicRoot; public class DynamicRootRequestModel { - + public required DynamicRootContextRequestModel Context { get; set; } + + public required DynamicRootQueryRequestModel Query { get; set; } } diff --git a/src/Umbraco.Core/DynamicRoot/QuerySteps/FurthestAncestorOrSelfDynamicRootQueryStep.cs b/src/Umbraco.Core/DynamicRoot/QuerySteps/FurthestAncestorOrSelfDynamicRootQueryStep.cs index c7da470baf..0b6c268b78 100644 --- a/src/Umbraco.Core/DynamicRoot/QuerySteps/FurthestAncestorOrSelfDynamicRootQueryStep.cs +++ b/src/Umbraco.Core/DynamicRoot/QuerySteps/FurthestAncestorOrSelfDynamicRootQueryStep.cs @@ -15,7 +15,7 @@ public class FurthestAncestorOrSelfDynamicRootQueryStep : IDynamicRootQueryStep _nodeFilterRepository = nodeFilterRepository; } - protected virtual string SupportedDirectionAlias { get; set; } = "FurthestAncestorOrSelf"; + public virtual string SupportedDirectionAlias { get; set; } = "FurthestAncestorOrSelf"; public async Task>> ExecuteAsync(ICollection origins, DynamicRootQueryStep filter) { diff --git a/src/Umbraco.Core/DynamicRoot/QuerySteps/FurthestDescendantOrSelfDynamicRootQueryStep.cs b/src/Umbraco.Core/DynamicRoot/QuerySteps/FurthestDescendantOrSelfDynamicRootQueryStep.cs index ceb2e4bac3..137ccfab88 100644 --- a/src/Umbraco.Core/DynamicRoot/QuerySteps/FurthestDescendantOrSelfDynamicRootQueryStep.cs +++ b/src/Umbraco.Core/DynamicRoot/QuerySteps/FurthestDescendantOrSelfDynamicRootQueryStep.cs @@ -13,7 +13,7 @@ public class FurthestDescendantOrSelfDynamicRootQueryStep : IDynamicRootQuerySte _nodeFilterRepository = nodeFilterRepository; } - protected virtual string SupportedDirectionAlias { get; set; } = "FurthestDescendantOrSelf"; + public virtual string SupportedDirectionAlias { get; set; } = "FurthestDescendantOrSelf"; public async Task>> ExecuteAsync(ICollection origins, DynamicRootQueryStep filter) { diff --git a/src/Umbraco.Core/DynamicRoot/QuerySteps/IDynamicRootQueryStep.cs b/src/Umbraco.Core/DynamicRoot/QuerySteps/IDynamicRootQueryStep.cs index a72b86474a..7468485ab4 100644 --- a/src/Umbraco.Core/DynamicRoot/QuerySteps/IDynamicRootQueryStep.cs +++ b/src/Umbraco.Core/DynamicRoot/QuerySteps/IDynamicRootQueryStep.cs @@ -5,4 +5,5 @@ namespace Umbraco.Cms.Core.DynamicRoot.QuerySteps; public interface IDynamicRootQueryStep { Task>> ExecuteAsync(ICollection origins, DynamicRootQueryStep filter); + string SupportedDirectionAlias { get; } } diff --git a/src/Umbraco.Core/DynamicRoot/QuerySteps/NearestAncestorOrSelfDynamicRootQueryStep.cs b/src/Umbraco.Core/DynamicRoot/QuerySteps/NearestAncestorOrSelfDynamicRootQueryStep.cs index 0146283ef9..8378d841d4 100644 --- a/src/Umbraco.Core/DynamicRoot/QuerySteps/NearestAncestorOrSelfDynamicRootQueryStep.cs +++ b/src/Umbraco.Core/DynamicRoot/QuerySteps/NearestAncestorOrSelfDynamicRootQueryStep.cs @@ -15,7 +15,7 @@ public class NearestAncestorOrSelfDynamicRootQueryStep : IDynamicRootQueryStep _nodeFilterRepository = nodeFilterRepository; } - protected virtual string SupportedDirectionAlias { get; set; } = "NearestAncestorOrSelf"; + public virtual string SupportedDirectionAlias { get; set; } = "NearestAncestorOrSelf"; public async Task>> ExecuteAsync(ICollection origins, DynamicRootQueryStep filter) { diff --git a/src/Umbraco.Core/DynamicRoot/QuerySteps/NearestDescendantOrSelfDynamicRootQueryStep.cs b/src/Umbraco.Core/DynamicRoot/QuerySteps/NearestDescendantOrSelfDynamicRootQueryStep.cs index 1e36c79436..3a2c6f4126 100644 --- a/src/Umbraco.Core/DynamicRoot/QuerySteps/NearestDescendantOrSelfDynamicRootQueryStep.cs +++ b/src/Umbraco.Core/DynamicRoot/QuerySteps/NearestDescendantOrSelfDynamicRootQueryStep.cs @@ -13,7 +13,7 @@ public class NearestDescendantOrSelfDynamicRootQueryStep : IDynamicRootQueryStep _nodeFilterRepository = nodeFilterRepository; } - protected virtual string SupportedDirectionAlias { get; set; } = "NearestDescendantOrSelf"; + public virtual string SupportedDirectionAlias { get; set; } = "NearestDescendantOrSelf"; public async Task>> ExecuteAsync(ICollection origins, DynamicRootQueryStep filter) { diff --git a/src/Umbraco.Core/Models/Context/BackOfficeVariationContext.cs b/src/Umbraco.Core/Models/Context/BackOfficeVariationContext.cs new file mode 100644 index 0000000000..2e2cd318ce --- /dev/null +++ b/src/Umbraco.Core/Models/Context/BackOfficeVariationContext.cs @@ -0,0 +1,26 @@ +namespace Umbraco.Cms.Core.Models.Context; + +/// +/// Represents the back-office variation context. +/// +public class BackOfficeVariationContext +{ + /// + /// Initializes a new instance of the class. + /// + public BackOfficeVariationContext(string? culture = null, string? segment = null) + { + Culture = culture ?? string.Empty; // cannot be null, default to invariant + Segment = segment ?? string.Empty; // cannot be null, default to neutral + } + + /// + /// Gets the culture. + /// + public string Culture { get; } + + /// + /// Gets the segment. + /// + public string Segment { get; } +} diff --git a/src/Umbraco.Core/Models/Context/HttpContextBackOfficeVariationContextAccessor.cs b/src/Umbraco.Core/Models/Context/HttpContextBackOfficeVariationContextAccessor.cs new file mode 100644 index 0000000000..0cbb0074da --- /dev/null +++ b/src/Umbraco.Core/Models/Context/HttpContextBackOfficeVariationContextAccessor.cs @@ -0,0 +1,25 @@ +using Umbraco.Cms.Core.Cache; + +namespace Umbraco.Cms.Core.Models.Context; + +/// +/// Implements on top of . +/// +public class HttpContextBackOfficeVariationContextAccessor : IBackOfficeVariationContextAccessor +{ + private const string ContextKey = "Umbraco.Web.Models.PublishedContent.HttpContextBackOfficeVariationContextAccessor"; + private readonly IRequestCache _requestCache; + + /// + /// Initializes a new instance of the class. + /// + public HttpContextBackOfficeVariationContextAccessor(IRequestCache requestCache) + => _requestCache = requestCache; + + /// + public BackOfficeVariationContext? VariationContext + { + get => (BackOfficeVariationContext?)_requestCache.Get(ContextKey); + set => _requestCache.Set(ContextKey, value); + } +} diff --git a/src/Umbraco.Core/Models/Context/IBackOfficeVariationContextAccessor.cs b/src/Umbraco.Core/Models/Context/IBackOfficeVariationContextAccessor.cs new file mode 100644 index 0000000000..b08fca66f5 --- /dev/null +++ b/src/Umbraco.Core/Models/Context/IBackOfficeVariationContextAccessor.cs @@ -0,0 +1,14 @@ +using Umbraco.Cms.Core.Models.PublishedContent; + +namespace Umbraco.Cms.Core.Models.Context; + +/// +/// Gives access to the current . +/// +public interface IBackOfficeVariationContextAccessor +{ + /// + /// Gets or sets the current . + /// + BackOfficeVariationContext? VariationContext { get; set; } +} diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs index fd897ad46a..a60cef7e7f 100644 --- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs +++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs @@ -23,6 +23,7 @@ using Umbraco.Cms.Core.Mail; using Umbraco.Cms.Core.Manifest; using Umbraco.Cms.Core.Media; using Umbraco.Cms.Core.Migrations; +using Umbraco.Cms.Core.Models.Context; using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Packaging; @@ -162,6 +163,7 @@ public static partial class UmbracoBuilderExtensions builder.Services.AddSingleton(); builder.Services.AddSingleton(); + builder.Services.AddSingleton(); // Config manipulator builder.Services.AddSingleton();