diff --git a/Directory.Build.props b/Directory.Build.props index 7e8128fd83a4..4dda16251ea6 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -20,6 +20,7 @@ <WarnOnPackingNonPackableProject>false</WarnOnPackingNonPackableProject> </PropertyGroup> + <PropertyGroup> <!-- TODO: Fix and remove overrides: @@ -39,7 +40,7 @@ <!-- Package Validation --> <PropertyGroup> <GenerateCompatibilitySuppressionFile>false</GenerateCompatibilitySuppressionFile> - <EnablePackageValidation>true</EnablePackageValidation> + <EnablePackageValidation>false</EnablePackageValidation> <PackageValidationBaselineVersion>15.0.0</PackageValidationBaselineVersion> <EnableStrictModeForCompatibleFrameworksInPackage>true</EnableStrictModeForCompatibleFrameworksInPackage> <EnableStrictModeForCompatibleTfms>true</EnableStrictModeForCompatibleTfms> diff --git a/Directory.Packages.props b/Directory.Packages.props index bc0a2db4fdb5..1b350a789e06 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -14,25 +14,25 @@ <ItemGroup> <PackageVersion Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="9.0.0" /> <PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.10.0" /> - <PackageVersion Include="Microsoft.Data.Sqlite" Version="9.0.0" /> - <PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.0" /> - <PackageVersion Include="Microsoft.EntityFrameworkCore.SqlServer" Version="9.0.0" /> + <PackageVersion Include="Microsoft.Data.Sqlite" Version="9.0.2" /> + <PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.2" /> + <PackageVersion Include="Microsoft.EntityFrameworkCore.SqlServer" Version="9.0.2" /> <PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.10" /> - <PackageVersion Include="Microsoft.Extensions.Caching.Abstractions" Version="9.0.0" /> - <PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="9.0.0" /> - <PackageVersion Include="Microsoft.Extensions.Configuration.Abstractions" Version="9.0.0" /> - <PackageVersion Include="Microsoft.Extensions.Configuration.Json" Version="9.0.0" /> - <PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="9.0.0" /> - <PackageVersion Include="Microsoft.Extensions.FileProviders.Embedded" Version="9.0.0" /> - <PackageVersion Include="Microsoft.Extensions.FileProviders.Physical" Version="9.0.0" /> - <PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="9.0.0" /> - <PackageVersion Include="Microsoft.Extensions.Http" Version="9.0.0" /> - <PackageVersion Include="Microsoft.Extensions.Identity.Core" Version="9.0.0" /> - <PackageVersion Include="Microsoft.Extensions.Identity.Stores" Version="9.0.0" /> - <PackageVersion Include="Microsoft.Extensions.Logging" Version="9.0.0" /> - <PackageVersion Include="Microsoft.Extensions.Options" Version="9.0.0" /> - <PackageVersion Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="9.0.0" /> - <PackageVersion Include="Microsoft.Extensions.Options.DataAnnotations" Version="9.0.0" /> + <PackageVersion Include="Microsoft.Extensions.Caching.Abstractions" Version="9.0.2" /> + <PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="9.0.2" /> + <PackageVersion Include="Microsoft.Extensions.Configuration.Abstractions" Version="9.0.2" /> + <PackageVersion Include="Microsoft.Extensions.Configuration.Json" Version="9.0.2" /> + <PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="9.0.2" /> + <PackageVersion Include="Microsoft.Extensions.FileProviders.Embedded" Version="9.0.2" /> + <PackageVersion Include="Microsoft.Extensions.FileProviders.Physical" Version="9.0.2" /> + <PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="9.0.2" /> + <PackageVersion Include="Microsoft.Extensions.Http" Version="9.0.2" /> + <PackageVersion Include="Microsoft.Extensions.Identity.Core" Version="9.0.2" /> + <PackageVersion Include="Microsoft.Extensions.Identity.Stores" Version="9.0.2" /> + <PackageVersion Include="Microsoft.Extensions.Logging" Version="9.0.2" /> + <PackageVersion Include="Microsoft.Extensions.Options" Version="9.0.2" /> + <PackageVersion Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="9.0.2" /> + <PackageVersion Include="Microsoft.Extensions.Options.DataAnnotations" Version="9.0.2" /> <PackageVersion Include="Microsoft.Extensions.Caching.Hybrid" Version="9.0.0-preview.9.24556.5" /> </ItemGroup> <!-- Umbraco packages --> @@ -46,22 +46,22 @@ <PackageVersion Include="Asp.Versioning.Mvc.ApiExplorer" Version="8.1.0" /> <PackageVersion Include="Dazinator.Extensions.FileProviders" Version="2.0.0" /> <PackageVersion Include="Examine" Version="3.6.0" /> - <PackageVersion Include="Examine.Core" Version="3.5.0" /> - <PackageVersion Include="HtmlAgilityPack" Version="1.11.71" /> + <PackageVersion Include="Examine.Core" Version="3.6.0" /> + <PackageVersion Include="HtmlAgilityPack" Version="1.11.74" /> <PackageVersion Include="JsonPatch.Net" Version="3.1.1" /> <PackageVersion Include="K4os.Compression.LZ4" Version="1.3.8" /> - <PackageVersion Include="MailKit" Version="4.8.0" /> + <PackageVersion Include="MailKit" Version="4.10.0" /> <PackageVersion Include="Markdown" Version="2.2.1" /> <PackageVersion Include="MessagePack" Version="2.5.192" /> <PackageVersion Include="MiniProfiler.AspNetCore.Mvc" Version="4.3.8" /> - <PackageVersion Include="MiniProfiler.Shared" Version="4.3.8" /> + <PackageVersion Include="MiniProfiler.Shared" Version="4.5.4" /> <PackageVersion Include="ncrontab" Version="3.3.3" /> <PackageVersion Include="NPoco" Version="5.7.1" /> <PackageVersion Include="NPoco.SqlServer" Version="5.7.1" /> - <PackageVersion Include="OpenIddict.Abstractions" Version="6.0.0-preview3.24551.41" /> - <PackageVersion Include="OpenIddict.AspNetCore" Version="6.0.0-preview3.24551.41" /> - <PackageVersion Include="OpenIddict.EntityFrameworkCore" Version="6.0.0-preview3.24551.41" /> - <PackageVersion Include="Serilog" Version="4.1.0" /> + <PackageVersion Include="OpenIddict.Abstractions" Version="6.1.1" /> + <PackageVersion Include="OpenIddict.AspNetCore" Version="6.1.1" /> + <PackageVersion Include="OpenIddict.EntityFrameworkCore" Version="6.1.1" /> + <PackageVersion Include="Serilog" Version="4.2.0" /> <PackageVersion Include="Serilog.AspNetCore" Version="8.0.3" /> <PackageVersion Include="Serilog.Enrichers.Process" Version="3.0.0" /> <PackageVersion Include="Serilog.Enrichers.Thread" Version="4.0.0" /> @@ -73,14 +73,14 @@ <PackageVersion Include="Serilog.Sinks.Async" Version="2.1.0" /> <PackageVersion Include="Serilog.Sinks.File" Version="6.0.0" /> <PackageVersion Include="Serilog.Sinks.Map" Version="2.0.0" /> - <PackageVersion Include="SixLabors.ImageSharp" Version="3.1.6" /> + <PackageVersion Include="SixLabors.ImageSharp" Version="3.1.7" /> <PackageVersion Include="SixLabors.ImageSharp.Web" Version="3.1.3" /> <PackageVersion Include="Swashbuckle.AspNetCore" Version="7.1.0" /> </ItemGroup> <!-- Transitive pinned versions (only required because our direct dependencies have vulnerable versions of transitive dependencies) --> <ItemGroup> <!-- Microsoft.EntityFrameworkCore.SqlServer and NPoco.SqlServer brings in a vulnerable version of Azure.Identity --> - <!-- Take top-level depedendency on Azure.Identity, because Microsoft.EntityFrameworkCore.SqlServer depends on a vulnerable version --> + <!-- Take top-level depedendency on Azure.Identity, because Microsoft.EntityFrameworkCore.SqlServer depends on a vulnerable version --> <PackageVersion Include="Azure.Identity" Version="1.13.1" /> <!-- Microsoft.EntityFrameworkCore.SqlServer brings in a vulnerable version of System.Runtime.Caching --> <PackageVersion Include="System.Runtime.Caching" Version="9.0.0" /> @@ -91,7 +91,7 @@ <!-- Dazinator.Extensions.FileProviders and MiniProfiler.AspNetCore.Mvc brings in a vulnerable version of System.Text.RegularExpressions --> <PackageVersion Include="System.Text.RegularExpressions" Version="4.3.1" /> <!-- OpenIddict.AspNetCore, Npoco.SqlServer and Microsoft.EntityFrameworkCore.SqlServer brings in a vulnerable version of Microsoft.IdentityModel.JsonWebTokens --> - <!-- Take top-level depedendency on Microsoft.IdentityModel.JsonWebTokens, because OpenIddict.AspNetCore, Npoco.SqlServer and Microsoft.EntityFrameworkCore.SqlServer depends on a vulnerable version --> + <!-- Take top-level depedendency on Microsoft.IdentityModel.JsonWebTokens, because OpenIddict.AspNetCore, Npoco.SqlServer and Microsoft.EntityFrameworkCore.SqlServer depends on a vulnerable version --> <PackageVersion Include="Microsoft.IdentityModel.JsonWebTokens" Version="8.2.1" /> <!-- Azure.Identity, Microsoft.EntityFrameworkCore.SqlServer and Dazinator.Extensions.FileProviders brings in a legacy version of System.Text.Encodings.Web --> <PackageVersion Include="System.Text.Encodings.Web" Version="9.0.0" /> diff --git a/build/azure-pipelines.yml b/build/azure-pipelines.yml index 781e2e69fd32..767c0848cfc9 100644 --- a/build/azure-pipelines.yml +++ b/build/azure-pipelines.yml @@ -593,8 +593,8 @@ stages: workingDirectory: tests/Umbraco.Tests.AcceptanceTest # Install Playwright and dependencies - - pwsh: npx playwright install --with-deps - displayName: Install Playwright + - pwsh: npx playwright install chromium + displayName: Install Playwright only with Chromium browser workingDirectory: tests/Umbraco.Tests.AcceptanceTest # Test @@ -639,7 +639,7 @@ stages: testResultsFormat: 'JUnit' testResultsFiles: '*.xml' searchFolder: "tests/Umbraco.Tests.AcceptanceTest/results" - testRunTitle: "$(Agent.JobName)" + testRunTitle: "$(Agent.JobName)" - job: displayName: E2E Tests (SQL Server) @@ -760,8 +760,8 @@ stages: workingDirectory: tests/Umbraco.Tests.AcceptanceTest # Install Playwright and dependencies - - pwsh: npx playwright install --with-deps - displayName: Install Playwright + - pwsh: npx playwright install chromium + displayName: Install Playwright only with Chromium browser workingDirectory: tests/Umbraco.Tests.AcceptanceTest # Test @@ -806,7 +806,7 @@ stages: inputs: targetPath: $(Build.ArtifactStagingDirectory) artifact: "Acceptance Test Results - $(Agent.JobName) - Attempt #$(System.JobAttempt)" - + # Publish test results - task: PublishTestResults@2 displayName: "Publish test results" diff --git a/src/Umbraco.Cms.Api.Common/OpenApi/SubTypesSelector.cs b/src/Umbraco.Cms.Api.Common/OpenApi/SubTypesSelector.cs index c6e172f3f74b..947e7a8197ef 100644 --- a/src/Umbraco.Cms.Api.Common/OpenApi/SubTypesSelector.cs +++ b/src/Umbraco.Cms.Api.Common/OpenApi/SubTypesSelector.cs @@ -10,12 +10,12 @@ namespace Umbraco.Cms.Api.Common.OpenApi; public class SubTypesSelector : ISubTypesSelector { - private readonly IOptions<GlobalSettings> _settings; private readonly IHostingEnvironment _hostingEnvironment; private readonly IHttpContextAccessor _httpContextAccessor; private readonly IEnumerable<ISubTypesHandler> _subTypeHandlers; private readonly IUmbracoJsonTypeInfoResolver _umbracoJsonTypeInfoResolver; + [Obsolete("The settings parameter is not required anymore, use the other constructor instead. Scheduled for removal in Umbraco 17.")] public SubTypesSelector( IOptions<GlobalSettings> settings, IHostingEnvironment hostingEnvironment, @@ -23,7 +23,18 @@ public SubTypesSelector( IEnumerable<ISubTypesHandler> subTypeHandlers, IUmbracoJsonTypeInfoResolver umbracoJsonTypeInfoResolver) { - _settings = settings; + _hostingEnvironment = hostingEnvironment; + _httpContextAccessor = httpContextAccessor; + _subTypeHandlers = subTypeHandlers; + _umbracoJsonTypeInfoResolver = umbracoJsonTypeInfoResolver; + } + + public SubTypesSelector( + IHostingEnvironment hostingEnvironment, + IHttpContextAccessor httpContextAccessor, + IEnumerable<ISubTypesHandler> subTypeHandlers, + IUmbracoJsonTypeInfoResolver umbracoJsonTypeInfoResolver) + { _hostingEnvironment = hostingEnvironment; _httpContextAccessor = httpContextAccessor; _subTypeHandlers = subTypeHandlers; @@ -32,7 +43,7 @@ public SubTypesSelector( public IEnumerable<Type> SubTypes(Type type) { - var backOfficePath = _settings.Value.GetBackOfficePath(_hostingEnvironment); + var backOfficePath = _hostingEnvironment.GetBackOfficePath(); var swaggerPath = $"{backOfficePath}/swagger"; if (_httpContextAccessor.HttpContext?.Request.Path.StartsWithSegments(swaggerPath) ?? false) diff --git a/src/Umbraco.Cms.Api.Common/OpenApi/SwaggerRouteTemplatePipelineFilter.cs b/src/Umbraco.Cms.Api.Common/OpenApi/SwaggerRouteTemplatePipelineFilter.cs index 9f05f57d5a5e..dba0e6f56d01 100644 --- a/src/Umbraco.Cms.Api.Common/OpenApi/SwaggerRouteTemplatePipelineFilter.cs +++ b/src/Umbraco.Cms.Api.Common/OpenApi/SwaggerRouteTemplatePipelineFilter.cs @@ -7,7 +7,7 @@ using Swashbuckle.AspNetCore.SwaggerGen; using Swashbuckle.AspNetCore.SwaggerUI; using Umbraco.Cms.Core; -using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Hosting; using Umbraco.Cms.Web.Common.ApplicationBuilder; using Umbraco.Extensions; using IHostingEnvironment = Umbraco.Cms.Core.Hosting.IHostingEnvironment; @@ -30,24 +30,21 @@ private void PostPipelineAction(IApplicationBuilder applicationBuilder) IOptions<SwaggerGenOptions> swaggerGenOptions = applicationBuilder.ApplicationServices.GetRequiredService<IOptions<SwaggerGenOptions>>(); applicationBuilder.UseSwagger(swaggerOptions => - { - swaggerOptions.RouteTemplate = SwaggerRouteTemplate(applicationBuilder); - }); + { + swaggerOptions.RouteTemplate = SwaggerRouteTemplate(applicationBuilder); + }); applicationBuilder.UseSwaggerUI(swaggerUiOptions => SwaggerUiConfiguration(swaggerUiOptions, swaggerGenOptions.Value, applicationBuilder)); } protected virtual bool SwaggerIsEnabled(IApplicationBuilder applicationBuilder) - { - IWebHostEnvironment webHostEnvironment = applicationBuilder.ApplicationServices.GetRequiredService<IWebHostEnvironment>(); - return webHostEnvironment.IsProduction() is false; - } + => applicationBuilder.ApplicationServices.GetRequiredService<IWebHostEnvironment>().IsProduction() is false; protected virtual string SwaggerRouteTemplate(IApplicationBuilder applicationBuilder) - => $"{GetUmbracoPath(applicationBuilder).TrimStart(Constants.CharArrays.ForwardSlash)}/swagger/{{documentName}}/swagger.json"; + => $"{GetBackOfficePath(applicationBuilder).TrimStart(Constants.CharArrays.ForwardSlash)}/swagger/{{documentName}}/swagger.json"; protected virtual string SwaggerUiRoutePrefix(IApplicationBuilder applicationBuilder) - => $"{GetUmbracoPath(applicationBuilder).TrimStart(Constants.CharArrays.ForwardSlash)}/swagger"; + => $"{GetBackOfficePath(applicationBuilder).TrimStart(Constants.CharArrays.ForwardSlash)}/swagger"; protected virtual void SwaggerUiConfiguration( SwaggerUIOptions swaggerUiOptions, @@ -56,8 +53,7 @@ protected virtual void SwaggerUiConfiguration( { swaggerUiOptions.RoutePrefix = SwaggerUiRoutePrefix(applicationBuilder); - foreach ((var name, OpenApiInfo? apiInfo) in swaggerGenOptions.SwaggerGeneratorOptions.SwaggerDocs - .OrderBy(x => x.Value.Title)) + foreach ((var name, OpenApiInfo? apiInfo) in swaggerGenOptions.SwaggerGeneratorOptions.SwaggerDocs.OrderBy(x => x.Value.Title)) { swaggerUiOptions.SwaggerEndpoint($"{name}/swagger.json", $"{apiInfo.Title}"); } @@ -70,11 +66,6 @@ protected virtual void SwaggerUiConfiguration( swaggerUiOptions.OAuthUsePkce(); } - private string GetUmbracoPath(IApplicationBuilder applicationBuilder) - { - GlobalSettings settings = applicationBuilder.ApplicationServices.GetRequiredService<IOptions<GlobalSettings>>().Value; - IHostingEnvironment hostingEnvironment = applicationBuilder.ApplicationServices.GetRequiredService<IHostingEnvironment>(); - - return settings.GetBackOfficePath(hostingEnvironment); - } + private string GetBackOfficePath(IApplicationBuilder applicationBuilder) + => applicationBuilder.ApplicationServices.GetRequiredService<IHostingEnvironment>().GetBackOfficePath(); } diff --git a/src/Umbraco.Cms.Api.Delivery/Controllers/Content/ByIdsContentApiController.cs b/src/Umbraco.Cms.Api.Delivery/Controllers/Content/ByIdsContentApiController.cs index 0771c357581d..7eb653e9d22c 100644 --- a/src/Umbraco.Cms.Api.Delivery/Controllers/Content/ByIdsContentApiController.cs +++ b/src/Umbraco.Cms.Api.Delivery/Controllers/Content/ByIdsContentApiController.cs @@ -58,6 +58,6 @@ private async Task<IActionResult> HandleRequest(HashSet<Guid> ids) .WhereNotNull() .ToArray(); - return await Task.FromResult(Ok(apiContentItems)); + return Ok(apiContentItems); } } diff --git a/src/Umbraco.Cms.Api.Delivery/Controllers/Content/ByRouteContentApiController.cs b/src/Umbraco.Cms.Api.Delivery/Controllers/Content/ByRouteContentApiController.cs index 54fe5785a9f4..2b94e1169fb6 100644 --- a/src/Umbraco.Cms.Api.Delivery/Controllers/Content/ByRouteContentApiController.cs +++ b/src/Umbraco.Cms.Api.Delivery/Controllers/Content/ByRouteContentApiController.cs @@ -117,7 +117,7 @@ private async Task<IActionResult> HandleRequest(string path) return deniedAccessResult; } - return await Task.FromResult(Ok(ApiContentResponseBuilder.Build(contentItem))); + return Ok(ApiContentResponseBuilder.Build(contentItem)); } IApiContentRoute? redirectRoute = _requestRedirectService.GetRedirectRoute(path); diff --git a/src/Umbraco.Cms.Api.Delivery/Controllers/Media/ByIdMediaApiController.cs b/src/Umbraco.Cms.Api.Delivery/Controllers/Media/ByIdMediaApiController.cs index f7fa2f6d927d..d790f7f948c6 100644 --- a/src/Umbraco.Cms.Api.Delivery/Controllers/Media/ByIdMediaApiController.cs +++ b/src/Umbraco.Cms.Api.Delivery/Controllers/Media/ByIdMediaApiController.cs @@ -1,4 +1,4 @@ -using Asp.Versioning; +using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Umbraco.Cms.Core.Models.DeliveryApi; @@ -24,8 +24,8 @@ public ByIdMediaApiController( [ProducesResponseType(typeof(IApiMediaWithCropsResponse), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] [Obsolete("Please use version 2 of this API. Will be removed in V15.")] - public async Task<IActionResult> ById(Guid id) - => await HandleRequest(id); + public Task<IActionResult> ById(Guid id) + => Task.FromResult(HandleRequest(id)); /// <summary> /// Gets a media item by id. @@ -36,16 +36,16 @@ public async Task<IActionResult> ById(Guid id) [MapToApiVersion("2.0")] [ProducesResponseType(typeof(IApiMediaWithCropsResponse), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task<IActionResult> ByIdV20(Guid id) - => await HandleRequest(id); + public Task<IActionResult> ByIdV20(Guid id) + => Task.FromResult(HandleRequest(id)); - private async Task<IActionResult> HandleRequest(Guid id) + private IActionResult HandleRequest(Guid id) { IPublishedContent? media = PublishedMediaCache.GetById(id); if (media is null) { - return await Task.FromResult(NotFound()); + return NotFound(); } return Ok(BuildApiMediaWithCrops(media)); diff --git a/src/Umbraco.Cms.Api.Delivery/Controllers/Media/ByIdsMediaApiController.cs b/src/Umbraco.Cms.Api.Delivery/Controllers/Media/ByIdsMediaApiController.cs index 957c982c38f1..0bf4b89c12ee 100644 --- a/src/Umbraco.Cms.Api.Delivery/Controllers/Media/ByIdsMediaApiController.cs +++ b/src/Umbraco.Cms.Api.Delivery/Controllers/Media/ByIdsMediaApiController.cs @@ -22,8 +22,8 @@ public ByIdsMediaApiController(IPublishedMediaCache publishedMediaCache, IApiMed [MapToApiVersion("1.0")] [ProducesResponseType(typeof(IEnumerable<IApiMediaWithCropsResponse>), StatusCodes.Status200OK)] [Obsolete("Please use version 2 of this API. Will be removed in V15.")] - public async Task<IActionResult> Item([FromQuery(Name = "id")] HashSet<Guid> ids) - => await HandleRequest(ids); + public Task<IActionResult> Item([FromQuery(Name = "id")] HashSet<Guid> ids) + => Task.FromResult(HandleRequest(ids)); /// <summary> /// Gets media items by ids. @@ -33,10 +33,10 @@ public async Task<IActionResult> Item([FromQuery(Name = "id")] HashSet<Guid> ids [HttpGet("items")] [MapToApiVersion("2.0")] [ProducesResponseType(typeof(IEnumerable<IApiMediaWithCropsResponse>), StatusCodes.Status200OK)] - public async Task<IActionResult> ItemsV20([FromQuery(Name = "id")] HashSet<Guid> ids) - => await HandleRequest(ids); + public Task<IActionResult> ItemsV20([FromQuery(Name = "id")] HashSet<Guid> ids) + => Task.FromResult(HandleRequest(ids)); - private async Task<IActionResult> HandleRequest(HashSet<Guid> ids) + private IActionResult HandleRequest(HashSet<Guid> ids) { IPublishedContent[] mediaItems = ids .Select(PublishedMediaCache.GetById) @@ -47,6 +47,6 @@ private async Task<IActionResult> HandleRequest(HashSet<Guid> ids) .Select(BuildApiMediaWithCrops) .ToArray(); - return await Task.FromResult(Ok(apiMediaItems)); + return Ok(apiMediaItems); } } diff --git a/src/Umbraco.Cms.Api.Delivery/Controllers/Media/ByPathMediaApiController.cs b/src/Umbraco.Cms.Api.Delivery/Controllers/Media/ByPathMediaApiController.cs index 0afedddffb7d..982a7279911c 100644 --- a/src/Umbraco.Cms.Api.Delivery/Controllers/Media/ByPathMediaApiController.cs +++ b/src/Umbraco.Cms.Api.Delivery/Controllers/Media/ByPathMediaApiController.cs @@ -1,4 +1,4 @@ -using Asp.Versioning; +using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Umbraco.Cms.Core.DeliveryApi; @@ -27,8 +27,8 @@ public ByPathMediaApiController( [ProducesResponseType(typeof(IApiMediaWithCropsResponse), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] [Obsolete("Please use version 2 of this API. Will be removed in V15.")] - public async Task<IActionResult> ByPath(string path) - => await HandleRequest(path); + public Task<IActionResult> ByPath(string path) + => Task.FromResult(HandleRequest(path)); /// <summary> /// Gets a media item by its path. @@ -39,17 +39,17 @@ public async Task<IActionResult> ByPath(string path) [MapToApiVersion("2.0")] [ProducesResponseType(typeof(IApiMediaWithCropsResponse), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task<IActionResult> ByPathV20(string path) - => await HandleRequest(path); + public Task<IActionResult> ByPathV20(string path) + => Task.FromResult(HandleRequest(path)); - private async Task<IActionResult> HandleRequest(string path) + private IActionResult HandleRequest(string path) { path = DecodePath(path); IPublishedContent? media = _apiMediaQueryService.GetByPath(path); if (media is null) { - return await Task.FromResult(NotFound()); + return NotFound(); } return Ok(BuildApiMediaWithCrops(media)); diff --git a/src/Umbraco.Cms.Api.Delivery/Controllers/Media/QueryMediaApiController.cs b/src/Umbraco.Cms.Api.Delivery/Controllers/Media/QueryMediaApiController.cs index c61aaf876493..0440126d6387 100644 --- a/src/Umbraco.Cms.Api.Delivery/Controllers/Media/QueryMediaApiController.cs +++ b/src/Umbraco.Cms.Api.Delivery/Controllers/Media/QueryMediaApiController.cs @@ -1,4 +1,4 @@ -using Asp.Versioning; +using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Umbraco.Cms.Api.Common.ViewModels.Pagination; @@ -32,13 +32,13 @@ public QueryMediaApiController( [ProducesResponseType(typeof(PagedViewModel<IApiMediaWithCropsResponse>), StatusCodes.Status200OK)] [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)] [Obsolete("Please use version 2 of this API. Will be removed in V15.")] - public async Task<IActionResult> Query( + public Task<IActionResult> Query( string? fetch, [FromQuery] string[] filter, [FromQuery] string[] sort, int skip = 0, int take = 10) - => await HandleRequest(fetch, filter, sort, skip, take); + => Task.FromResult(HandleRequest(fetch, filter, sort, skip, take)); /// <summary> /// Gets a paginated list of media item(s) from query. @@ -53,15 +53,15 @@ public async Task<IActionResult> Query( [MapToApiVersion("2.0")] [ProducesResponseType(typeof(PagedViewModel<IApiMediaWithCropsResponse>), StatusCodes.Status200OK)] [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)] - public async Task<IActionResult> QueryV20( + public Task<IActionResult> QueryV20( string? fetch, [FromQuery] string[] filter, [FromQuery] string[] sort, int skip = 0, int take = 10) - => await HandleRequest(fetch, filter, sort, skip, take); + => Task.FromResult(HandleRequest(fetch, filter, sort, skip, take)); - private async Task<IActionResult> HandleRequest(string? fetch, string[] filter, string[] sort, int skip, int take) + private IActionResult HandleRequest(string? fetch, string[] filter, string[] sort, int skip, int take) { Attempt<PagedModel<Guid>, ApiMediaQueryOperationStatus> queryAttempt = _apiMediaQueryService.ExecuteQuery(fetch, filter, sort, skip, take); @@ -79,6 +79,6 @@ private async Task<IActionResult> HandleRequest(string? fetch, string[] filter, Items = mediaItems.Select(BuildApiMediaWithCrops) }; - return await Task.FromResult(Ok(model)); + return Ok(model); } } diff --git a/src/Umbraco.Cms.Api.Delivery/Handlers/InitializeMemberApplicationNotificationHandler.cs b/src/Umbraco.Cms.Api.Delivery/Handlers/InitializeMemberApplicationNotificationHandler.cs index 43653239b371..fdd98e1702c3 100644 --- a/src/Umbraco.Cms.Api.Delivery/Handlers/InitializeMemberApplicationNotificationHandler.cs +++ b/src/Umbraco.Cms.Api.Delivery/Handlers/InitializeMemberApplicationNotificationHandler.cs @@ -91,7 +91,7 @@ private async Task HandleMemberClientCredentialsApplication(IMemberApplicationMa } } - private bool ValidateRedirectUrls(Uri[] redirectUrls) + private bool ValidateRedirectUrls(IEnumerable<Uri> redirectUrls) { if (redirectUrls.Any() is false) { diff --git a/src/Umbraco.Cms.Api.Management/Controllers/BackOfficeLoginController.cs b/src/Umbraco.Cms.Api.Management/Controllers/BackOfficeLoginController.cs index c6ff576b93d3..0416e5c303dd 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/BackOfficeLoginController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/BackOfficeLoginController.cs @@ -24,7 +24,7 @@ public class BackOfficeLoginModel public bool UserIsAlreadyLoggedIn { get; set; } } -[ApiExplorerSettings(IgnoreApi=true)] +[ApiExplorerSettings(IgnoreApi = true)] [Route(LoginPath)] public class BackOfficeLoginController : Controller { @@ -51,7 +51,7 @@ public async Task<IActionResult> Index(CancellationToken cancellationToken, Back if (string.IsNullOrEmpty(model.UmbracoUrl)) { - model.UmbracoUrl = _hostingEnvironment.ToAbsolute(Constants.System.DefaultUmbracoPath); + model.UmbracoUrl = _hostingEnvironment.GetBackOfficePath(); } if (string.IsNullOrEmpty(model.ReturnUrl)) diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Culture/AllCultureController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Culture/AllCultureController.cs index 20d677044431..eb5177996aab 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Culture/AllCultureController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Culture/AllCultureController.cs @@ -1,4 +1,4 @@ -using System.Globalization; +using System.Globalization; using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; @@ -28,7 +28,7 @@ public AllCultureController(IUmbracoMapper umbracoMapper, ICultureService cultur [HttpGet] [MapToApiVersion("1.0")] [ProducesResponseType(typeof(PagedViewModel<CultureReponseModel>), StatusCodes.Status200OK)] - public async Task<PagedViewModel<CultureReponseModel>> GetAll(CancellationToken cancellationToken, int skip = 0, int take = 100) + public Task<PagedViewModel<CultureReponseModel>> GetAll(CancellationToken cancellationToken, int skip = 0, int take = 100) { CultureInfo[] all = _cultureService.GetValidCultureInfos(); @@ -38,6 +38,6 @@ public async Task<PagedViewModel<CultureReponseModel>> GetAll(CancellationToken Total = all.Length }; - return await Task.FromResult(viewModel); + return Task.FromResult(viewModel); } } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/DataType/CopyDataTypeController.cs b/src/Umbraco.Cms.Api.Management/Controllers/DataType/CopyDataTypeController.cs index 2bafe735324f..6bf47269d5ce 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/DataType/CopyDataTypeController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/DataType/CopyDataTypeController.cs @@ -1,4 +1,5 @@ using Asp.Versioning; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Umbraco.Cms.Api.Management.ViewModels.DataType; @@ -7,10 +8,12 @@ using Umbraco.Cms.Core.Security; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Services.OperationStatus; +using Umbraco.Cms.Web.Common.Authorization; namespace Umbraco.Cms.Api.Management.Controllers.DataType; [ApiVersion("1.0")] +[Authorize(Policy = AuthorizationPolicies.TreeAccessDataTypes)] public class CopyDataTypeController : DataTypeControllerBase { private readonly IDataTypeService _dataTypeService; diff --git a/src/Umbraco.Cms.Api.Management/Controllers/DataType/CreateDataTypeController.cs b/src/Umbraco.Cms.Api.Management/Controllers/DataType/CreateDataTypeController.cs index 9275e25001c0..1acea3969626 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/DataType/CreateDataTypeController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/DataType/CreateDataTypeController.cs @@ -1,4 +1,5 @@ -using Asp.Versioning; +using Asp.Versioning; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Umbraco.Cms.Api.Management.Factories; @@ -8,10 +9,12 @@ using Umbraco.Cms.Core.Security; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Services.OperationStatus; +using Umbraco.Cms.Web.Common.Authorization; namespace Umbraco.Cms.Api.Management.Controllers.DataType; [ApiVersion("1.0")] +[Authorize(Policy = AuthorizationPolicies.TreeAccessDataTypes)] public class CreateDataTypeController : DataTypeControllerBase { private readonly IDataTypeService _dataTypeService; diff --git a/src/Umbraco.Cms.Api.Management/Controllers/DataType/DeleteDataTypeController.cs b/src/Umbraco.Cms.Api.Management/Controllers/DataType/DeleteDataTypeController.cs index aed80dc5fedf..ba6cb63d8f98 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/DataType/DeleteDataTypeController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/DataType/DeleteDataTypeController.cs @@ -1,4 +1,5 @@ -using Asp.Versioning; +using Asp.Versioning; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Umbraco.Cms.Core; @@ -6,10 +7,12 @@ using Umbraco.Cms.Core.Security; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Services.OperationStatus; +using Umbraco.Cms.Web.Common.Authorization; namespace Umbraco.Cms.Api.Management.Controllers.DataType; [ApiVersion("1.0")] +[Authorize(Policy = AuthorizationPolicies.TreeAccessDataTypes)] public class DeleteDataTypeController : DataTypeControllerBase { private readonly IDataTypeService _dataTypeService; diff --git a/src/Umbraco.Cms.Api.Management/Controllers/DataType/MoveDataTypeController.cs b/src/Umbraco.Cms.Api.Management/Controllers/DataType/MoveDataTypeController.cs index bec84124b500..e8f5230463a7 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/DataType/MoveDataTypeController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/DataType/MoveDataTypeController.cs @@ -1,4 +1,5 @@ using Asp.Versioning; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Umbraco.Cms.Api.Management.ViewModels.DataType; @@ -7,10 +8,12 @@ using Umbraco.Cms.Core.Security; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Services.OperationStatus; +using Umbraco.Cms.Web.Common.Authorization; namespace Umbraco.Cms.Api.Management.Controllers.DataType; [ApiVersion("1.0")] +[Authorize(Policy = AuthorizationPolicies.TreeAccessDataTypes)] public class MoveDataTypeController : DataTypeControllerBase { private readonly IDataTypeService _dataTypeService; diff --git a/src/Umbraco.Cms.Api.Management/Controllers/DataType/UpdateDataTypeController.cs b/src/Umbraco.Cms.Api.Management/Controllers/DataType/UpdateDataTypeController.cs index 71afc06b26ea..4b67b55653ce 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/DataType/UpdateDataTypeController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/DataType/UpdateDataTypeController.cs @@ -1,4 +1,5 @@ -using Asp.Versioning; +using Asp.Versioning; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Umbraco.Cms.Api.Management.Factories; @@ -8,10 +9,12 @@ using Umbraco.Cms.Core.Security; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Services.OperationStatus; +using Umbraco.Cms.Web.Common.Authorization; namespace Umbraco.Cms.Api.Management.Controllers.DataType; [ApiVersion("1.0")] +[Authorize(Policy = AuthorizationPolicies.TreeAccessDataTypes)] public class UpdateDataTypeController : DataTypeControllerBase { private readonly IDataTypeService _dataTypeService; diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Dictionary/AllDictionaryController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Dictionary/AllDictionaryController.cs index b40db7607a30..a0e77472437a 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Dictionary/AllDictionaryController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Dictionary/AllDictionaryController.cs @@ -1,4 +1,4 @@ -using Asp.Versioning; +using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Umbraco.Cms.Core.Mapping; @@ -38,6 +38,6 @@ public async Task<ActionResult<PagedViewModel<DictionaryOverviewResponseModel>>> Total = items.Length, Items = _umbracoMapper.MapEnumerable<IDictionaryItem, DictionaryOverviewResponseModel>(items.Skip(skip).Take(take)) }; - return await Task.FromResult(model); + return model; } } 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 5e55924079a0..2b02ff541a0c 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Dictionary/Tree/DictionaryTreeControllerBase.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Dictionary/Tree/DictionaryTreeControllerBase.cs @@ -28,7 +28,7 @@ public DictionaryTreeControllerBase(IEntityService entityService, IDictionaryIte protected IDictionaryItemService DictionaryItemService { get; } protected async Task<IEnumerable<NamedEntityTreeItemResponseModel>> MapTreeItemViewModels(IEnumerable<IDictionaryItem> dictionaryItems) - => await Task.WhenAll(dictionaryItems.Select(CreateEntityTreeItemViewModelAsync)); + => await MapTreeItemViewModelsAsync(dictionaryItems).ToArrayAsync(); protected override async Task<ActionResult<IEnumerable<NamedEntityTreeItemResponseModel>>> GetAncestors(Guid descendantKey, bool includeSelf = true) { @@ -54,10 +54,19 @@ protected override async Task<ActionResult<IEnumerable<NamedEntityTreeItemRespon } } - NamedEntityTreeItemResponseModel[] viewModels = await Task.WhenAll(ancestors.Select(CreateEntityTreeItemViewModelAsync)); + NamedEntityTreeItemResponseModel[] viewModels = await MapTreeItemViewModelsAsync(ancestors).ToArrayAsync(); + return Ok(viewModels.Reverse()); } + private async IAsyncEnumerable<NamedEntityTreeItemResponseModel> MapTreeItemViewModelsAsync(IEnumerable<IDictionaryItem> dictionaryItems) + { + foreach (IDictionaryItem dictionaryItem in dictionaryItems) + { + yield return await CreateEntityTreeItemViewModelAsync(dictionaryItem); + } + } + private async Task<NamedEntityTreeItemResponseModel> CreateEntityTreeItemViewModelAsync(IDictionaryItem dictionaryItem) { var hasChildren = await DictionaryItemService.CountChildrenAsync(dictionaryItem.Key) > 0; 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 bea90daf42ed..ebefb5bfb283 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Document/Item/ItemDocumentItemController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Document/Item/ItemDocumentItemController.cs @@ -1,4 +1,4 @@ -using Asp.Versioning; +using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Umbraco.Cms.Api.Management.Factories; @@ -24,13 +24,13 @@ public ItemDocumentItemController(IEntityService entityService, IDocumentPresent [HttpGet] [MapToApiVersion("1.0")] [ProducesResponseType(typeof(IEnumerable<DocumentItemResponseModel>), StatusCodes.Status200OK)] - public async Task<IActionResult> Item( + public Task<IActionResult> Item( CancellationToken cancellationToken, [FromQuery(Name = "id")] HashSet<Guid> ids) { if (ids.Count is 0) { - return Ok(Enumerable.Empty<DocumentItemResponseModel>()); + return Task.FromResult<IActionResult>(Ok(Enumerable.Empty<DocumentItemResponseModel>())); } IEnumerable<IDocumentEntitySlim> documents = _entityService @@ -38,6 +38,6 @@ public async Task<IActionResult> Item( .OfType<IDocumentEntitySlim>(); IEnumerable<DocumentItemResponseModel> documentItemResponseModels = documents.Select(_documentPresentationFactory.CreateItemResponseModel); - return await Task.FromResult(Ok(documentItemResponseModels)); + return Task.FromResult<IActionResult>(Ok(documentItemResponseModels)); } } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Document/Item/SearchDocumentItemController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Document/Item/SearchDocumentItemController.cs index 0ab15f0e7f66..345a35f12493 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Document/Item/SearchDocumentItemController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Document/Item/SearchDocumentItemController.cs @@ -1,4 +1,4 @@ -using System.Text.Json.Serialization; +using System.Text.Json.Serialization; using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; @@ -24,8 +24,8 @@ public SearchDocumentItemController(IIndexedEntitySearchService indexedEntitySea [NonAction] [Obsolete("Scheduled to be removed in v16, use the non obsoleted method instead")] - public async Task<IActionResult> Search(CancellationToken cancellationToken, string query, int skip = 0, int take = 100) - => await SearchFromParent(cancellationToken, query, skip, take); + public Task<IActionResult> Search(CancellationToken cancellationToken, string query, int skip = 0, int take = 100) + => SearchFromParent(cancellationToken, query, skip, take); [NonAction] [Obsolete("Scheduled to be removed in v16, use the non obsoleted method instead")] @@ -35,7 +35,7 @@ public async Task<IActionResult> SearchFromParent(CancellationToken cancellation [HttpGet("search")] [MapToApiVersion("1.0")] [ProducesResponseType(typeof(PagedModel<DocumentItemResponseModel>), StatusCodes.Status200OK)] - public async Task<IActionResult> SearchFromParentWithAllowedTypes(CancellationToken cancellationToken, string query, int skip = 0, int take = 100, Guid? parentId = null, [FromQuery]IEnumerable<Guid>? allowedDocumentTypes = null) + public Task<IActionResult> SearchFromParentWithAllowedTypes(CancellationToken cancellationToken, string query, int skip = 0, int take = 100, Guid? parentId = null, [FromQuery]IEnumerable<Guid>? allowedDocumentTypes = null) { PagedModel<IEntitySlim> searchResult = _indexedEntitySearchService.Search(UmbracoObjectTypes.Document, query, parentId, allowedDocumentTypes, skip, take); var result = new PagedModel<DocumentItemResponseModel> @@ -44,6 +44,6 @@ public async Task<IActionResult> SearchFromParentWithAllowedTypes(CancellationTo Total = searchResult.Total, }; - return await Task.FromResult(Ok(result)); + return Task.FromResult<IActionResult>(Ok(result)); } } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Document/PublishDocumentWithDescendantsController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Document/PublishDocumentWithDescendantsController.cs index 62f06993429f..869bc4c8800b 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Document/PublishDocumentWithDescendantsController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Document/PublishDocumentWithDescendantsController.cs @@ -69,11 +69,6 @@ private static PublishBranchFilter BuildPublishBranchFilter(PublishDocumentWithD publishBranchFilter |= PublishBranchFilter.IncludeUnpublished; } - if (requestModel.ForceRepublish) - { - publishBranchFilter |= PublishBranchFilter.ForceRepublish; - } - return publishBranchFilter; } } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Document/References/AreReferencedDocumentController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Document/References/AreReferencedDocumentController.cs index ed593e9c9ec2..bc2353dc5592 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Document/References/AreReferencedDocumentController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Document/References/AreReferencedDocumentController.cs @@ -1,4 +1,4 @@ -using Asp.Versioning; +using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Umbraco.Cms.Api.Common.ViewModels.Pagination; @@ -45,6 +45,6 @@ public async Task<ActionResult<PagedViewModel<ReferenceByIdModel>>> GetPagedRefe Items = _umbracoMapper.MapEnumerable<Guid, ReferenceByIdModel>(distinctByKeyItemsWithReferencedRelations.Items), }; - return await Task.FromResult(pagedViewModel); + return pagedViewModel; } } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Document/References/ReferencedByDocumentController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Document/References/ReferencedByDocumentController.cs index 47df66fd6df6..6a1d9b282416 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Document/References/ReferencedByDocumentController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Document/References/ReferencedByDocumentController.cs @@ -45,6 +45,6 @@ public async Task<ActionResult<PagedViewModel<IReferenceResponseModel>>> Referen Items = await _relationTypePresentationFactory.CreateReferenceResponseModelsAsync(relationItems.Items), }; - return await Task.FromResult(pagedViewModel); + return pagedViewModel; } } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Document/References/ReferencedDescendantsDocumentController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Document/References/ReferencedDescendantsDocumentController.cs index 4b931632e1dc..138b9196287b 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Document/References/ReferencedDescendantsDocumentController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Document/References/ReferencedDescendantsDocumentController.cs @@ -1,4 +1,4 @@ -using Asp.Versioning; +using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Umbraco.Cms.Api.Common.ViewModels.Pagination; @@ -45,6 +45,6 @@ public async Task<ActionResult<PagedViewModel<ReferenceByIdModel>>> ReferencedDe Items = _umbracoMapper.MapEnumerable<RelationItemModel, ReferenceByIdModel>(relationItems.Items), }; - return await Task.FromResult(pagedViewModel); + return pagedViewModel; } } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Document/UpdateNotificationsController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Document/UpdateNotificationsController.cs index d02e1456f9c5..0d504b22a6d7 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Document/UpdateNotificationsController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Document/UpdateNotificationsController.cs @@ -1,4 +1,4 @@ -using Asp.Versioning; +using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Umbraco.Cms.Api.Management.ViewModels.Document; @@ -35,6 +35,6 @@ public async Task<IActionResult> UpdateNotifications(CancellationToken cancellat } _notificationService.SetNotifications(_backOfficeSecurityAccessor.BackOfficeSecurity?.CurrentUser, content, updateModel.SubscribedActionIds); - return await Task.FromResult(Ok()); + return Ok(); } } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/DocumentBlueprint/Item/ItemDocumentBlueprintController.cs b/src/Umbraco.Cms.Api.Management/Controllers/DocumentBlueprint/Item/ItemDocumentBlueprintController.cs index 5694d5b0b9c0..551f6cc407b2 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/DocumentBlueprint/Item/ItemDocumentBlueprintController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/DocumentBlueprint/Item/ItemDocumentBlueprintController.cs @@ -1,4 +1,4 @@ -using Asp.Versioning; +using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Umbraco.Cms.Api.Management.Factories; @@ -25,13 +25,13 @@ public ItemDocumentBlueprintController(IEntityService entityService, IDocumentPr [HttpGet] [MapToApiVersion("1.0")] [ProducesResponseType(typeof(IEnumerable<DocumentBlueprintItemResponseModel>), StatusCodes.Status200OK)] - public async Task<IActionResult> Item( + public Task<IActionResult> Item( CancellationToken cancellationToken, [FromQuery(Name = "id")] HashSet<Guid> ids) { if (ids.Count is 0) { - return Ok(Enumerable.Empty<DocumentBlueprintItemResponseModel>()); + return Task.FromResult<IActionResult>(Ok(Enumerable.Empty<DocumentBlueprintItemResponseModel>())); } IEnumerable<IDocumentEntitySlim> documents = _entityService @@ -39,6 +39,6 @@ public async Task<IActionResult> Item( .Select(x => x as IDocumentEntitySlim) .WhereNotNull(); IEnumerable<DocumentBlueprintItemResponseModel> responseModels = documents.Select(x => _documentPresentationFactory.CreateBlueprintItemResponseModel(x)); - return await Task.FromResult(Ok(responseModels)); + return Task.FromResult<IActionResult>(Ok(responseModels)); } } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/DocumentType/AllowedChildrenDocumentTypeController.cs b/src/Umbraco.Cms.Api.Management/Controllers/DocumentType/AllowedChildrenDocumentTypeController.cs index 5bff749e8a51..f7403cb4635f 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/DocumentType/AllowedChildrenDocumentTypeController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/DocumentType/AllowedChildrenDocumentTypeController.cs @@ -1,5 +1,4 @@ -using Asp.Versioning; -using Microsoft.AspNetCore.Authorization; +using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Umbraco.Cms.Api.Common.ViewModels.Pagination; @@ -9,7 +8,6 @@ using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Services.OperationStatus; -using Umbraco.Cms.Web.Common.Authorization; namespace Umbraco.Cms.Api.Management.Controllers.DocumentType; @@ -25,6 +23,15 @@ public AllowedChildrenDocumentTypeController(IContentTypeService contentTypeServ _umbracoMapper = umbracoMapper; } + [NonAction] + [Obsolete("Use the non obsoleted method instead. Scheduled to be removed in v16")] + public async Task<IActionResult> AllowedChildrenByKey( + CancellationToken cancellationToken, + Guid id, + int skip = 0, + int take = 100) + => await AllowedChildrenByKey(cancellationToken, id, null, skip, take); + [HttpGet("{id:guid}/allowed-children")] [MapToApiVersion("1.0")] [ProducesResponseType(typeof(PagedViewModel<AllowedDocumentType>), StatusCodes.Status200OK)] @@ -32,10 +39,11 @@ public AllowedChildrenDocumentTypeController(IContentTypeService contentTypeServ public async Task<IActionResult> AllowedChildrenByKey( CancellationToken cancellationToken, Guid id, + Guid? parentContentKey = null, int skip = 0, int take = 100) { - Attempt<PagedModel<IContentType>?, ContentTypeOperationStatus> attempt = await _contentTypeService.GetAllowedChildrenAsync(id, skip, take); + Attempt<PagedModel<IContentType>?, ContentTypeOperationStatus> attempt = await _contentTypeService.GetAllowedChildrenAsync(id, parentContentKey, skip, take); if (attempt.Success is false) { return OperationStatusResult(attempt.Status); diff --git a/src/Umbraco.Cms.Api.Management/Controllers/DocumentType/ExportDocumentTypeController.cs b/src/Umbraco.Cms.Api.Management/Controllers/DocumentType/ExportDocumentTypeController.cs index d0c66d059995..4d2eba27eb84 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/DocumentType/ExportDocumentTypeController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/DocumentType/ExportDocumentTypeController.cs @@ -1,14 +1,17 @@ using Asp.Versioning; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Umbraco.Cms.Api.Management.Factories; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Services.OperationStatus; +using Umbraco.Cms.Web.Common.Authorization; namespace Umbraco.Cms.Api.Management.Controllers.DocumentType; [ApiVersion("1.0")] +[Authorize(Policy = AuthorizationPolicies.TreeAccessDocumentTypes)] public class ExportDocumentTypeController : DocumentTypeControllerBase { private readonly IContentTypeService _contentTypeService; diff --git a/src/Umbraco.Cms.Api.Management/Controllers/DocumentType/ImportExistingDocumentTypeController.cs b/src/Umbraco.Cms.Api.Management/Controllers/DocumentType/ImportExistingDocumentTypeController.cs index 9b1d6506a1db..46f28d366520 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/DocumentType/ImportExistingDocumentTypeController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/DocumentType/ImportExistingDocumentTypeController.cs @@ -1,17 +1,19 @@ using Asp.Versioning; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Umbraco.Cms.Api.Management.ViewModels.DocumentType; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Models.Membership; using Umbraco.Cms.Core.Security; using Umbraco.Cms.Core.Services.ImportExport; using Umbraco.Cms.Core.Services.OperationStatus; +using Umbraco.Cms.Web.Common.Authorization; namespace Umbraco.Cms.Api.Management.Controllers.DocumentType; [ApiVersion("1.0")] +[Authorize(Policy = AuthorizationPolicies.TreeAccessDocumentTypes)] public class ImportExistingDocumentTypeController : DocumentTypeControllerBase { private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor; diff --git a/src/Umbraco.Cms.Api.Management/Controllers/DocumentType/ImportNewDocumentTypeController.cs b/src/Umbraco.Cms.Api.Management/Controllers/DocumentType/ImportNewDocumentTypeController.cs index 5b4fbde19910..9b0cb2af0d76 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/DocumentType/ImportNewDocumentTypeController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/DocumentType/ImportNewDocumentTypeController.cs @@ -1,17 +1,19 @@ using Asp.Versioning; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Umbraco.Cms.Api.Management.ViewModels.DocumentType; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Models.Membership; using Umbraco.Cms.Core.Security; using Umbraco.Cms.Core.Services.ImportExport; using Umbraco.Cms.Core.Services.OperationStatus; +using Umbraco.Cms.Web.Common.Authorization; namespace Umbraco.Cms.Api.Management.Controllers.DocumentType; [ApiVersion("1.0")] +[Authorize(Policy = AuthorizationPolicies.TreeAccessDocumentTypes)] public class ImportNewDocumentTypeController : DocumentTypeControllerBase { private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor; diff --git a/src/Umbraco.Cms.Api.Management/Controllers/DocumentType/Item/ItemDocumentTypeItemController.cs b/src/Umbraco.Cms.Api.Management/Controllers/DocumentType/Item/ItemDocumentTypeItemController.cs index 55211c75556f..a4ed4eaa29cc 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/DocumentType/Item/ItemDocumentTypeItemController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/DocumentType/Item/ItemDocumentTypeItemController.cs @@ -1,4 +1,4 @@ -using Asp.Versioning; +using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Umbraco.Cms.Api.Management.ViewModels.DocumentType.Item; @@ -23,17 +23,17 @@ public ItemDocumentTypeItemController(IContentTypeService contentTypeService, IU [HttpGet] [MapToApiVersion("1.0")] [ProducesResponseType(typeof(IEnumerable<DocumentTypeItemResponseModel>), StatusCodes.Status200OK)] - public async Task<IActionResult> Item( + public Task<IActionResult> Item( CancellationToken cancellationToken, [FromQuery(Name = "id")] HashSet<Guid> ids) { if (ids.Count is 0) { - return Ok(Enumerable.Empty<DocumentTypeItemResponseModel>()); + return Task.FromResult<IActionResult>(Ok(Enumerable.Empty<DocumentTypeItemResponseModel>())); } IEnumerable<IContentType> contentTypes = _contentTypeService.GetMany(ids); List<DocumentTypeItemResponseModel> responseModels = _mapper.MapEnumerable<IContentType, DocumentTypeItemResponseModel>(contentTypes); - return await Task.FromResult(Ok(responseModels)); + return Task.FromResult<IActionResult>(Ok(responseModels)); } } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/DocumentType/Item/SearchDocumentTypeItemController.cs b/src/Umbraco.Cms.Api.Management/Controllers/DocumentType/Item/SearchDocumentTypeItemController.cs index 5069df0d80cc..28708b52d1b7 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/DocumentType/Item/SearchDocumentTypeItemController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/DocumentType/Item/SearchDocumentTypeItemController.cs @@ -1,4 +1,4 @@ -using Asp.Versioning; +using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Umbraco.Cms.Api.Management.ViewModels.DocumentType.Item; @@ -27,12 +27,12 @@ public SearchDocumentTypeItemController(IEntitySearchService entitySearchService [HttpGet("search")] [MapToApiVersion("1.0")] [ProducesResponseType(typeof(PagedModel<DocumentTypeItemResponseModel>), StatusCodes.Status200OK)] - public async Task<IActionResult> Search(CancellationToken cancellationToken, string query, int skip = 0, int take = 100) + public Task<IActionResult> Search(CancellationToken cancellationToken, string query, int skip = 0, int take = 100) { PagedModel<IEntitySlim> searchResult = _entitySearchService.Search(UmbracoObjectTypes.DocumentType, query, skip, take); if (searchResult.Items.Any() is false) { - return await Task.FromResult(Ok(new PagedModel<DocumentTypeItemResponseModel> { Total = searchResult.Total })); + return Task.FromResult<IActionResult>(Ok(new PagedModel<DocumentTypeItemResponseModel> { Total = searchResult.Total })); } IEnumerable<IContentType> contentTypes = _contentTypeService.GetMany(searchResult.Items.Select(item => item.Key).ToArray().EmptyNull()); @@ -42,6 +42,6 @@ public async Task<IActionResult> Search(CancellationToken cancellationToken, str Total = searchResult.Total }; - return Ok(result); + return Task.FromResult<IActionResult>(Ok(result)); } } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/DynamicRoot/GetQueryStepsController.cs b/src/Umbraco.Cms.Api.Management/Controllers/DynamicRoot/GetQueryStepsController.cs index 1271c2ab3fc3..5dc68f4522d0 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/DynamicRoot/GetQueryStepsController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/DynamicRoot/GetQueryStepsController.cs @@ -21,10 +21,10 @@ public GetQueryStepsController(DynamicRootQueryStepCollection dynamicRootQuerySt [HttpGet($"steps")] [MapToApiVersion("1.0")] [ProducesResponseType(typeof(IEnumerable<string>), StatusCodes.Status200OK)] - public async Task<IActionResult> GetQuerySteps(CancellationToken cancellationToken) + public Task<IActionResult> GetQuerySteps(CancellationToken cancellationToken) { IEnumerable<string> querySteps = _dynamicRootQueryStepCollection.Select(x => x.SupportedDirectionAlias); - return await Task.FromResult(Ok(querySteps)); + return Task.FromResult<IActionResult>(Ok(querySteps)); } } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/HealthCheck/ExecuteActionHealthCheckController.cs b/src/Umbraco.Cms.Api.Management/Controllers/HealthCheck/ExecuteActionHealthCheckController.cs index 6f3e5ea933e2..4a8583f4fddb 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/HealthCheck/ExecuteActionHealthCheckController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/HealthCheck/ExecuteActionHealthCheckController.cs @@ -63,6 +63,6 @@ public async Task<ActionResult<HealthCheckResultResponseModel>> ExecuteAction( HealthCheckStatus result = await healthCheck.ExecuteActionAsync(_umbracoMapper.Map<HealthCheckAction>(action)!); - return await Task.FromResult(Ok(_umbracoMapper.Map<HealthCheckResultResponseModel>(result))); + return Ok(_umbracoMapper.Map<HealthCheckResultResponseModel>(result)); } } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/HealthCheck/Group/AllHealthCheckGroupController.cs b/src/Umbraco.Cms.Api.Management/Controllers/HealthCheck/Group/AllHealthCheckGroupController.cs index baaaf467bfe7..f41f352cc76d 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/HealthCheck/Group/AllHealthCheckGroupController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/HealthCheck/Group/AllHealthCheckGroupController.cs @@ -31,7 +31,7 @@ public AllHealthCheckGroupController( [HttpGet] [MapToApiVersion("1.0")] [ProducesResponseType(typeof(PagedViewModel<HealthCheckGroupResponseModel>), StatusCodes.Status200OK)] - public async Task<ActionResult<PagedViewModel<HealthCheckGroupResponseModel>>> All( + public Task<ActionResult<PagedViewModel<HealthCheckGroupResponseModel>>> All( CancellationToken cancellationToken, int skip = 0, int take = 100) @@ -46,6 +46,6 @@ public async Task<ActionResult<PagedViewModel<HealthCheckGroupResponseModel>>> A Items = _umbracoMapper.MapEnumerable<IGrouping<string?, Core.HealthChecks.HealthCheck>, HealthCheckGroupResponseModel>(groups.Skip(skip).Take(take)) }; - return await Task.FromResult(Ok(viewModel)); + return Task.FromResult<ActionResult<PagedViewModel<HealthCheckGroupResponseModel>>>(Ok(viewModel)); } } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/HealthCheck/Group/ByNameHealthCheckGroupController.cs b/src/Umbraco.Cms.Api.Management/Controllers/HealthCheck/Group/ByNameHealthCheckGroupController.cs index 1aa97c624980..6ffa173e9b5e 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/HealthCheck/Group/ByNameHealthCheckGroupController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/HealthCheck/Group/ByNameHealthCheckGroupController.cs @@ -31,7 +31,7 @@ public ByNameHealthCheckGroupController( [MapToApiVersion("1.0")] [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)] [ProducesResponseType(typeof(HealthCheckGroupPresentationModel), StatusCodes.Status200OK)] - public async Task<IActionResult> ByName( + public Task<IActionResult> ByName( CancellationToken cancellationToken, string name) { @@ -42,9 +42,9 @@ public async Task<IActionResult> ByName( if (group is null) { - return HealthCheckGroupNotFound(); + return Task.FromResult(HealthCheckGroupNotFound()); } - return await Task.FromResult(Ok(_umbracoMapper.Map<HealthCheckGroupPresentationModel>(group))); + return Task.FromResult<IActionResult>(Ok(_umbracoMapper.Map<HealthCheckGroupPresentationModel>(group))); } } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Help/GetHelpController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Help/GetHelpController.cs index 1c0ea568a125..e2227409b2ce 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Help/GetHelpController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Help/GetHelpController.cs @@ -1,4 +1,4 @@ -using Asp.Versioning; +using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; @@ -82,6 +82,5 @@ public async Task<IActionResult> Get( return Ok(PagedViewModel<HelpPageResponseModel>.Empty()); } - private bool IsAllowedUrl(string? url) => - _helpPageSettings.HelpPageUrlAllowList is null || _helpPageSettings.HelpPageUrlAllowList.Contains(url); + private bool IsAllowedUrl(string? url) => url is null || _helpPageSettings.HelpPageUrlAllowList.Contains(url); } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Indexer/DetailsIndexerController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Indexer/DetailsIndexerController.cs index a6d8a7c4070e..0eee31be10d1 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Indexer/DetailsIndexerController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Indexer/DetailsIndexerController.cs @@ -1,4 +1,4 @@ -using Asp.Versioning; +using Asp.Versioning; using Examine; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; @@ -34,11 +34,11 @@ public DetailsIndexerController( [MapToApiVersion("1.0")] [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)] [ProducesResponseType(typeof(IndexResponseModel), StatusCodes.Status200OK)] - public async Task<ActionResult<IndexResponseModel?>> Details(CancellationToken cancellationToken, string indexName) + public Task<ActionResult<IndexResponseModel?>> Details(CancellationToken cancellationToken, string indexName) { if (_examineManager.TryGetIndex(indexName, out IIndex? index)) { - return await Task.FromResult(_indexPresentationFactory.Create(index!)); + return Task.FromResult<ActionResult<IndexResponseModel?>>(_indexPresentationFactory.Create(index)); } var invalidModelProblem = new ProblemDetails @@ -49,7 +49,6 @@ public DetailsIndexerController( Type = "Error", }; - return await Task.FromResult(NotFound(invalidModelProblem)); - + return Task.FromResult<ActionResult<IndexResponseModel?>>(NotFound(invalidModelProblem)); } } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Indexer/RebuildIndexerController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Indexer/RebuildIndexerController.cs index 97131adcb904..cba22529913a 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Indexer/RebuildIndexerController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Indexer/RebuildIndexerController.cs @@ -36,7 +36,7 @@ public RebuildIndexerController( [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)] [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status409Conflict)] [ProducesResponseType(StatusCodes.Status200OK)] - public async Task<IActionResult> Rebuild(CancellationToken cancellationToken, string indexName) + public Task<IActionResult> Rebuild(CancellationToken cancellationToken, string indexName) { if (!_examineManager.TryGetIndex(indexName, out IIndex? index)) { @@ -48,7 +48,7 @@ public async Task<IActionResult> Rebuild(CancellationToken cancellationToken, st Type = "Error", }; - return await Task.FromResult(NotFound(invalidModelProblem)); + return Task.FromResult<IActionResult>(NotFound(invalidModelProblem)); } if (!_indexingRebuilderService.CanRebuild(index.Name)) @@ -62,14 +62,14 @@ public async Task<IActionResult> Rebuild(CancellationToken cancellationToken, st Type = "Error", }; - return await Task.FromResult(BadRequest(invalidModelProblem)); + return Task.FromResult<IActionResult>(BadRequest(invalidModelProblem)); } _logger.LogInformation("Rebuilding index '{IndexName}'", indexName); if (_indexingRebuilderService.TryRebuild(index, indexName)) { - return await Task.FromResult(Ok()); + return Task.FromResult<IActionResult>(Ok()); } var problemDetails = new ProblemDetails @@ -80,6 +80,6 @@ public async Task<IActionResult> Rebuild(CancellationToken cancellationToken, st Type = "Error", }; - return await Task.FromResult(Conflict(problemDetails)); + return Task.FromResult<IActionResult>(Conflict(problemDetails)); } } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/LogViewer/AllSinkLevelLogViewerController.cs b/src/Umbraco.Cms.Api.Management/Controllers/LogViewer/AllSinkLevelLogViewerController.cs index f2ec9e034ffe..e3bd05292266 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/LogViewer/AllSinkLevelLogViewerController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/LogViewer/AllSinkLevelLogViewerController.cs @@ -30,7 +30,7 @@ public AllSinkLevelLogViewerController(ILogViewerService logViewerService, IUmbr [HttpGet("level")] [MapToApiVersion("1.0")] [ProducesResponseType(typeof(PagedViewModel<LoggerResponseModel>), StatusCodes.Status200OK)] - public async Task<ActionResult<PagedViewModel<LoggerResponseModel>>> AllLogLevels( + public Task<ActionResult<PagedViewModel<LoggerResponseModel>>> AllLogLevels( CancellationToken cancellationToken, int skip = 0, int take = 100) @@ -45,6 +45,6 @@ public async Task<ActionResult<PagedViewModel<LoggerResponseModel>>> AllLogLevel Items = _umbracoMapper.MapEnumerable<KeyValuePair<string, LogLevel>, LoggerResponseModel>(logLevels.Skip(skip).Take(take)) }; - return await Task.FromResult(Ok(viewModel)); + return Task.FromResult<ActionResult<PagedViewModel<LoggerResponseModel>>>(Ok(viewModel)); } } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Media/Item/ItemMediaItemController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Media/Item/ItemMediaItemController.cs index ddea5cfbcba6..7bab65c3caa3 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Media/Item/ItemMediaItemController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Media/Item/ItemMediaItemController.cs @@ -1,4 +1,4 @@ -using Asp.Versioning; +using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Umbraco.Cms.Api.Management.Factories; @@ -25,13 +25,13 @@ public ItemMediaItemController(IEntityService entityService, IMediaPresentationF [HttpGet] [MapToApiVersion("1.0")] [ProducesResponseType(typeof(IEnumerable<MediaItemResponseModel>), StatusCodes.Status200OK)] - public async Task<IActionResult> Item( + public Task<IActionResult> Item( CancellationToken cancellationToken, [FromQuery(Name = "id")] HashSet<Guid> ids) { if (ids.Count is 0) { - return Ok(Enumerable.Empty<MediaItemResponseModel>()); + return Task.FromResult<IActionResult>(Ok(Enumerable.Empty<MediaItemResponseModel>())); } IEnumerable<IMediaEntitySlim> media = _entityService @@ -39,6 +39,6 @@ public async Task<IActionResult> Item( .OfType<IMediaEntitySlim>(); IEnumerable<MediaItemResponseModel> responseModels = media.Select(_mediaPresentationFactory.CreateItemResponseModel); - return await Task.FromResult(Ok(responseModels)); + return Task.FromResult<IActionResult>(Ok(responseModels)); } } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Media/Item/SearchMediaItemController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Media/Item/SearchMediaItemController.cs index c7cccd0cea62..a29814de4d65 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Media/Item/SearchMediaItemController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Media/Item/SearchMediaItemController.cs @@ -1,4 +1,4 @@ -using Asp.Versioning; +using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Umbraco.Cms.Api.Management.Factories; @@ -35,7 +35,7 @@ public async Task<IActionResult> SearchFromParent(CancellationToken cancellation [HttpGet("search")] [MapToApiVersion("1.0")] [ProducesResponseType(typeof(PagedModel<MediaItemResponseModel>), StatusCodes.Status200OK)] - public async Task<IActionResult> SearchFromParentWithAllowedTypes(CancellationToken cancellationToken, string query, int skip = 0, int take = 100, Guid? parentId = null, [FromQuery]IEnumerable<Guid>? allowedMediaTypes = null) + public Task<IActionResult> SearchFromParentWithAllowedTypes(CancellationToken cancellationToken, string query, int skip = 0, int take = 100, Guid? parentId = null, [FromQuery]IEnumerable<Guid>? allowedMediaTypes = null) { PagedModel<IEntitySlim> searchResult = _indexedEntitySearchService.Search(UmbracoObjectTypes.Media, query, parentId, allowedMediaTypes, skip, take); var result = new PagedModel<MediaItemResponseModel> @@ -44,6 +44,6 @@ public async Task<IActionResult> SearchFromParentWithAllowedTypes(CancellationTo Total = searchResult.Total, }; - return await Task.FromResult(Ok(result)); + return Task.FromResult<IActionResult>(Ok(result)); } } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Media/MediaUrlController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Media/MediaUrlController.cs index d967bd943513..1db1a3c1be9d 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Media/MediaUrlController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Media/MediaUrlController.cs @@ -1,4 +1,4 @@ -using Asp.Versioning; +using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Umbraco.Cms.Api.Management.Factories; @@ -25,10 +25,10 @@ public MediaUrlController( [MapToApiVersion("1.0")] [HttpGet("urls")] [ProducesResponseType(typeof(IEnumerable<MediaUrlInfoResponseModel>), StatusCodes.Status200OK)] - public async Task<IActionResult> GetUrls([FromQuery(Name = "id")] HashSet<Guid> ids) + public Task<IActionResult> GetUrls([FromQuery(Name = "id")] HashSet<Guid> ids) { IEnumerable<IMedia> items = _mediaService.GetByIds(ids); - return await Task.FromResult(Ok(_mediaUrlFactory.CreateUrlSets(items))); + return Task.FromResult<IActionResult>(Ok(_mediaUrlFactory.CreateUrlSets(items))); } } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Media/References/AreReferencedMediaController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Media/References/AreReferencedMediaController.cs index 89e0c2d1ba50..d10bdb962740 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Media/References/AreReferencedMediaController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Media/References/AreReferencedMediaController.cs @@ -1,4 +1,4 @@ -using Asp.Versioning; +using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Umbraco.Cms.Api.Common.ViewModels.Pagination; @@ -46,6 +46,6 @@ public async Task<ActionResult<PagedViewModel<ReferenceByIdModel>>> GetPagedRefe Items = _umbracoMapper.MapEnumerable<Guid, ReferenceByIdModel>(distinctByKeyItemsWithReferencedRelations.Items), }; - return await Task.FromResult(pagedViewModel); + return pagedViewModel; } } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Media/References/ReferencedByMediaController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Media/References/ReferencedByMediaController.cs index 63748741b1f7..e9e62504edba 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Media/References/ReferencedByMediaController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Media/References/ReferencedByMediaController.cs @@ -45,6 +45,6 @@ public async Task<ActionResult<PagedViewModel<IReferenceResponseModel>>> Referen Items = await _relationTypePresentationFactory.CreateReferenceResponseModelsAsync(relationItems.Items), }; - return await Task.FromResult(pagedViewModel); + return pagedViewModel; } } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Media/References/ReferencedDescendantsMediaController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Media/References/ReferencedDescendantsMediaController.cs index 2990602e026a..c6c16cb222d5 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Media/References/ReferencedDescendantsMediaController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Media/References/ReferencedDescendantsMediaController.cs @@ -1,4 +1,4 @@ -using Asp.Versioning; +using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Umbraco.Cms.Api.Common.ViewModels.Pagination; @@ -45,6 +45,6 @@ public async Task<ActionResult<PagedViewModel<ReferenceByIdModel>>> ReferencedDe Items = _umbracoMapper.MapEnumerable<RelationItemModel, ReferenceByIdModel>(relationItems.Items), }; - return await Task.FromResult(pagedViewModel); + return pagedViewModel; } } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/MediaType/AllowedChildrenMediaTypeController.cs b/src/Umbraco.Cms.Api.Management/Controllers/MediaType/AllowedChildrenMediaTypeController.cs index 231db5646e16..71ee63d6e3a2 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/MediaType/AllowedChildrenMediaTypeController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/MediaType/AllowedChildrenMediaTypeController.cs @@ -1,4 +1,4 @@ -using Asp.Versioning; +using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Umbraco.Cms.Api.Common.ViewModels.Pagination; @@ -23,6 +23,15 @@ public AllowedChildrenMediaTypeController(IMediaTypeService mediaTypeService, IU _umbracoMapper = umbracoMapper; } + [NonAction] + [Obsolete("Use the non obsoleted method instead. Scheduled for removal in Umbraco 16.")] + public async Task<IActionResult> AllowedChildrenByKey( + CancellationToken cancellationToken, + Guid id, + int skip = 0, + int take = 100) + => await AllowedChildrenByKey(cancellationToken, id, null, skip, take); + [HttpGet("{id:guid}/allowed-children")] [MapToApiVersion("1.0")] [ProducesResponseType(typeof(PagedViewModel<AllowedMediaType>), StatusCodes.Status200OK)] @@ -30,10 +39,11 @@ public AllowedChildrenMediaTypeController(IMediaTypeService mediaTypeService, IU public async Task<IActionResult> AllowedChildrenByKey( CancellationToken cancellationToken, Guid id, + Guid? parentContentKey = null, int skip = 0, int take = 100) { - Attempt<PagedModel<IMediaType>?, ContentTypeOperationStatus> attempt = await _mediaTypeService.GetAllowedChildrenAsync(id, skip, take); + Attempt<PagedModel<IMediaType>?, ContentTypeOperationStatus> attempt = await _mediaTypeService.GetAllowedChildrenAsync(id, parentContentKey, skip, take); if (attempt.Success is false) { return OperationStatusResult(attempt.Status); diff --git a/src/Umbraco.Cms.Api.Management/Controllers/MediaType/ByKeyMediaTypeController.cs b/src/Umbraco.Cms.Api.Management/Controllers/MediaType/ByKeyMediaTypeController.cs index ec3d955b2f60..75a32569bd52 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/MediaType/ByKeyMediaTypeController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/MediaType/ByKeyMediaTypeController.cs @@ -1,4 +1,4 @@ -using Asp.Versioning; +using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Umbraco.Cms.Api.Management.ViewModels.MediaType; @@ -34,6 +34,6 @@ public async Task<IActionResult> ByKey(CancellationToken cancellationToken, Guid } MediaTypeResponseModel model = _umbracoMapper.Map<MediaTypeResponseModel>(mediaType)!; - return await Task.FromResult(Ok(model)); + return Ok(model); } } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/MediaType/ExportMediaTypeController.cs b/src/Umbraco.Cms.Api.Management/Controllers/MediaType/ExportMediaTypeController.cs index 12f8540a4ea6..40423c3b1d73 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/MediaType/ExportMediaTypeController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/MediaType/ExportMediaTypeController.cs @@ -1,14 +1,17 @@ using Asp.Versioning; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Umbraco.Cms.Api.Management.Factories; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Services.OperationStatus; +using Umbraco.Cms.Web.Common.Authorization; namespace Umbraco.Cms.Api.Management.Controllers.MediaType; [ApiVersion("1.0")] +[Authorize(Policy = AuthorizationPolicies.TreeAccessMediaTypes)] public class ExportMediaTypeController : MediaTypeControllerBase { private readonly IMediaTypeService _mediaTypeService; diff --git a/src/Umbraco.Cms.Api.Management/Controllers/MediaType/ImportExistingMediaTypeController.cs b/src/Umbraco.Cms.Api.Management/Controllers/MediaType/ImportExistingMediaTypeController.cs index 9c4ba5ed9592..9aedac01e48a 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/MediaType/ImportExistingMediaTypeController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/MediaType/ImportExistingMediaTypeController.cs @@ -1,17 +1,19 @@ using Asp.Versioning; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Umbraco.Cms.Api.Management.ViewModels.MediaType; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Models.Membership; using Umbraco.Cms.Core.Security; using Umbraco.Cms.Core.Services.ImportExport; using Umbraco.Cms.Core.Services.OperationStatus; +using Umbraco.Cms.Web.Common.Authorization; namespace Umbraco.Cms.Api.Management.Controllers.MediaType; [ApiVersion("1.0")] +[Authorize(Policy = AuthorizationPolicies.TreeAccessMediaTypes)] public class ImportExistingMediaTypeController : MediaTypeControllerBase { private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor; diff --git a/src/Umbraco.Cms.Api.Management/Controllers/MediaType/ImportNewMediaTypeController.cs b/src/Umbraco.Cms.Api.Management/Controllers/MediaType/ImportNewMediaTypeController.cs index 868822e49498..e71e2b2abb72 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/MediaType/ImportNewMediaTypeController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/MediaType/ImportNewMediaTypeController.cs @@ -1,18 +1,19 @@ using Asp.Versioning; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using Umbraco.Cms.Api.Management.Controllers.DocumentType; using Umbraco.Cms.Api.Management.ViewModels.MediaType; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Models.Membership; using Umbraco.Cms.Core.Security; using Umbraco.Cms.Core.Services.ImportExport; using Umbraco.Cms.Core.Services.OperationStatus; +using Umbraco.Cms.Web.Common.Authorization; namespace Umbraco.Cms.Api.Management.Controllers.MediaType; [ApiVersion("1.0")] +[Authorize(Policy = AuthorizationPolicies.TreeAccessMediaTypes)] public class ImportNewMediaTypeController : MediaTypeControllerBase { private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor; diff --git a/src/Umbraco.Cms.Api.Management/Controllers/MediaType/Item/ItemMediaTypeItemController.cs b/src/Umbraco.Cms.Api.Management/Controllers/MediaType/Item/ItemMediaTypeItemController.cs index 14bcff0e5135..92133d280868 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/MediaType/Item/ItemMediaTypeItemController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/MediaType/Item/ItemMediaTypeItemController.cs @@ -1,4 +1,4 @@ -using Asp.Versioning; +using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Umbraco.Cms.Api.Management.ViewModels.MediaType.Item; @@ -23,17 +23,17 @@ public ItemMediaTypeItemController(IMediaTypeService mediaTypeService, IUmbracoM [HttpGet] [MapToApiVersion("1.0")] [ProducesResponseType(typeof(IEnumerable<MediaTypeItemResponseModel>), StatusCodes.Status200OK)] - public async Task<IActionResult> Item( + public Task<IActionResult> Item( CancellationToken cancellationToken, [FromQuery(Name = "id")] HashSet<Guid> ids) { if (ids.Count is 0) { - return Ok(Enumerable.Empty<MediaTypeItemResponseModel>()); + return Task.FromResult<IActionResult>(Ok(Enumerable.Empty<MediaTypeItemResponseModel>())); } IEnumerable<IMediaType> mediaTypes = _mediaTypeService.GetMany(ids); List<MediaTypeItemResponseModel> responseModels = _mapper.MapEnumerable<IMediaType, MediaTypeItemResponseModel>(mediaTypes); - return await Task.FromResult(Ok(responseModels)); + return Task.FromResult<IActionResult>(Ok(responseModels)); } } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/MediaType/Item/SearchMediaTypeItemController.cs b/src/Umbraco.Cms.Api.Management/Controllers/MediaType/Item/SearchMediaTypeItemController.cs index 475087766f96..a6fbade1c77a 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/MediaType/Item/SearchMediaTypeItemController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/MediaType/Item/SearchMediaTypeItemController.cs @@ -1,4 +1,4 @@ -using Asp.Versioning; +using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Umbraco.Cms.Api.Management.ViewModels.MediaType.Item; @@ -27,12 +27,12 @@ public SearchMediaTypeItemController(IEntitySearchService entitySearchService, I [HttpGet("search")] [MapToApiVersion("1.0")] [ProducesResponseType(typeof(PagedModel<MediaTypeItemResponseModel>), StatusCodes.Status200OK)] - public async Task<IActionResult> Search(CancellationToken cancellationToken, string query, int skip = 0, int take = 100) + public Task<IActionResult> Search(CancellationToken cancellationToken, string query, int skip = 0, int take = 100) { PagedModel<IEntitySlim> searchResult = _entitySearchService.Search(UmbracoObjectTypes.MediaType, query, skip, take); if (searchResult.Items.Any() is false) { - return await Task.FromResult(Ok(new PagedModel<MediaTypeItemResponseModel> { Total = searchResult.Total })); + return Task.FromResult<IActionResult>(Ok(new PagedModel<MediaTypeItemResponseModel> { Total = searchResult.Total })); } IEnumerable<IMediaType> mediaTypes = _mediaTypeService.GetMany(searchResult.Items.Select(item => item.Key).ToArray().EmptyNull()); @@ -42,6 +42,6 @@ public async Task<IActionResult> Search(CancellationToken cancellationToken, str Total = searchResult.Total }; - return Ok(result); + return Task.FromResult<IActionResult>(Ok(result)); } } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/MediaType/MediaTypeControllerBase.cs b/src/Umbraco.Cms.Api.Management/Controllers/MediaType/MediaTypeControllerBase.cs index 38d11c175d5f..387d260cde62 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/MediaType/MediaTypeControllerBase.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/MediaType/MediaTypeControllerBase.cs @@ -1,9 +1,8 @@ -using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Umbraco.Cms.Api.Management.Controllers.DocumentType; using Umbraco.Cms.Api.Management.Routing; -using Umbraco.Cms.Api.Management.ViewModels.MediaType; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Services.OperationStatus; using Umbraco.Cms.Web.Common.Authorization; diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Member/Item/ItemMemberItemController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Member/Item/ItemMemberItemController.cs index eb81efed183e..8e2e73538cae 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Member/Item/ItemMemberItemController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Member/Item/ItemMemberItemController.cs @@ -1,4 +1,4 @@ -using Asp.Versioning; +using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Umbraco.Cms.Api.Management.Factories; @@ -25,13 +25,13 @@ public ItemMemberItemController(IEntityService entityService, IMemberPresentatio [HttpGet] [MapToApiVersion("1.0")] [ProducesResponseType(typeof(IEnumerable<MemberItemResponseModel>), StatusCodes.Status200OK)] - public async Task<IActionResult> Item( + public Task<IActionResult> Item( CancellationToken cancellationToken, [FromQuery(Name = "id")] HashSet<Guid> ids) { if (ids.Count is 0) { - return Ok(Enumerable.Empty<MemberItemResponseModel>()); + return Task.FromResult<IActionResult>(Ok(Enumerable.Empty<MemberItemResponseModel>())); } IEnumerable<IMemberEntitySlim> members = _entityService @@ -39,6 +39,6 @@ public async Task<IActionResult> Item( .OfType<IMemberEntitySlim>(); IEnumerable<MemberItemResponseModel> responseModels = members.Select(_memberPresentationFactory.CreateItemResponseModel); - return await Task.FromResult(Ok(responseModels)); + return Task.FromResult<IActionResult>(Ok(responseModels)); } } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Member/Item/SearchMemberItemController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Member/Item/SearchMemberItemController.cs index 187dc239b399..eac1a3909ee2 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Member/Item/SearchMemberItemController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Member/Item/SearchMemberItemController.cs @@ -1,4 +1,4 @@ -using Asp.Versioning; +using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Umbraco.Cms.Api.Management.Factories; @@ -23,13 +23,13 @@ public SearchMemberItemController(IIndexedEntitySearchService indexedEntitySearc [NonAction] [Obsolete("Scheduled to be removed in v16, use the non obsoleted method instead")] - public async Task<IActionResult> Search(CancellationToken cancellationToken, string query, int skip = 0, int take = 100) - => await SearchWithAllowedTypes(cancellationToken, query, skip, take); + public Task<IActionResult> Search(CancellationToken cancellationToken, string query, int skip = 0, int take = 100) + => SearchWithAllowedTypes(cancellationToken, query, skip, take); [HttpGet("search")] [MapToApiVersion("1.0")] [ProducesResponseType(typeof(PagedModel<MemberItemResponseModel>), StatusCodes.Status200OK)] - public async Task<IActionResult> SearchWithAllowedTypes(CancellationToken cancellationToken, string query, int skip = 0, int take = 100, [FromQuery]IEnumerable<Guid>? allowedMemberTypes = null) + public Task<IActionResult> SearchWithAllowedTypes(CancellationToken cancellationToken, string query, int skip = 0, int take = 100, [FromQuery]IEnumerable<Guid>? allowedMemberTypes = null) { PagedModel<IEntitySlim> searchResult = _indexedEntitySearchService.Search(UmbracoObjectTypes.Member, query, null, allowedMemberTypes, skip, take); var result = new PagedModel<MemberItemResponseModel> @@ -38,6 +38,6 @@ public async Task<IActionResult> SearchWithAllowedTypes(CancellationToken cancel Total = searchResult.Total }; - return await Task.FromResult(Ok(result)); + return Task.FromResult<IActionResult>(Ok(result)); } } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/MemberGroup/AllMemberGroupController.cs b/src/Umbraco.Cms.Api.Management/Controllers/MemberGroup/AllMemberGroupController.cs index 8f2bfd73182f..0f5b3384ba37 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/MemberGroup/AllMemberGroupController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/MemberGroup/AllMemberGroupController.cs @@ -1,4 +1,4 @@ -using Asp.Versioning; +using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Umbraco.Cms.Api.Common.ViewModels.Pagination; @@ -36,6 +36,6 @@ public async Task<ActionResult<PagedViewModel<MemberGroupResponseModel>>> All( Items = _mapper.MapEnumerable<IMemberGroup, MemberGroupResponseModel>(memberGroups.Skip(skip).Take(take)), }; - return await Task.FromResult(Ok(viewModel)); + return Ok(viewModel); } } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/MemberGroup/Item/ItemMemberGroupItemController.cs b/src/Umbraco.Cms.Api.Management/Controllers/MemberGroup/Item/ItemMemberGroupItemController.cs index 4a7b2e762d01..479a8c561704 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/MemberGroup/Item/ItemMemberGroupItemController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/MemberGroup/Item/ItemMemberGroupItemController.cs @@ -25,17 +25,17 @@ public ItemMemberGroupItemController(IEntityService entityService, IUmbracoMappe [HttpGet] [MapToApiVersion("1.0")] [ProducesResponseType(typeof(IEnumerable<MemberGroupItemResponseModel>), StatusCodes.Status200OK)] - public async Task<IActionResult> Item( + public Task<IActionResult> Item( CancellationToken cancellationToken, [FromQuery(Name = "id")] HashSet<Guid> ids) { if (ids.Count is 0) { - return Ok(Enumerable.Empty<MemberGroupItemResponseModel>()); + return Task.FromResult<IActionResult>(Ok(Enumerable.Empty<MemberGroupItemResponseModel>())); } IEnumerable<IEntitySlim> memberGroups = _entityService.GetAll(UmbracoObjectTypes.MemberGroup, ids.ToArray()); List<MemberGroupItemResponseModel> responseModel = _mapper.MapEnumerable<IEntitySlim, MemberGroupItemResponseModel>(memberGroups); - return Ok(responseModel); + return Task.FromResult<IActionResult>(Ok(responseModel)); } } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/MemberType/Item/ItemMemberTypeItemController.cs b/src/Umbraco.Cms.Api.Management/Controllers/MemberType/Item/ItemMemberTypeItemController.cs index c110e9ef6b0c..8aa28468b1df 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/MemberType/Item/ItemMemberTypeItemController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/MemberType/Item/ItemMemberTypeItemController.cs @@ -23,17 +23,17 @@ public ItemMemberTypeItemController(IUmbracoMapper mapper, IMemberTypeService me [HttpGet] [MapToApiVersion("1.0")] [ProducesResponseType(typeof(IEnumerable<MemberTypeItemResponseModel>), StatusCodes.Status200OK)] - public async Task<IActionResult> Item( + public Task<IActionResult> Item( CancellationToken cancellationToken, [FromQuery(Name = "id")] HashSet<Guid> ids) { if (ids.Count is 0) { - return Ok(Enumerable.Empty<MemberTypeItemResponseModel>()); + return Task.FromResult<IActionResult>(Ok(Enumerable.Empty<MemberTypeItemResponseModel>())); } IEnumerable<IMemberType> memberTypes = _memberTypeService.GetMany(ids); List<MemberTypeItemResponseModel> responseModels = _mapper.MapEnumerable<IMemberType, MemberTypeItemResponseModel>(memberTypes); - return Ok(responseModels); + return Task.FromResult<IActionResult>(Ok(responseModels)); } } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/MemberType/Item/SearchMemberTypeItemController.cs b/src/Umbraco.Cms.Api.Management/Controllers/MemberType/Item/SearchMemberTypeItemController.cs index 6e8151d8a29e..0d211bad43ba 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/MemberType/Item/SearchMemberTypeItemController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/MemberType/Item/SearchMemberTypeItemController.cs @@ -1,4 +1,4 @@ -using Asp.Versioning; +using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Umbraco.Cms.Api.Management.ViewModels.MemberType.Item; @@ -26,12 +26,12 @@ public SearchMemberTypeItemController(IEntitySearchService entitySearchService, [HttpGet("search")] [MapToApiVersion("1.0")] [ProducesResponseType(typeof(PagedModel<MemberTypeItemResponseModel>), StatusCodes.Status200OK)] - public async Task<IActionResult> Search(CancellationToken cancellationToken, string query, int skip = 0, int take = 100) + public Task<IActionResult> Search(CancellationToken cancellationToken, string query, int skip = 0, int take = 100) { PagedModel<IEntitySlim> searchResult = _entitySearchService.Search(UmbracoObjectTypes.MemberType, query, skip, take); if (searchResult.Items.Any() is false) { - return await Task.FromResult(Ok(new PagedModel<MemberTypeItemResponseModel> { Total = searchResult.Total })); + return Task.FromResult<IActionResult>(Ok(new PagedModel<MemberTypeItemResponseModel> { Total = searchResult.Total })); } IEnumerable<IMemberType> memberTypes = _memberTypeService.GetMany(searchResult.Items.Select(item => item.Key).ToArray()); @@ -41,6 +41,6 @@ public async Task<IActionResult> Search(CancellationToken cancellationToken, str Total = searchResult.Total }; - return Ok(result); + return Task.FromResult<IActionResult>(Ok(result)); } } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/ModelsBuilder/BuildModelsBuilderController.cs b/src/Umbraco.Cms.Api.Management/Controllers/ModelsBuilder/BuildModelsBuilderController.cs index 14adb70c3b60..bd4af3e97b4d 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/ModelsBuilder/BuildModelsBuilderController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/ModelsBuilder/BuildModelsBuilderController.cs @@ -56,7 +56,7 @@ public BuildModelsBuilderController( [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status428PreconditionRequired)] [MapToApiVersion("1.0")] - public async Task<IActionResult> BuildModels(CancellationToken cancellationToken) + public Task<IActionResult> BuildModels(CancellationToken cancellationToken) { try { @@ -70,7 +70,7 @@ public async Task<IActionResult> BuildModels(CancellationToken cancellationToken Type = "Error", }; - return new ObjectResult(problemDetailsModel) { StatusCode = StatusCodes.Status428PreconditionRequired }; + return Task.FromResult<IActionResult>(new ObjectResult(problemDetailsModel) { StatusCode = StatusCodes.Status428PreconditionRequired }); } _modelGenerator.GenerateModels(); @@ -81,6 +81,6 @@ public async Task<IActionResult> BuildModels(CancellationToken cancellationToken _mbErrors.Report("Failed to build models.", e); } - return Ok(); + return Task.FromResult<IActionResult>(Ok()); } } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/ModelsBuilder/GetModelsBuilderController.cs b/src/Umbraco.Cms.Api.Management/Controllers/ModelsBuilder/GetModelsBuilderController.cs index 764ba65b75ce..492ad23c6399 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/ModelsBuilder/GetModelsBuilderController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/ModelsBuilder/GetModelsBuilderController.cs @@ -1,4 +1,4 @@ -using Asp.Versioning; +using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Umbraco.Cms.Api.Management.Factories; @@ -16,6 +16,6 @@ public class GetModelsBuilderController : ModelsBuilderControllerBase [HttpGet("dashboard")] [ProducesResponseType(typeof(ModelsBuilderResponseModel), StatusCodes.Status200OK)] [MapToApiVersion("1.0")] - public async Task<ActionResult<ModelsBuilderResponseModel>> GetDashboard(CancellationToken cancellationToken) - => await Task.FromResult(Ok(_modelsBuilderPresentationFactory.Create())); + public Task<ActionResult<ModelsBuilderResponseModel>> GetDashboard(CancellationToken cancellationToken) + => Task.FromResult<ActionResult<ModelsBuilderResponseModel>>(Ok(_modelsBuilderPresentationFactory.Create())); } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/ModelsBuilder/StatusModelsBuilderController.cs b/src/Umbraco.Cms.Api.Management/Controllers/ModelsBuilder/StatusModelsBuilderController.cs index fcbcd9e3cc15..f49b16b8517f 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/ModelsBuilder/StatusModelsBuilderController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/ModelsBuilder/StatusModelsBuilderController.cs @@ -1,4 +1,4 @@ -using System.Threading.Tasks; +using System.Threading.Tasks; using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; @@ -18,7 +18,7 @@ public class StatusModelsBuilderController : ModelsBuilderControllerBase [HttpGet("status")] [ProducesResponseType(typeof(OutOfDateStatusResponseModel), StatusCodes.Status200OK)] [MapToApiVersion("1.0")] - public async Task<ActionResult<OutOfDateStatusResponseModel>> GetModelsOutOfDateStatus(CancellationToken cancellationToken) + public Task<ActionResult<OutOfDateStatusResponseModel>> GetModelsOutOfDateStatus(CancellationToken cancellationToken) { OutOfDateStatusResponseModel status = _outOfDateModelsStatus.IsEnabled ? _outOfDateModelsStatus.IsOutOfDate @@ -26,6 +26,6 @@ public async Task<ActionResult<OutOfDateStatusResponseModel>> GetModelsOutOfDate : new OutOfDateStatusResponseModel { Status = OutOfDateType.Current } : new OutOfDateStatusResponseModel { Status = OutOfDateType.Unknown }; - return await Task.FromResult(Ok(status)); + return Task.FromResult<ActionResult<OutOfDateStatusResponseModel>>(Ok(status)); } } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/ObjectTypes/AllowedObjectTypesController.cs b/src/Umbraco.Cms.Api.Management/Controllers/ObjectTypes/AllowedObjectTypesController.cs index 394ddb657370..7b5687971d68 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/ObjectTypes/AllowedObjectTypesController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/ObjectTypes/AllowedObjectTypesController.cs @@ -1,4 +1,4 @@ -using Asp.Versioning; +using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Umbraco.Cms.Api.Common.ViewModels.Pagination; @@ -17,11 +17,11 @@ public class AllowedObjectTypesController : ObjectTypesControllerBase [HttpGet] [MapToApiVersion("1.0")] [ProducesResponseType(typeof(PagedViewModel<ObjectTypeResponseModel>), StatusCodes.Status200OK)] - public async Task<IActionResult> Allowed(CancellationToken cancellationToken, int skip = 0, int take = 100) + public Task<IActionResult> Allowed(CancellationToken cancellationToken, int skip = 0, int take = 100) { ObjectTypeResponseModel[] objectTypes = _objectTypePresentationFactory.Create().ToArray(); - return await Task.FromResult(Ok(new PagedViewModel<ObjectTypeResponseModel> + return Task.FromResult<IActionResult>(Ok(new PagedViewModel<ObjectTypeResponseModel> { Total = objectTypes.Length, Items = objectTypes.Skip(skip).Take(take), diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Package/Created/AllCreatedPackageController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Package/Created/AllCreatedPackageController.cs index 64ab3dec4755..73ef8030115f 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Package/Created/AllCreatedPackageController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Package/Created/AllCreatedPackageController.cs @@ -44,6 +44,6 @@ public async Task<ActionResult<PagedViewModel<PackageDefinitionResponseModel>>> Items = _umbracoMapper.MapEnumerable<PackageDefinition, PackageDefinitionResponseModel>(createdPackages.Items) }; - return await Task.FromResult(Ok(viewModel)); + return Ok(viewModel); } } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/PartialView/Item/ItemPartialViewItemController.cs b/src/Umbraco.Cms.Api.Management/Controllers/PartialView/Item/ItemPartialViewItemController.cs index 370f1ab8f525..27f09cb4d40e 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/PartialView/Item/ItemPartialViewItemController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/PartialView/Item/ItemPartialViewItemController.cs @@ -1,4 +1,4 @@ -using Asp.Versioning; +using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Umbraco.Cms.Api.Management.Extensions; @@ -18,17 +18,17 @@ public ItemPartialViewItemController(IFileItemPresentationFactory fileItemPresen [HttpGet] [MapToApiVersion("1.0")] [ProducesResponseType(typeof(IEnumerable<PartialViewItemResponseModel>), StatusCodes.Status200OK)] - public async Task<IActionResult> Item( + public Task<IActionResult> Item( CancellationToken cancellationToken, [FromQuery(Name = "path")] HashSet<string> paths) { if (paths.Count is 0) { - return Ok(Enumerable.Empty<PartialViewItemResponseModel>()); + return Task.FromResult<IActionResult>(Ok(Enumerable.Empty<PartialViewItemResponseModel>())); } paths = paths.Select(path => path.VirtualPathToSystemPath()).ToHashSet(); IEnumerable<PartialViewItemResponseModel> responseModels = _fileItemPresentationFactory.CreatePartialViewItemResponseModels(paths); - return await Task.FromResult(Ok(responseModels)); + return Task.FromResult<IActionResult>(Ok(responseModels)); } } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/PublishedCache/RebuildPublishedCacheController.cs b/src/Umbraco.Cms.Api.Management/Controllers/PublishedCache/RebuildPublishedCacheController.cs index d48ad9fdbbac..6083df32c191 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/PublishedCache/RebuildPublishedCacheController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/PublishedCache/RebuildPublishedCacheController.cs @@ -1,4 +1,4 @@ -using Asp.Versioning; +using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Umbraco.Cms.Core.PublishedCache; @@ -17,7 +17,20 @@ public class RebuildPublishedCacheController : PublishedCacheControllerBase [ProducesResponseType(StatusCodes.Status200OK)] public async Task<IActionResult> Rebuild(CancellationToken cancellationToken) { - _databaseCacheRebuilder.Rebuild(); + if (_databaseCacheRebuilder.IsRebuilding()) + { + var problemDetails = new ProblemDetails + { + Title = "Database cache can not be rebuilt", + Detail = "The database cache is in the process of rebuilding.", + Status = StatusCodes.Status400BadRequest, + Type = "Error", + }; + + return await Task.FromResult(Conflict(problemDetails)); + } + + _databaseCacheRebuilder.Rebuild(true); return await Task.FromResult(Ok()); } } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/PublishedCache/RebuildPublishedCacheStatusController.cs b/src/Umbraco.Cms.Api.Management/Controllers/PublishedCache/RebuildPublishedCacheStatusController.cs new file mode 100644 index 000000000000..5ecceecd3dd4 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Controllers/PublishedCache/RebuildPublishedCacheStatusController.cs @@ -0,0 +1,27 @@ +using Asp.Versioning; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Api.Management.ViewModels.PublishedCache; +using Umbraco.Cms.Core.PublishedCache; + +namespace Umbraco.Cms.Api.Management.Controllers.PublishedCache; + +[ApiVersion("1.0")] +public class RebuildPublishedCacheStatusController : PublishedCacheControllerBase +{ + private readonly IDatabaseCacheRebuilder _databaseCacheRebuilder; + + public RebuildPublishedCacheStatusController(IDatabaseCacheRebuilder databaseCacheRebuilder) => _databaseCacheRebuilder = databaseCacheRebuilder; + + [HttpGet("rebuild/status")] + [MapToApiVersion("1.0")] + [ProducesResponseType(typeof(RebuildStatusModel), StatusCodes.Status200OK)] + public Task<IActionResult> Status(CancellationToken cancellationToken) + { + var isRebuilding = _databaseCacheRebuilder.IsRebuilding(); + return Task.FromResult((IActionResult)Ok(new RebuildStatusModel + { + IsRebuilding = isRebuilding + })); + } +} diff --git a/src/Umbraco.Cms.Api.Management/Controllers/PublishedCache/ReloadPublishedCacheController.cs b/src/Umbraco.Cms.Api.Management/Controllers/PublishedCache/ReloadPublishedCacheController.cs index da655627f5bb..e147c9f7752a 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/PublishedCache/ReloadPublishedCacheController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/PublishedCache/ReloadPublishedCacheController.cs @@ -1,4 +1,4 @@ -using Asp.Versioning; +using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Umbraco.Cms.Core.Cache; @@ -16,10 +16,9 @@ public class ReloadPublishedCacheController : PublishedCacheControllerBase [HttpPost("reload")] [MapToApiVersion("1.0")] [ProducesResponseType(StatusCodes.Status200OK)] - public async Task<IActionResult> Reload(CancellationToken cancellationToken) + public Task<IActionResult> Reload(CancellationToken cancellationToken) { _distributedCache.RefreshAllPublishedSnapshot(); - return await Task.FromResult(Ok()); + return Task.FromResult<IActionResult>(Ok()); } } - diff --git a/src/Umbraco.Cms.Api.Management/Controllers/PublishedCache/StatusPublishedCacheController.cs b/src/Umbraco.Cms.Api.Management/Controllers/PublishedCache/StatusPublishedCacheController.cs index 3a9d72c12cb8..e4c287f762a0 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/PublishedCache/StatusPublishedCacheController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/PublishedCache/StatusPublishedCacheController.cs @@ -1,4 +1,4 @@ -using Asp.Versioning; +using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; @@ -11,8 +11,6 @@ public class StatusPublishedCacheController : PublishedCacheControllerBase [HttpGet("status")] [MapToApiVersion("1.0")] [ProducesResponseType(StatusCodes.Status501NotImplemented)] - public async Task<ActionResult<string>> Status(CancellationToken cancellationToken) - { - return StatusCode(StatusCodes.Status501NotImplemented); - } + public Task<ActionResult<string>> Status(CancellationToken cancellationToken) + => Task.FromResult<ActionResult<string>>(StatusCode(StatusCodes.Status501NotImplemented)); } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/RecycleBin/RecycleBinControllerBase.cs b/src/Umbraco.Cms.Api.Management/Controllers/RecycleBin/RecycleBinControllerBase.cs index 69ae9b0b9d4b..850d7240d02d 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/RecycleBin/RecycleBinControllerBase.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/RecycleBin/RecycleBinControllerBase.cs @@ -23,17 +23,18 @@ protected RecycleBinControllerBase(IEntityService entityService) protected abstract Guid RecycleBinRootKey { get; } - protected async Task<ActionResult<PagedViewModel<TItem>>> GetRoot(int skip, int take) + protected Task<ActionResult<PagedViewModel<TItem>>> GetRoot(int skip, int take) { IEntitySlim[] rootEntities = GetPagedRootEntities(skip, take, out var totalItems); TItem[] treeItemViewModels = MapRecycleBinViewModels(null, rootEntities); PagedViewModel<TItem> result = PagedViewModel(treeItemViewModels, totalItems); - return await Task.FromResult(Ok(result)); + + return Task.FromResult<ActionResult<PagedViewModel<TItem>>>(Ok(result)); } - protected async Task<ActionResult<PagedViewModel<TItem>>> GetChildren(Guid parentKey, int skip, int take) + protected Task<ActionResult<PagedViewModel<TItem>>> GetChildren(Guid parentKey, int skip, int take) { IEntitySlim[] children = GetPagedChildEntities(parentKey, skip, take, out var totalItems); @@ -41,7 +42,7 @@ protected async Task<ActionResult<PagedViewModel<TItem>>> GetChildren(Guid paren PagedViewModel<TItem> result = PagedViewModel(treeItemViewModels, totalItems); - return await Task.FromResult(Ok(result)); + return Task.FromResult<ActionResult<PagedViewModel<TItem>>>(Ok(result)); } protected virtual TItem MapRecycleBinViewModel(Guid? parentKey, IEntitySlim entity) diff --git a/src/Umbraco.Cms.Api.Management/Controllers/RedirectUrlManagement/DeleteByKeyRedirectUrlManagementController.cs b/src/Umbraco.Cms.Api.Management/Controllers/RedirectUrlManagement/DeleteByKeyRedirectUrlManagementController.cs index 3a0e60b61d51..6f33b166996b 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/RedirectUrlManagement/DeleteByKeyRedirectUrlManagementController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/RedirectUrlManagement/DeleteByKeyRedirectUrlManagementController.cs @@ -18,9 +18,9 @@ public DeleteByKeyRedirectUrlManagementController(IRedirectUrlService redirectUr [MapToApiVersion("1.0")] [HttpDelete("{id:guid}")] [ProducesResponseType(StatusCodes.Status200OK)] - public async Task<IActionResult> DeleteByKey(CancellationToken cancellationToken, Guid id) + public Task<IActionResult> DeleteByKey(CancellationToken cancellationToken, Guid id) { _redirectUrlService.Delete(id); - return Ok(); + return Task.FromResult<IActionResult>(Ok()); } } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/RedirectUrlManagement/GetAllRedirectUrlManagementController.cs b/src/Umbraco.Cms.Api.Management/Controllers/RedirectUrlManagement/GetAllRedirectUrlManagementController.cs index f4600ef653eb..bd3449679ee9 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/RedirectUrlManagement/GetAllRedirectUrlManagementController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/RedirectUrlManagement/GetAllRedirectUrlManagementController.cs @@ -27,7 +27,7 @@ public GetAllRedirectUrlManagementController( [HttpGet] [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)] [ProducesResponseType(typeof(PagedViewModel<RedirectUrlResponseModel>), StatusCodes.Status200OK)] - public async Task<ActionResult<PagedViewModel<RedirectUrlResponseModel>>> GetAll( + public Task<ActionResult<PagedViewModel<RedirectUrlResponseModel>>> GetAll( CancellationToken cancellationToken, string? filter, int skip = 0, @@ -39,6 +39,6 @@ public async Task<ActionResult<PagedViewModel<RedirectUrlResponseModel>>> GetAll : _redirectUrlService.SearchRedirectUrls(filter, skip, take, out total); IEnumerable<RedirectUrlResponseModel> redirectViewModels = _redirectUrlPresentationFactory.CreateMany(redirects); - return new PagedViewModel<RedirectUrlResponseModel> { Items = redirectViewModels, Total = total }; + return Task.FromResult<ActionResult<PagedViewModel<RedirectUrlResponseModel>>>(new PagedViewModel<RedirectUrlResponseModel> { Items = redirectViewModels, Total = total }); } } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Relation/ByRelationTypeKeyRelationController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Relation/ByRelationTypeKeyRelationController.cs index cde52b5884bb..189019cc85dd 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Relation/ByRelationTypeKeyRelationController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Relation/ByRelationTypeKeyRelationController.cs @@ -43,16 +43,15 @@ public async Task<IActionResult> ByRelationTypeKey( if (relationsAttempt.Success is false) { - return await Task.FromResult(RelationOperationStatusResult(relationsAttempt.Status)); + return RelationOperationStatusResult(relationsAttempt.Status); } IEnumerable<RelationResponseModel> mappedRelations = relationsAttempt.Result.Items.Select(_relationPresentationFactory.Create); - return await Task.FromResult(Ok(new PagedViewModel<RelationResponseModel> + return Ok(new PagedViewModel<RelationResponseModel> { Total = relationsAttempt.Result.Total, Items = mappedRelations, - })); - + }); } } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/RelationType/ByKeyRelationTypeController.cs b/src/Umbraco.Cms.Api.Management/Controllers/RelationType/ByKeyRelationTypeController.cs index a0d6ae4a46e8..691c6ae0ef9b 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/RelationType/ByKeyRelationTypeController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/RelationType/ByKeyRelationTypeController.cs @@ -1,4 +1,4 @@ -using Asp.Versioning; +using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Umbraco.Cms.Api.Management.ViewModels.RelationType; @@ -24,16 +24,16 @@ public ByKeyRelationTypeController(IRelationService relationService, IUmbracoMap [MapToApiVersion("1.0")] [ProducesResponseType(typeof(RelationTypeResponseModel), StatusCodes.Status200OK)] [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)] - public async Task<IActionResult> ByKey(CancellationToken cancellationToken, Guid id) + public Task<IActionResult> ByKey(CancellationToken cancellationToken, Guid id) { IRelationType? relationType = _relationService.GetRelationTypeById(id); if (relationType is null) { - return RelationTypeNotFound(); + return Task.FromResult(RelationTypeNotFound()); } RelationTypeResponseModel mappedRelationType = _mapper.Map<RelationTypeResponseModel>(relationType)!; - return await Task.FromResult(Ok(mappedRelationType)); + return Task.FromResult<IActionResult>(Ok(mappedRelationType)); } } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/RelationType/Item/ItemRelationTypeItemController.cs b/src/Umbraco.Cms.Api.Management/Controllers/RelationType/Item/ItemRelationTypeItemController.cs index 6745108b93e7..6ba42c15795f 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/RelationType/Item/ItemRelationTypeItemController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/RelationType/Item/ItemRelationTypeItemController.cs @@ -1,4 +1,4 @@ -using Asp.Versioning; +using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Umbraco.Cms.Api.Management.ViewModels.RelationType.Item; @@ -23,13 +23,13 @@ public ItemRelationTypeItemController(IRelationService relationService, IUmbraco [HttpGet] [MapToApiVersion("1.0")] [ProducesResponseType(typeof(IEnumerable<RelationTypeItemResponseModel>), StatusCodes.Status200OK)] - public async Task<IActionResult> Item( + public Task<IActionResult> Item( CancellationToken cancellationToken, [FromQuery(Name = "id")] HashSet<Guid> ids) { if (ids.Count is 0) { - return Ok(Enumerable.Empty<RelationTypeItemResponseModel>()); + return Task.FromResult<IActionResult>(Ok(Enumerable.Empty<RelationTypeItemResponseModel>())); } // relation service does not allow fetching a collection of relation types by their ids; instead it relies @@ -40,6 +40,6 @@ public async Task<IActionResult> Item( List<RelationTypeItemResponseModel> responseModels = _mapper.MapEnumerable<IRelationType, RelationTypeItemResponseModel>(relationTypes); - return await Task.FromResult(Ok(responseModels)); + return Task.FromResult<IActionResult>(Ok(responseModels)); } } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Script/Item/ItemScriptItemController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Script/Item/ItemScriptItemController.cs index 5786c7aaf6b1..e18eda31a7d9 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Script/Item/ItemScriptItemController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Script/Item/ItemScriptItemController.cs @@ -1,4 +1,4 @@ -using Asp.Versioning; +using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Umbraco.Cms.Api.Management.Extensions; @@ -18,17 +18,17 @@ public ItemScriptItemController(IFileItemPresentationFactory fileItemPresentatio [HttpGet] [MapToApiVersion("1.0")] [ProducesResponseType(typeof(IEnumerable<ScriptItemResponseModel>), StatusCodes.Status200OK)] - public async Task<IActionResult> Item( + public Task<IActionResult> Item( CancellationToken cancellationToken, [FromQuery(Name = "path")] HashSet<string> paths) { if (paths.Count is 0) { - return Ok(Enumerable.Empty<ScriptItemResponseModel>()); + return Task.FromResult<IActionResult>(Ok(Enumerable.Empty<ScriptItemResponseModel>())); } paths = paths.Select(path => path.VirtualPathToSystemPath()).ToHashSet(); IEnumerable<ScriptItemResponseModel> responseModels = _fileItemPresentationFactory.CreateScriptItemResponseModels(paths); - return await Task.FromResult(Ok(responseModels)); + return Task.FromResult<IActionResult>(Ok(responseModels)); } } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Searcher/AllSearcherController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Searcher/AllSearcherController.cs index 76cd2f639b92..60ee9f22b7e5 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Searcher/AllSearcherController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Searcher/AllSearcherController.cs @@ -1,4 +1,4 @@ -using Asp.Versioning; +using Asp.Versioning; using Examine; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; @@ -22,7 +22,7 @@ public class AllSearcherController : SearcherControllerBase [HttpGet] [MapToApiVersion("1.0")] [ProducesResponseType(typeof(PagedViewModel<SearcherResponse>), StatusCodes.Status200OK)] - public async Task<ActionResult<PagedViewModel<SearcherResponse>>> All( + public Task<ActionResult<PagedViewModel<SearcherResponse>>> All( CancellationToken cancellationToken, int skip = 0, int take = 100) @@ -37,6 +37,6 @@ public async Task<ActionResult<PagedViewModel<SearcherResponse>>> All( Total = searchers.Count, }; - return await Task.FromResult(Ok(viewModel)); + return Task.FromResult<ActionResult<PagedViewModel<SearcherResponse>>>(Ok(viewModel)); } } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Searcher/QuerySearcherController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Searcher/QuerySearcherController.cs index f3f7d0acaddd..2017d4b3761d 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Searcher/QuerySearcherController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Searcher/QuerySearcherController.cs @@ -1,4 +1,4 @@ -using Asp.Versioning; +using Asp.Versioning; using Examine; using Examine.Lucene.Search; using Examine.Search; @@ -23,7 +23,7 @@ public class QuerySearcherController : SearcherControllerBase [MapToApiVersion("1.0")] [ProducesResponseType(typeof(PagedViewModel<SearchResultResponseModel>), StatusCodes.Status200OK)] [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)] - public async Task<ActionResult<PagedViewModel<SearchResultResponseModel>>> Query( + public Task<ActionResult<PagedViewModel<SearchResultResponseModel>>> Query( CancellationToken cancellationToken, string searcherName, string? term, @@ -34,7 +34,7 @@ public async Task<ActionResult<PagedViewModel<SearchResultResponseModel>>> Query if (term.IsNullOrWhiteSpace()) { - return new PagedViewModel<SearchResultResponseModel>(); + return Task.FromResult<ActionResult<PagedViewModel<SearchResultResponseModel>>>(new PagedViewModel<SearchResultResponseModel>()); } if (!_examineManagerService.TryFindSearcher(searcherName, out ISearcher searcher)) @@ -47,7 +47,7 @@ public async Task<ActionResult<PagedViewModel<SearchResultResponseModel>>> Query Type = "Error", }; - return NotFound(invalidModelProblem); + return Task.FromResult<ActionResult<PagedViewModel<SearchResultResponseModel>>>(NotFound(invalidModelProblem)); } ISearchResults results; @@ -71,10 +71,10 @@ public async Task<ActionResult<PagedViewModel<SearchResultResponseModel>>> Query Type = "Error", }; - return BadRequest(invalidModelProblem); + return Task.FromResult<ActionResult<PagedViewModel<SearchResultResponseModel>>>(BadRequest(invalidModelProblem)); } - return await Task.FromResult(new PagedViewModel<SearchResultResponseModel> + return Task.FromResult<ActionResult<PagedViewModel<SearchResultResponseModel>>>(new PagedViewModel<SearchResultResponseModel> { Total = results.TotalItemCount, Items = results.Select(x => new SearchResultResponseModel diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Security/BackOfficeController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Security/BackOfficeController.cs index cada1031e34e..de2ae97a2df8 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Security/BackOfficeController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Security/BackOfficeController.cs @@ -492,7 +492,7 @@ private async Task<IActionResult> AuthorizeExternal(OpenIddictRequest request) return new ChallengeResult(provider, properties); } - private async Task<IActionResult> SignInBackOfficeUser(ClaimsPrincipal backOfficePrincipal, OpenIddictRequest request) + private Task<IActionResult> SignInBackOfficeUser(ClaimsPrincipal backOfficePrincipal, OpenIddictRequest request) { Claim[] backOfficeClaims = backOfficePrincipal.Claims.ToArray(); foreach (Claim backOfficeClaim in backOfficeClaims) @@ -506,7 +506,7 @@ private async Task<IActionResult> SignInBackOfficeUser(ClaimsPrincipal backOffic backOfficePrincipal.SetScopes(OpenIddictConstants.Scopes.OfflineAccess); } - return new SignInResult(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme, backOfficePrincipal); + return Task.FromResult<IActionResult>(new SignInResult(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme, backOfficePrincipal)); } private async Task<IActionResult> SignInBackOfficeUser(BackOfficeIdentityUser backOfficeUser, OpenIddictRequest request) diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Security/ConfigurationSecurityController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Security/ConfigurationSecurityController.cs index e65ea9752c25..c96f4a2bcff7 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Security/ConfigurationSecurityController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Security/ConfigurationSecurityController.cs @@ -1,4 +1,4 @@ -using Asp.Versioning; +using Asp.Versioning; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; @@ -20,13 +20,13 @@ public ConfigurationSecurityController(IPasswordConfigurationPresentationFactory [HttpGet("configuration")] [MapToApiVersion("1.0")] [ProducesResponseType(typeof(SecurityConfigurationResponseModel), StatusCodes.Status200OK)] - public async Task<IActionResult> Configuration(CancellationToken cancellationToken) + public Task<IActionResult> Configuration(CancellationToken cancellationToken) { var viewModel = new SecurityConfigurationResponseModel { PasswordConfiguration = _passwordConfigurationPresentationFactory.CreatePasswordConfigurationResponseModel(), }; - return await Task.FromResult(Ok(viewModel)); + return Task.FromResult<IActionResult>(Ok(viewModel)); } } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Server/StatusServerController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Server/StatusServerController.cs index 0e25c9d3c65c..4a9afe24ec71 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Server/StatusServerController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Server/StatusServerController.cs @@ -1,4 +1,4 @@ -using Asp.Versioning; +using Asp.Versioning; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; @@ -19,6 +19,6 @@ public class StatusServerController : ServerControllerBase [MapToApiVersion("1.0")] [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)] [ProducesResponseType(typeof(ServerStatusResponseModel), StatusCodes.Status200OK)] - public async Task<ActionResult<ServerStatusResponseModel>> Get(CancellationToken cancellationToken) => - await Task.FromResult(new ServerStatusResponseModel { ServerStatus = _runtimeState.Level }); + public Task<ActionResult<ServerStatusResponseModel>> Get(CancellationToken cancellationToken) + => Task.FromResult<ActionResult<ServerStatusResponseModel>>(new ServerStatusResponseModel { ServerStatus = _runtimeState.Level }); } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/StaticFile/Item/ItemStaticFileItemController.cs b/src/Umbraco.Cms.Api.Management/Controllers/StaticFile/Item/ItemStaticFileItemController.cs index 3301ecf8e1e0..600499c8a1b0 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/StaticFile/Item/ItemStaticFileItemController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/StaticFile/Item/ItemStaticFileItemController.cs @@ -1,4 +1,4 @@ -using Asp.Versioning; +using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Umbraco.Cms.Api.Management.Extensions; @@ -18,17 +18,17 @@ public ItemStaticFileItemController(IFileItemPresentationFactory fileItemPresent [HttpGet] [MapToApiVersion("1.0")] [ProducesResponseType(typeof(IEnumerable<StaticFileItemResponseModel>), StatusCodes.Status200OK)] - public async Task<IActionResult> Item( + public Task<IActionResult> Item( CancellationToken cancellationToken, [FromQuery(Name = "path")] HashSet<string> paths) { if (paths.Count is 0) { - return Ok(Enumerable.Empty<StaticFileItemResponseModel>()); + return Task.FromResult<IActionResult>(Ok(Enumerable.Empty<StaticFileItemResponseModel>())); } paths = paths.Select(path => path.VirtualPathToSystemPath()).ToHashSet(); IEnumerable<StaticFileItemResponseModel> responseModels = _fileItemPresentationFactory.CreateStaticFileItemResponseModels(paths); - return await Task.FromResult(Ok(responseModels)); + return Task.FromResult<IActionResult>(Ok(responseModels)); } } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Stylesheet/Item/ItemStylesheetItemController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Stylesheet/Item/ItemStylesheetItemController.cs index d9ab9b4aa93a..f5e6d71adb39 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Stylesheet/Item/ItemStylesheetItemController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Stylesheet/Item/ItemStylesheetItemController.cs @@ -1,4 +1,4 @@ -using Asp.Versioning; +using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Umbraco.Cms.Api.Management.Extensions; @@ -18,17 +18,17 @@ public ItemStylesheetItemController(IFileItemPresentationFactory fileItemPresent [HttpGet] [MapToApiVersion("1.0")] [ProducesResponseType(typeof(IEnumerable<StylesheetItemResponseModel>), StatusCodes.Status200OK)] - public async Task<IActionResult> Item( + public Task<IActionResult> Item( CancellationToken cancellationToken, [FromQuery(Name = "path")] HashSet<string> paths) { if (paths.Count is 0) { - return Ok(Enumerable.Empty<StylesheetItemResponseModel>()); + return Task.FromResult<IActionResult>(Ok(Enumerable.Empty<StylesheetItemResponseModel>())); } paths = paths.Select(path => path.VirtualPathToSystemPath()).ToHashSet(); IEnumerable<StylesheetItemResponseModel> responseModels = _fileItemPresentationFactory.CreateStylesheetItemResponseModels(paths); - return await Task.FromResult(Ok(responseModels)); + return Task.FromResult<IActionResult>(Ok(responseModels)); } } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Tag/ByQueryTagController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Tag/ByQueryTagController.cs index d528eabbc43f..368d90859836 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Tag/ByQueryTagController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Tag/ByQueryTagController.cs @@ -1,4 +1,4 @@ -using Asp.Versioning; +using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Umbraco.Cms.Api.Common.ViewModels.Pagination; @@ -42,6 +42,6 @@ public async Task<ActionResult<PagedViewModel<TagResponseModel>>> ByQuery( Total = responseModels.Count, }; - return await Task.FromResult(Ok(pagedViewModel)); + return Ok(pagedViewModel); } } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Telemetry/AllTelemetryController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Telemetry/AllTelemetryController.cs index 625b8b146b79..df18521fbc6b 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Telemetry/AllTelemetryController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Telemetry/AllTelemetryController.cs @@ -13,13 +13,13 @@ public class AllTelemetryController : TelemetryControllerBase [HttpGet] [MapToApiVersion("1.0")] [ProducesResponseType(typeof(PagedViewModel<TelemetryResponseModel>), StatusCodes.Status200OK)] - public async Task<PagedViewModel<TelemetryResponseModel>> GetAll( + public Task<PagedViewModel<TelemetryResponseModel>> GetAll( CancellationToken cancellationToken, int skip = 0, int take = 100) { TelemetryLevel[] levels = Enum.GetValues<TelemetryLevel>(); - return await Task.FromResult(new PagedViewModel<TelemetryResponseModel> + return Task.FromResult(new PagedViewModel<TelemetryResponseModel> { Total = levels.Length, Items = levels.Skip(skip).Take(take).Select(level => new TelemetryResponseModel { TelemetryLevel = level }), diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Telemetry/GetTelemetryController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Telemetry/GetTelemetryController.cs index d3bfe107a392..c442e6727b8f 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Telemetry/GetTelemetryController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Telemetry/GetTelemetryController.cs @@ -1,4 +1,4 @@ -using Asp.Versioning; +using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Umbraco.Cms.Core.Services; @@ -16,6 +16,6 @@ public class GetTelemetryController : TelemetryControllerBase [HttpGet("level")] [MapToApiVersion("1.0")] [ProducesResponseType(typeof(TelemetryResponseModel), StatusCodes.Status200OK)] - public async Task<TelemetryRepresentationBase> Get(CancellationToken cancellationToken) - => await Task.FromResult(new TelemetryResponseModel { TelemetryLevel = _metricsConsentService.GetConsentLevel() }); + public Task<TelemetryRepresentationBase> Get(CancellationToken cancellationToken) + => Task.FromResult<TelemetryRepresentationBase>(new TelemetryResponseModel { TelemetryLevel = _metricsConsentService.GetConsentLevel() }); } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Telemetry/SetTelemetryController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Telemetry/SetTelemetryController.cs index b0f8110e4095..f48abbc11d37 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Telemetry/SetTelemetryController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Telemetry/SetTelemetryController.cs @@ -1,4 +1,4 @@ -using Asp.Versioning; +using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Umbraco.Cms.Core.Services; @@ -34,6 +34,6 @@ public async Task<IActionResult> SetConsentLevel( } await _metricsConsentService.SetConsentLevelAsync(telemetryRepresentationBase.TelemetryLevel); - return await Task.FromResult(Ok()); + return Ok(); } } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Template/Query/ExecuteTemplateQueryController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Template/Query/ExecuteTemplateQueryController.cs index b491b45f59df..07063b69f7df 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Template/Query/ExecuteTemplateQueryController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Template/Query/ExecuteTemplateQueryController.cs @@ -1,4 +1,4 @@ -using System.Diagnostics; +using System.Diagnostics; using System.Linq.Expressions; using System.Text; using Asp.Versioning; @@ -28,6 +28,7 @@ public class ExecuteTemplateQueryController : TemplateQueryControllerBase private static readonly string _indent = $"{Environment.NewLine} "; + [ActivatorUtilitiesConstructor] public ExecuteTemplateQueryController( IPublishedContentQuery publishedContentQuery, IPublishedValueFallback publishedValueFallback, @@ -80,7 +81,7 @@ public ExecuteTemplateQueryController( [HttpPost("execute")] [MapToApiVersion("1.0")] [ProducesResponseType(typeof(TemplateQueryResultResponseModel), StatusCodes.Status200OK)] - public async Task<ActionResult<TemplateQueryResultResponseModel>> Execute( + public Task<ActionResult<TemplateQueryResultResponseModel>> Execute( CancellationToken cancellationToken, TemplateQueryExecuteModel query) { @@ -96,7 +97,7 @@ public async Task<ActionResult<TemplateQueryResultResponseModel>> Execute( .GetMany(results.Select(content => content.ContentType.Key).Distinct()) .ToDictionary(contentType => contentType.Key, contentType => contentType.Icon); - return await Task.FromResult(Ok(new TemplateQueryResultResponseModel + return Task.FromResult<ActionResult<TemplateQueryResultResponseModel>>(Ok(new TemplateQueryResultResponseModel { QueryExpression = queryExpression.ToString(), ResultCount = results.Count, diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Template/Query/SettingsTemplateQueryController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Template/Query/SettingsTemplateQueryController.cs index d5d5026c4f1f..868bd8baa37a 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Template/Query/SettingsTemplateQueryController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Template/Query/SettingsTemplateQueryController.cs @@ -1,4 +1,4 @@ -using Asp.Versioning; +using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Umbraco.Cms.Api.Management.ViewModels.Template.Query; @@ -17,7 +17,7 @@ public SettingsTemplateQueryController(IContentTypeService contentTypeService) [HttpGet("settings")] [MapToApiVersion("1.0")] [ProducesResponseType(typeof(TemplateQuerySettingsResponseModel), StatusCodes.Status200OK)] - public async Task<ActionResult<TemplateQuerySettingsResponseModel>> Settings(CancellationToken cancellationToken) + public Task<ActionResult<TemplateQuerySettingsResponseModel>> Settings(CancellationToken cancellationToken) { var contentTypeAliases = _contentTypeService .GetAll() @@ -29,7 +29,7 @@ public async Task<ActionResult<TemplateQuerySettingsResponseModel>> Settings(Can IEnumerable<TemplateQueryOperatorViewModel> operators = GetOperators(); - return await Task.FromResult(Ok(new TemplateQuerySettingsResponseModel + return Task.FromResult<ActionResult<TemplateQuerySettingsResponseModel>>(Ok(new TemplateQuerySettingsResponseModel { DocumentTypeAliases = contentTypeAliases, Properties = properties, diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Tree/EntityTreeControllerBase.cs b/src/Umbraco.Cms.Api.Management/Controllers/Tree/EntityTreeControllerBase.cs index 57f575fd8ab7..bf86b852ed06 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Tree/EntityTreeControllerBase.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Tree/EntityTreeControllerBase.cs @@ -1,4 +1,4 @@ -using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc; using Umbraco.Cms.Api.Common.ViewModels.Pagination; using Umbraco.Cms.Api.Management.ViewModels; using Umbraco.Cms.Api.Management.ViewModels.Tree; @@ -22,24 +22,26 @@ protected EntityTreeControllerBase(IEntityService entityService) protected virtual Ordering ItemOrdering => Ordering.By(nameof(Infrastructure.Persistence.Dtos.NodeDto.Text)); - protected async Task<ActionResult<PagedViewModel<TItem>>> GetRoot(int skip, int take) + protected Task<ActionResult<PagedViewModel<TItem>>> GetRoot(int skip, int take) { IEntitySlim[] rootEntities = GetPagedRootEntities(skip, take, out var totalItems); TItem[] treeItemViewModels = MapTreeItemViewModels(null, rootEntities); PagedViewModel<TItem> result = PagedViewModel(treeItemViewModels, totalItems); - return await Task.FromResult(Ok(result)); + + return Task.FromResult<ActionResult<PagedViewModel<TItem>>>(Ok(result)); } - protected async Task<ActionResult<PagedViewModel<TItem>>> GetChildren(Guid parentId, int skip, int take) + protected Task<ActionResult<PagedViewModel<TItem>>> GetChildren(Guid parentId, int skip, int take) { IEntitySlim[] children = GetPagedChildEntities(parentId, skip, take, out var totalItems); TItem[] treeItemViewModels = MapTreeItemViewModels(parentId, children); PagedViewModel<TItem> result = PagedViewModel(treeItemViewModels, totalItems); - return await Task.FromResult(Ok(result)); + + return Task.FromResult<ActionResult<PagedViewModel<TItem>>>(Ok(result)); } protected virtual async Task<ActionResult<IEnumerable<TItem>>> GetAncestors(Guid descendantKey, bool includeSelf = true) @@ -60,13 +62,13 @@ protected virtual async Task<ActionResult<IEnumerable<TItem>>> GetAncestors(Guid return Ok(result); } - protected virtual async Task<IEntitySlim[]> GetAncestorEntitiesAsync(Guid descendantKey, bool includeSelf) + protected virtual Task<IEntitySlim[]> GetAncestorEntitiesAsync(Guid descendantKey, bool includeSelf) { IEntitySlim? entity = EntityService.Get(descendantKey, ItemObjectType); if (entity is null) { // not much else we can do here but return nothing - return await Task.FromResult(Array.Empty<IEntitySlim>()); + return Task.FromResult(Array.Empty<IEntitySlim>()); } var ancestorIds = entity.AncestorIds(); @@ -76,7 +78,7 @@ protected virtual async Task<IEntitySlim[]> GetAncestorEntitiesAsync(Guid descen : Array.Empty<IEntitySlim>(); ancestors = ancestors.Union(includeSelf ? new[] { entity } : Array.Empty<IEntitySlim>()); - return ancestors.OrderBy(item => item.Level).ToArray(); + return Task.FromResult(ancestors.OrderBy(item => item.Level).ToArray()); } protected virtual IEntitySlim[] GetPagedRootEntities(int skip, int take, out long totalItems) diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Tree/FileSystemTreeControllerBase.cs b/src/Umbraco.Cms.Api.Management/Controllers/Tree/FileSystemTreeControllerBase.cs index 80b1edffeb58..1388e4b798d3 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Tree/FileSystemTreeControllerBase.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Tree/FileSystemTreeControllerBase.cs @@ -1,4 +1,4 @@ -using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc; using Umbraco.Cms.Api.Common.ViewModels.Pagination; using Umbraco.Cms.Api.Management.Extensions; using Umbraco.Cms.Api.Management.ViewModels.FileSystem; @@ -12,28 +12,28 @@ public abstract class FileSystemTreeControllerBase : ManagementApiControllerBase { protected abstract IFileSystem FileSystem { get; } - protected async Task<ActionResult<PagedViewModel<FileSystemTreeItemPresentationModel>>> GetRoot(int skip, int take) + protected Task<ActionResult<PagedViewModel<FileSystemTreeItemPresentationModel>>> GetRoot(int skip, int take) { FileSystemTreeItemPresentationModel[] viewModels = GetPathViewModels(string.Empty, skip, take, out var totalItems); PagedViewModel<FileSystemTreeItemPresentationModel> result = PagedViewModel(viewModels, totalItems); - return await Task.FromResult(Ok(result)); + return Task.FromResult<ActionResult<PagedViewModel<FileSystemTreeItemPresentationModel>>>(Ok(result)); } - protected async Task<ActionResult<PagedViewModel<FileSystemTreeItemPresentationModel>>> GetChildren(string path, int skip, int take) + protected Task<ActionResult<PagedViewModel<FileSystemTreeItemPresentationModel>>> GetChildren(string path, int skip, int take) { FileSystemTreeItemPresentationModel[] viewModels = GetPathViewModels(path, skip, take, out var totalItems); PagedViewModel<FileSystemTreeItemPresentationModel> result = PagedViewModel(viewModels, totalItems); - return await Task.FromResult(Ok(result)); + return Task.FromResult<ActionResult<PagedViewModel<FileSystemTreeItemPresentationModel>>>(Ok(result)); } - protected virtual async Task<ActionResult<IEnumerable<FileSystemTreeItemPresentationModel>>> GetAncestors(string path, bool includeSelf = true) + protected virtual Task<ActionResult<IEnumerable<FileSystemTreeItemPresentationModel>>> GetAncestors(string path, bool includeSelf = true) { path = path.VirtualPathToSystemPath(); FileSystemTreeItemPresentationModel[] models = GetAncestorModels(path, includeSelf); - return await Task.FromResult(Ok(models)); + return Task.FromResult<ActionResult<IEnumerable<FileSystemTreeItemPresentationModel>>>(Ok(models)); } protected virtual FileSystemTreeItemPresentationModel[] GetAncestorModels(string path, bool includeSelf) diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Tree/FolderTreeControllerBase.cs b/src/Umbraco.Cms.Api.Management/Controllers/Tree/FolderTreeControllerBase.cs index 2a33d95aa313..a7c201372c6d 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Tree/FolderTreeControllerBase.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Tree/FolderTreeControllerBase.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Api.Management.ViewModels.Tree; +using Umbraco.Cms.Api.Management.ViewModels.Tree; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Entities; @@ -63,14 +63,14 @@ protected override TItem MapTreeItemViewModel(Guid? parentKey, IEntitySlim entit return viewModel; } - protected override async Task<IEntitySlim[]> GetAncestorEntitiesAsync(Guid descendantKey, bool includeSelf = true) + protected override Task<IEntitySlim[]> GetAncestorEntitiesAsync(Guid descendantKey, bool includeSelf = true) { IEntitySlim? entity = EntityService.Get(descendantKey, ItemObjectType) ?? EntityService.Get(descendantKey, FolderObjectType); if (entity is null) { // not much else we can do here but return nothing - return await Task.FromResult(Array.Empty<IEntitySlim>()); + return Task.FromResult(Array.Empty<IEntitySlim>()); } var ancestorIds = entity.AncestorIds(); @@ -81,9 +81,12 @@ protected override async Task<IEntitySlim[]> GetAncestorEntitiesAsync(Guid desce .GetAll(ItemObjectType, ancestorIds) .Union(containers) : Array.Empty<IEntitySlim>(); - ancestors = ancestors.Union(includeSelf ? new[] { entity } : Array.Empty<IEntitySlim>()); + if (includeSelf) + { + ancestors = ancestors.Append(entity); + } - return ancestors.OrderBy(item => item.Level).ToArray(); + return Task.FromResult(ancestors.OrderBy(item => item.Level).ToArray()); } private IEntitySlim[] GetEntities(Guid? parentKey, int skip, int take, out long totalItems) @@ -94,7 +97,7 @@ private IEntitySlim[] GetEntities(Guid? parentKey, int skip, int take, out long IEntitySlim[] itemEntities = EntityService.GetPagedChildren( parentKey, - new [] { FolderObjectType, ItemObjectType }, + [FolderObjectType, ItemObjectType], childObjectTypes, skip, take, diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Upgrade/SettingsUpgradeController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Upgrade/SettingsUpgradeController.cs index 66e212650f04..2dcbd8a01412 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Upgrade/SettingsUpgradeController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Upgrade/SettingsUpgradeController.cs @@ -1,4 +1,4 @@ -using Asp.Versioning; +using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Umbraco.Cms.Core.Mapping; @@ -26,7 +26,7 @@ public SettingsUpgradeController( [MapToApiVersion("1.0")] [ProducesResponseType(typeof(UpgradeSettingsResponseModel), StatusCodes.Status200OK)] [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status428PreconditionRequired)] - public async Task<ActionResult<UpgradeSettingsResponseModel>> Settings(CancellationToken cancellationToken) + public Task<ActionResult<UpgradeSettingsResponseModel>> Settings(CancellationToken cancellationToken) { // TODO: Async - We need to figure out what we want to do with async endpoints that doesn't do anything async // We want these to be async for future use (Ideally we'll have more async things), @@ -34,6 +34,6 @@ public async Task<ActionResult<UpgradeSettingsResponseModel>> Settings(Cancellat UpgradeSettingsModel upgradeSettings = _upgradeSettingsFactory.GetUpgradeSettings(); UpgradeSettingsResponseModel responseModel = _mapper.Map<UpgradeSettingsResponseModel>(upgradeSettings)!; - return await Task.FromResult(responseModel); + return Task.FromResult<ActionResult<UpgradeSettingsResponseModel>>(responseModel); } } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/UrlSegment/ResizeImagingController.cs b/src/Umbraco.Cms.Api.Management/Controllers/UrlSegment/ResizeImagingController.cs index 2c2dba9a19dc..ea13c080e280 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/UrlSegment/ResizeImagingController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/UrlSegment/ResizeImagingController.cs @@ -1,4 +1,4 @@ -using Asp.Versioning; +using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Umbraco.Cms.Api.Management.Factories; @@ -23,10 +23,10 @@ public ResizeImagingController(IMediaService mediaService, IReziseImageUrlFactor [MapToApiVersion("1.0")] [HttpGet("resize/urls")] [ProducesResponseType(typeof(IEnumerable<MediaUrlInfoResponseModel>), StatusCodes.Status200OK)] - public async Task<IActionResult> Urls([FromQuery(Name = "id")] HashSet<Guid> ids, int height = 200, int width = 200, ImageCropMode? mode = null) + public Task<IActionResult> Urls([FromQuery(Name = "id")] HashSet<Guid> ids, int height = 200, int width = 200, ImageCropMode? mode = null) { IEnumerable<IMedia> items = _mediaService.GetByIds(ids); - return await Task.FromResult(Ok(_reziseImageUrlFactory.CreateUrlSets(items, height, width, mode))); + return Task.FromResult<IActionResult>(Ok(_reziseImageUrlFactory.CreateUrlSets(items, height, width, mode))); } } diff --git a/src/Umbraco.Cms.Api.Management/DependencyInjection/ApplicationBuilderExtensions.cs b/src/Umbraco.Cms.Api.Management/DependencyInjection/ApplicationBuilderExtensions.cs index 870c4d3e1ede..7a3394ad5df3 100644 --- a/src/Umbraco.Cms.Api.Management/DependencyInjection/ApplicationBuilderExtensions.cs +++ b/src/Umbraco.Cms.Api.Management/DependencyInjection/ApplicationBuilderExtensions.cs @@ -1,4 +1,4 @@ -using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Diagnostics; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; @@ -6,9 +6,8 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Options; using Umbraco.Cms.Core; -using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Hosting; using Umbraco.Extensions; using IHostingEnvironment = Umbraco.Cms.Core.Hosting.IHostingEnvironment; @@ -20,14 +19,10 @@ internal static IApplicationBuilder UseProblemDetailsExceptionHandling(this IApp => applicationBuilder.UseWhen( httpContext => { - GlobalSettings settings = httpContext.RequestServices - .GetRequiredService<IOptions<GlobalSettings>>().Value; - IHostingEnvironment hostingEnvironment = - httpContext.RequestServices.GetRequiredService<IHostingEnvironment>(); - var officePath = settings.GetBackOfficePath(hostingEnvironment); + var backOfficePath = httpContext.RequestServices.GetRequiredService<IHostingEnvironment>().GetBackOfficePath(); // Only use the API exception handler when we are requesting an API - return httpContext.Request.Path.Value?.StartsWith($"{officePath}{Constants.Web.ManagementApiPath}") ?? false; + return httpContext.Request.Path.Value?.StartsWith($"{backOfficePath}{Constants.Web.ManagementApiPath}") ?? false; }, innerBuilder => { @@ -58,14 +53,13 @@ internal static IApplicationBuilder UseEndpoints(this IApplicationBuilder applic applicationBuilder.UseEndpoints(endpoints => { - GlobalSettings settings = provider.GetRequiredService<IOptions<GlobalSettings>>().Value; - IHostingEnvironment hostingEnvironment = provider.GetRequiredService<IHostingEnvironment>(); - var officePath = settings.GetBackOfficePath(hostingEnvironment); + var backOfficePath = provider.GetRequiredService<IHostingEnvironment>().GetBackOfficePath(); + // Maps attribute routed controllers. endpoints.MapControllers(); // Serve contract - endpoints.MapGet($"{officePath}{Constants.Web.ManagementApiPath}openapi.json", async context => + endpoints.MapGet($"{backOfficePath}{Constants.Web.ManagementApiPath}openapi.json", async context => { await context.Response.SendFileAsync(new EmbeddedFileProvider(typeof(ManagementApiComposer).Assembly).GetFileInfo("OpenApi.json")); }); diff --git a/src/Umbraco.Cms.Api.Management/DependencyInjection/UmbracoBuilderExtensions.cs b/src/Umbraco.Cms.Api.Management/DependencyInjection/UmbracoBuilderExtensions.cs index 4ef42524c4da..48e53c97e0d3 100644 --- a/src/Umbraco.Cms.Api.Management/DependencyInjection/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.Cms.Api.Management/DependencyInjection/UmbracoBuilderExtensions.cs @@ -64,7 +64,6 @@ public static IUmbracoBuilder AddUmbracoManagementApi(this IUmbracoBuilder build .AddWebhooks() .AddServer() .AddCorsPolicy() - .AddWebhooks() .AddPreview() .AddServerEvents() .AddPasswordConfiguration() diff --git a/src/Umbraco.Cms.Api.Management/Factories/DocumentNotificationPresentationFactory.cs b/src/Umbraco.Cms.Api.Management/Factories/DocumentNotificationPresentationFactory.cs index 7daf762b781a..5a79c575a596 100644 --- a/src/Umbraco.Cms.Api.Management/Factories/DocumentNotificationPresentationFactory.cs +++ b/src/Umbraco.Cms.Api.Management/Factories/DocumentNotificationPresentationFactory.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Api.Management.ViewModels.Document; +using Umbraco.Cms.Api.Management.ViewModels.Document; using Umbraco.Cms.Core.Actions; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Security; @@ -19,15 +19,14 @@ public DocumentNotificationPresentationFactory(ActionCollection actionCollection _backOfficeSecurityAccessor = backOfficeSecurityAccessor; } - public async Task<IEnumerable<DocumentNotificationResponseModel>> CreateNotificationModelsAsync(IContent content) + public Task<IEnumerable<DocumentNotificationResponseModel>> CreateNotificationModelsAsync(IContent content) { var subscribedActionIds = _notificationService - .GetUserNotifications(_backOfficeSecurityAccessor.BackOfficeSecurity?.CurrentUser, content.Path)? - .Select(n => n.Action) - .ToArray() - ?? Array.Empty<string>(); + .GetUserNotifications(_backOfficeSecurityAccessor.BackOfficeSecurity?.CurrentUser, content.Path)? + .Select(n => n.Action) + .ToArray() ?? Array.Empty<string>(); - return await Task.FromResult(_actionCollection + return Task.FromResult<IEnumerable<DocumentNotificationResponseModel>>(_actionCollection .Where(action => action.ShowInNotifier) .Select(action => new DocumentNotificationResponseModel { diff --git a/src/Umbraco.Cms.Api.Management/Factories/DocumentVersionPresentationFactory.cs b/src/Umbraco.Cms.Api.Management/Factories/DocumentVersionPresentationFactory.cs index 2c3b80fd3c36..21d87f53f809 100644 --- a/src/Umbraco.Cms.Api.Management/Factories/DocumentVersionPresentationFactory.cs +++ b/src/Umbraco.Cms.Api.Management/Factories/DocumentVersionPresentationFactory.cs @@ -31,6 +31,14 @@ public async Task<DocumentVersionItemResponseModel> CreateAsync(ContentVersionMe contentVersion.CurrentDraftVersion, contentVersion.PreventCleanup); - public async Task<IEnumerable<DocumentVersionItemResponseModel>> CreateMultipleAsync(IEnumerable<ContentVersionMeta> contentVersions) => - await Task.WhenAll(contentVersions.Select(CreateAsync)); + public async Task<IEnumerable<DocumentVersionItemResponseModel>> CreateMultipleAsync(IEnumerable<ContentVersionMeta> contentVersions) + => await CreateMultipleImplAsync(contentVersions).ToArrayAsync(); + + private async IAsyncEnumerable<DocumentVersionItemResponseModel> CreateMultipleImplAsync(IEnumerable<ContentVersionMeta> contentVersions) + { + foreach (ContentVersionMeta contentVersion in contentVersions) + { + yield return await CreateAsync(contentVersion); + } + } } diff --git a/src/Umbraco.Cms.Api.Management/Factories/RelationTypePresentationFactory.cs b/src/Umbraco.Cms.Api.Management/Factories/RelationTypePresentationFactory.cs index 3ce445a12506..651abfd71afb 100644 --- a/src/Umbraco.Cms.Api.Management/Factories/RelationTypePresentationFactory.cs +++ b/src/Umbraco.Cms.Api.Management/Factories/RelationTypePresentationFactory.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Api.Management.ViewModels.TrackedReferences; +using Umbraco.Cms.Api.Management.ViewModels.TrackedReferences; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Mapping; using Umbraco.Cms.Core.Models; @@ -15,7 +15,7 @@ public RelationTypePresentationFactory(IUmbracoMapper umbracoMapper) _umbracoMapper = umbracoMapper; } - public async Task<IEnumerable<IReferenceResponseModel>> CreateReferenceResponseModelsAsync(IEnumerable<RelationItemModel> relationItemModels) + public Task<IEnumerable<IReferenceResponseModel>> CreateReferenceResponseModelsAsync(IEnumerable<RelationItemModel> relationItemModels) { IReferenceResponseModel[] result = relationItemModels.Select(relationItemModel => relationItemModel.NodeType switch { @@ -24,6 +24,6 @@ public async Task<IEnumerable<IReferenceResponseModel>> CreateReferenceResponseM _ => _umbracoMapper.Map<DefaultReferenceResponseModel>(relationItemModel) as IReferenceResponseModel, }).WhereNotNull().ToArray(); - return await Task.FromResult(result); + return Task.FromResult<IEnumerable<IReferenceResponseModel>>(result); } } diff --git a/src/Umbraco.Cms.Api.Management/Factories/TemporaryFileConfigurationPresentationFactory.cs b/src/Umbraco.Cms.Api.Management/Factories/TemporaryFileConfigurationPresentationFactory.cs index c0aa1d1c2802..2850304f1ccb 100644 --- a/src/Umbraco.Cms.Api.Management/Factories/TemporaryFileConfigurationPresentationFactory.cs +++ b/src/Umbraco.Cms.Api.Management/Factories/TemporaryFileConfigurationPresentationFactory.cs @@ -1,4 +1,4 @@ -using Microsoft.Extensions.Options; +using Microsoft.Extensions.Options; using Umbraco.Cms.Api.Management.ViewModels.TemporaryFile; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Media; @@ -22,8 +22,8 @@ public TemporaryFileConfigurationResponseModel Create() => new() { ImageFileTypes = _imageUrlGenerator.SupportedImageFileTypes.ToArray(), - DisallowedUploadedFilesExtensions = _contentSettings.DisallowedUploadedFileExtensions, - AllowedUploadedFileExtensions = _contentSettings.AllowedUploadedFileExtensions, + DisallowedUploadedFilesExtensions = _contentSettings.DisallowedUploadedFileExtensions.ToArray(), + AllowedUploadedFileExtensions = _contentSettings.AllowedUploadedFileExtensions.ToArray(), MaxFileSize = _runtimeSettings.MaxRequestLength, }; } diff --git a/src/Umbraco.Cms.Api.Management/Factories/UserPresentationFactory.cs b/src/Umbraco.Cms.Api.Management/Factories/UserPresentationFactory.cs index e1ee38d51a5e..baa67a3299cf 100644 --- a/src/Umbraco.Cms.Api.Management/Factories/UserPresentationFactory.cs +++ b/src/Umbraco.Cms.Api.Management/Factories/UserPresentationFactory.cs @@ -96,7 +96,7 @@ public UserItemResponseModel CreateItemResponseModel(IUser user) => Kind = user.Kind }; - public async Task<UserCreateModel> CreateCreationModelAsync(CreateUserRequestModel requestModel) + public Task<UserCreateModel> CreateCreationModelAsync(CreateUserRequestModel requestModel) { var createModel = new UserCreateModel { @@ -108,10 +108,10 @@ public async Task<UserCreateModel> CreateCreationModelAsync(CreateUserRequestMod Kind = requestModel.Kind }; - return await Task.FromResult(createModel); + return Task.FromResult(createModel); } - public async Task<UserInviteModel> CreateInviteModelAsync(InviteUserRequestModel requestModel) + public Task<UserInviteModel> CreateInviteModelAsync(InviteUserRequestModel requestModel) { var inviteModel = new UserInviteModel { @@ -122,10 +122,10 @@ public async Task<UserInviteModel> CreateInviteModelAsync(InviteUserRequestModel Message = requestModel.Message, }; - return await Task.FromResult(inviteModel); + return Task.FromResult(inviteModel); } - public async Task<UserResendInviteModel> CreateResendInviteModelAsync(ResendInviteUserRequestModel requestModel) + public Task<UserResendInviteModel> CreateResendInviteModelAsync(ResendInviteUserRequestModel requestModel) { var inviteModel = new UserResendInviteModel { @@ -133,10 +133,10 @@ public async Task<UserResendInviteModel> CreateResendInviteModelAsync(ResendInvi Message = requestModel.Message, }; - return await Task.FromResult(inviteModel); + return Task.FromResult(inviteModel); } - public async Task<CurrenUserConfigurationResponseModel> CreateCurrentUserConfigurationModelAsync() + public Task<CurrenUserConfigurationResponseModel> CreateCurrentUserConfigurationModelAsync() { var model = new CurrenUserConfigurationResponseModel { @@ -149,7 +149,7 @@ public async Task<CurrenUserConfigurationResponseModel> CreateCurrentUserConfigu AllowTwoFactor = _externalLoginProviders.HasDenyLocalLogin() is false, }; - return await Task.FromResult(model); + return Task.FromResult(model); } public Task<UserConfigurationResponseModel> CreateUserConfigurationModelAsync() => @@ -165,7 +165,7 @@ public Task<UserConfigurationResponseModel> CreateUserConfigurationModelAsync() AllowTwoFactor = _externalLoginProviders.HasDenyLocalLogin() is false, }); - public async Task<UserUpdateModel> CreateUpdateModelAsync(Guid existingUserKey, UpdateUserRequestModel updateModel) + public Task<UserUpdateModel> CreateUpdateModelAsync(Guid existingUserKey, UpdateUserRequestModel updateModel) { var model = new UserUpdateModel { @@ -182,7 +182,7 @@ public async Task<UserUpdateModel> CreateUpdateModelAsync(Guid existingUserKey, model.UserGroupKeys = updateModel.UserGroupIds.Select(x => x.Id).ToHashSet(); - return await Task.FromResult(model); + return Task.FromResult(model); } public async Task<CurrentUserResponseModel> CreateCurrentUserResponseModelAsync(IUser user) @@ -202,7 +202,7 @@ public async Task<CurrentUserResponseModel> CreateCurrentUserResponseModelAsync( var allowedSections = presentationGroups.SelectMany(x => x.Sections).ToHashSet(); - return await Task.FromResult(new CurrentUserResponseModel() + return new CurrentUserResponseModel() { Id = presentationUser.Id, Email = presentationUser.Email, @@ -222,17 +222,17 @@ public async Task<CurrentUserResponseModel> CreateCurrentUserResponseModelAsync( AllowedSections = allowedSections, IsAdmin = user.IsAdmin(), UserGroupIds = presentationUser.UserGroupIds, - }); + }; } - public async Task<CalculatedUserStartNodesResponseModel> CreateCalculatedUserStartNodesResponseModelAsync(IUser user) + public Task<CalculatedUserStartNodesResponseModel> CreateCalculatedUserStartNodesResponseModelAsync(IUser user) { var mediaStartNodeIds = user.CalculateMediaStartNodeIds(_entityService, _appCaches); ISet<ReferenceByIdModel> mediaStartNodeKeys = GetKeysFromIds(mediaStartNodeIds, UmbracoObjectTypes.Media); var contentStartNodeIds = user.CalculateContentStartNodeIds(_entityService, _appCaches); ISet<ReferenceByIdModel> documentStartNodeKeys = GetKeysFromIds(contentStartNodeIds, UmbracoObjectTypes.Document); - return await Task.FromResult(new CalculatedUserStartNodesResponseModel() + return Task.FromResult<CalculatedUserStartNodesResponseModel>(new CalculatedUserStartNodesResponseModel() { Id = user.Key, MediaStartNodeIds = mediaStartNodeKeys, diff --git a/src/Umbraco.Cms.Api.Management/OpenApi.json b/src/Umbraco.Cms.Api.Management/OpenApi.json index 9ff09a431ab1..e2f18da0cd99 100644 --- a/src/Umbraco.Cms.Api.Management/OpenApi.json +++ b/src/Umbraco.Cms.Api.Management/OpenApi.json @@ -182,7 +182,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -254,7 +254,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -351,7 +351,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -491,7 +491,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -622,7 +622,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -690,7 +690,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -794,7 +794,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -869,7 +869,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -904,7 +904,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -1038,7 +1038,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -1110,7 +1110,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -1207,7 +1207,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -1347,7 +1347,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -1436,7 +1436,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -1592,7 +1592,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -1663,7 +1663,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -1726,7 +1726,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -1788,7 +1788,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -1946,7 +1946,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -2018,7 +2018,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -2115,7 +2115,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -2255,7 +2255,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -2336,7 +2336,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -2466,7 +2466,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -2612,7 +2612,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -2721,7 +2721,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -2784,7 +2784,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -2839,7 +2839,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -2973,7 +2973,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -3045,7 +3045,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -3142,7 +3142,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -3282,7 +3282,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -3398,7 +3398,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -3544,7 +3544,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -3616,7 +3616,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -3713,7 +3713,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -3853,7 +3853,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -3973,7 +3973,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -4082,7 +4082,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -4153,7 +4153,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -4216,7 +4216,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -4350,7 +4350,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -4422,7 +4422,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -4493,7 +4493,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -4633,7 +4633,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -4671,6 +4671,14 @@ "format": "uuid" } }, + { + "name": "parentContentKey", + "in": "query", + "schema": { + "type": "string", + "format": "uuid" + } + }, { "name": "skip", "in": "query", @@ -4723,7 +4731,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -4801,7 +4809,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -4878,7 +4886,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -5023,7 +5031,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -5096,7 +5104,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -5226,7 +5234,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -5368,7 +5376,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -5435,7 +5443,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -5516,7 +5524,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -5563,7 +5571,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -5697,7 +5705,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -5769,7 +5777,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -5866,7 +5874,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -6006,7 +6014,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -6152,7 +6160,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -6320,7 +6328,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -6391,7 +6399,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -6454,7 +6462,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -6553,7 +6561,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -6627,7 +6635,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -6733,7 +6741,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -6851,7 +6859,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -6992,7 +7000,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -7126,7 +7134,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -7198,7 +7206,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -7295,7 +7303,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -7435,7 +7443,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -7526,7 +7534,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -7645,7 +7653,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -7717,7 +7725,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -7871,7 +7879,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -7987,7 +7995,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -8098,7 +8106,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -8173,7 +8181,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -8275,7 +8283,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -8406,7 +8414,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -8489,7 +8497,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -8559,7 +8567,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -8661,7 +8669,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -8803,7 +8811,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -8945,7 +8953,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -9017,7 +9025,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -9081,7 +9089,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -9145,7 +9153,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -9275,7 +9283,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -9417,7 +9425,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -9560,7 +9568,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -9639,7 +9647,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -9674,7 +9682,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -9793,7 +9801,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -9857,7 +9865,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -9976,7 +9984,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -10177,7 +10185,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -10288,7 +10296,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -10374,7 +10382,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -10504,7 +10512,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -10579,7 +10587,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -10634,7 +10642,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -10682,7 +10690,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -10753,7 +10761,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -10816,7 +10824,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -10894,7 +10902,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -10940,7 +10948,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -10995,7 +11003,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -11054,7 +11062,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -11137,7 +11145,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -11253,7 +11261,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -11430,7 +11438,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -12195,7 +12203,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -12359,7 +12367,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -12498,7 +12506,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -12565,7 +12573,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -12632,7 +12640,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -12727,7 +12735,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -12812,7 +12820,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -12867,7 +12875,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -12973,7 +12981,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -13044,7 +13052,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -13114,7 +13122,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -13182,7 +13190,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -13220,7 +13228,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -13258,7 +13266,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -13638,7 +13646,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -13710,7 +13718,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -13781,7 +13789,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -13921,7 +13929,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -13959,6 +13967,14 @@ "format": "uuid" } }, + { + "name": "parentContentKey", + "in": "query", + "schema": { + "type": "string", + "format": "uuid" + } + }, { "name": "skip", "in": "query", @@ -14011,7 +14027,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -14088,7 +14104,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -14233,7 +14249,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -14306,7 +14322,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -14436,7 +14452,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -14578,7 +14594,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -14645,7 +14661,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -14726,7 +14742,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -14773,7 +14789,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -14907,7 +14923,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -14979,7 +14995,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -15076,7 +15092,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -15216,7 +15232,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -15362,7 +15378,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -15422,7 +15438,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -15493,7 +15509,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -15556,7 +15572,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -15677,7 +15693,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -15938,7 +15954,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -16010,7 +16026,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -16107,7 +16123,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -16247,7 +16263,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -16338,7 +16354,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -16442,7 +16458,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -16553,7 +16569,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -16629,7 +16645,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -16693,7 +16709,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -16823,7 +16839,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -16902,7 +16918,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -16937,7 +16953,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -17056,7 +17072,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -17120,7 +17136,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -17239,7 +17255,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -17313,7 +17329,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -17424,7 +17440,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -17510,7 +17526,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -17640,7 +17656,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -17715,7 +17731,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -17770,7 +17786,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -17818,7 +17834,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -17889,7 +17905,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -17952,7 +17968,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -18056,7 +18072,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -18162,7 +18178,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -18223,7 +18239,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -18320,7 +18336,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -18460,7 +18476,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -18527,7 +18543,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -18769,7 +18785,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -18841,7 +18857,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -18912,7 +18928,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -19052,7 +19068,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -19141,7 +19157,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -19255,7 +19271,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -19348,7 +19364,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -19395,7 +19411,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -19450,7 +19466,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -19570,7 +19586,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -19823,7 +19839,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -19895,7 +19911,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -19992,7 +20008,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -20132,7 +20148,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -20274,7 +20290,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -20321,7 +20337,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -20440,7 +20456,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -20514,7 +20530,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -20561,7 +20577,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -20596,7 +20612,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -20709,7 +20725,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -20807,7 +20823,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -20854,7 +20870,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -20909,7 +20925,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -21041,7 +21057,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -21113,7 +21129,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -21184,7 +21200,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -21298,7 +21314,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -21371,7 +21387,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -21426,7 +21442,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -21608,7 +21624,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -21679,7 +21695,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -21775,7 +21791,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -21914,7 +21930,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -22070,7 +22086,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -22216,7 +22232,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -22287,7 +22303,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -22383,7 +22399,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -22450,7 +22466,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -22509,7 +22525,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -22556,7 +22572,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -22618,7 +22634,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -22673,7 +22689,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -22764,7 +22780,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -22829,7 +22845,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -22903,7 +22919,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -22980,6 +22996,38 @@ ] } }, + "/umbraco/management/api/v1/published-cache/rebuild/status": { + "get": { + "tags": [ + "Published Cache" + ], + "operationId": "GetPublishedCacheRebuildStatus", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/RebuildStatusModel" + } + ] + } + } + } + }, + "401": { + "description": "The resource is protected and requires an authentication token" + } + }, + "security": [ + { + "Backoffice User": [ ] + } + ] + } + }, "/umbraco/management/api/v1/published-cache/reload": { "post": { "tags": [ @@ -23101,7 +23149,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -23165,7 +23213,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -23210,7 +23258,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -23257,7 +23305,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -23300,7 +23348,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -23416,7 +23464,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -23476,7 +23524,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -23554,7 +23602,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -23736,7 +23784,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -23807,7 +23855,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -23903,7 +23951,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -24042,7 +24090,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -24198,7 +24246,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -24344,7 +24392,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -24415,7 +24463,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -24511,7 +24559,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -24570,7 +24618,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -24632,7 +24680,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -24687,7 +24735,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -24855,7 +24903,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -24948,7 +24996,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -25079,7 +25127,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -25279,7 +25327,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -25440,7 +25488,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -25825,7 +25873,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -25896,7 +25944,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -25992,7 +26040,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -26131,7 +26179,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -26287,7 +26335,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -26433,7 +26481,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -26504,7 +26552,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -26600,7 +26648,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -26659,7 +26707,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -26721,7 +26769,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -26776,7 +26824,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -26904,7 +26952,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -26939,7 +26987,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -27030,7 +27078,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -27284,7 +27332,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -27356,7 +27404,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -27453,7 +27501,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -27593,7 +27641,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -27640,7 +27688,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -27718,7 +27766,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -27765,7 +27813,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -27813,7 +27861,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -27876,7 +27924,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -27931,7 +27979,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -28321,7 +28369,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -28382,7 +28430,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -28818,7 +28866,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -28960,7 +29008,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -29078,7 +29126,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -29143,7 +29191,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -29203,7 +29251,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -29274,7 +29322,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -29388,7 +29436,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -29513,7 +29561,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -29636,7 +29684,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -29776,7 +29824,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -29959,7 +30007,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -30062,7 +30110,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -30141,7 +30189,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -30201,7 +30249,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -30298,7 +30346,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -30438,7 +30486,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -30513,7 +30561,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -30620,7 +30668,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -30692,7 +30740,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -30822,7 +30870,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -30938,7 +30986,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -30993,7 +31041,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -31074,7 +31122,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -31196,7 +31244,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -31307,7 +31355,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -31447,7 +31495,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -31494,7 +31542,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -32081,7 +32129,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -32418,7 +32466,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -32549,7 +32597,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -32695,7 +32743,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -32823,7 +32871,7 @@ } }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -32949,7 +32997,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -33088,7 +33136,7 @@ } }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -33162,7 +33210,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -33267,7 +33315,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -33383,7 +33431,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -33515,7 +33563,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -33587,7 +33635,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -33684,7 +33732,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -33824,7 +33872,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource", + "description": "The authenticated user does not have access to this resource", "headers": { "Umb-Notifications": { "description": "The list of notifications produced during the request.", @@ -33952,7 +34000,7 @@ "description": "The resource is protected and requires an authentication token" }, "403": { - "description": "The authenticated user do not have access to this resource" + "description": "The authenticated user does not have access to this resource" } }, "security": [ @@ -36826,6 +36874,8 @@ "required": [ "documentType", "id", + "isProtected", + "isTrashed", "sortOrder", "values", "variants" @@ -36871,6 +36921,12 @@ } ] }, + "isTrashed": { + "type": "boolean" + }, + "isProtected": { + "type": "boolean" + }, "updater": { "type": "string", "nullable": true @@ -42795,7 +42851,6 @@ "PublishDocumentWithDescendantsRequestModel": { "required": [ "cultures", - "forceRepublish", "includeUnpublishedDescendants" ], "type": "object", @@ -42803,9 +42858,6 @@ "includeUnpublishedDescendants": { "type": "boolean" }, - "forceRepublish": { - "type": "boolean" - }, "cultures": { "type": "array", "items": { @@ -42881,6 +42933,18 @@ }, "additionalProperties": false }, + "RebuildStatusModel": { + "required": [ + "isRebuilding" + ], + "type": "object", + "properties": { + "isRebuilding": { + "type": "boolean" + } + }, + "additionalProperties": false + }, "RedirectStatusModel": { "enum": [ "Enabled", @@ -44037,7 +44101,7 @@ }, "maxFileSize": { "type": "integer", - "format": "int32", + "format": "int64", "nullable": true } }, diff --git a/src/Umbraco.Cms.Api.Management/OpenApi/BackOfficeSecurityRequirementsOperationFilterBase.cs b/src/Umbraco.Cms.Api.Management/OpenApi/BackOfficeSecurityRequirementsOperationFilterBase.cs index 655783494737..907b91cdac64 100644 --- a/src/Umbraco.Cms.Api.Management/OpenApi/BackOfficeSecurityRequirementsOperationFilterBase.cs +++ b/src/Umbraco.Cms.Api.Management/OpenApi/BackOfficeSecurityRequirementsOperationFilterBase.cs @@ -1,4 +1,4 @@ -using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.OpenApi.Models; using Swashbuckle.AspNetCore.SwaggerGen; @@ -59,7 +59,7 @@ public void Apply(OpenApiOperation operation, OperationFilterContext context) { operation.Responses.Add(StatusCodes.Status403Forbidden.ToString(), new OpenApiResponse() { - Description = "The authenticated user do not have access to this resource" + Description = "The authenticated user does not have access to this resource" }); } } diff --git a/src/Umbraco.Cms.Api.Management/Routing/BackOfficeAreaRoutes.cs b/src/Umbraco.Cms.Api.Management/Routing/BackOfficeAreaRoutes.cs index e7f0a597a65b..778dbf691f12 100644 --- a/src/Umbraco.Cms.Api.Management/Routing/BackOfficeAreaRoutes.cs +++ b/src/Umbraco.Cms.Api.Management/Routing/BackOfficeAreaRoutes.cs @@ -1,12 +1,8 @@ -using System.Text; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Routing; -using Microsoft.Extensions.Options; using Umbraco.Cms.Api.Management.Controllers.Security; using Umbraco.Cms.Api.Management.ServerEvents; using Umbraco.Cms.Core; -using Umbraco.Cms.Core.Configuration.Models; -using Umbraco.Cms.Core.Hosting; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Web.Common.Routing; using Umbraco.Extensions; @@ -14,53 +10,28 @@ namespace Umbraco.Cms.Api.Management.Routing; /// <summary> -/// Creates routes for the back office area +/// Creates routes for the back office area. /// </summary> public sealed class BackOfficeAreaRoutes : IAreaRoutes { private readonly IRuntimeState _runtimeState; - private readonly string _umbracoPathSegment; /// <summary> - /// Initializes a new instance of the <see cref="BackOfficeAreaRoutes" /> class. + /// Initializes a new instance of the <see cref="BackOfficeAreaRoutes" /> class. /// </summary> - public BackOfficeAreaRoutes( - IOptions<GlobalSettings> globalSettings, - IHostingEnvironment hostingEnvironment, - IRuntimeState runtimeState) - { - _runtimeState = runtimeState; - _umbracoPathSegment = globalSettings.Value.GetUmbracoMvcArea(hostingEnvironment); - } - - [Obsolete("Use non-obsolete constructor. This will be removed in Umbraco 15.")] - public BackOfficeAreaRoutes( - IOptions<GlobalSettings> globalSettings, - IHostingEnvironment hostingEnvironment, - IRuntimeState runtimeState, - UmbracoApiControllerTypeCollection apiControllers) : this(globalSettings, hostingEnvironment, runtimeState) - { + public BackOfficeAreaRoutes(IRuntimeState runtimeState) + => _runtimeState = runtimeState; - } /// <inheritdoc /> public void CreateRoutes(IEndpointRouteBuilder endpoints) { - - - switch (_runtimeState.Level) + if (_runtimeState.Level is RuntimeLevel.Install or RuntimeLevel.Upgrade or RuntimeLevel.Run) { - case RuntimeLevel.Install: - case RuntimeLevel.Upgrade: - case RuntimeLevel.Run: - MapMinimalBackOffice(endpoints); - endpoints.MapHub<BackofficeHub>(_umbracoPathSegment + Constants.Web.BackofficeSignalRHub); - endpoints.MapHub<ServerEventHub>(_umbracoPathSegment + Constants.Web.ServerEventSignalRHub); - break; - case RuntimeLevel.BootFailed: - case RuntimeLevel.Unknown: - case RuntimeLevel.Boot: - break; + MapMinimalBackOffice(endpoints); + + endpoints.MapHub<BackofficeHub>(Constants.System.UmbracoPathSegment + Constants.Web.BackofficeSignalRHub); + endpoints.MapHub<ServerEventHub>(Constants.System.UmbracoPathSegment + Constants.Web.ServerEventSignalRHub); } } @@ -70,7 +41,7 @@ public void CreateRoutes(IEndpointRouteBuilder endpoints) private void MapMinimalBackOffice(IEndpointRouteBuilder endpoints) { endpoints.MapUmbracoRoute<BackOfficeDefaultController>( - _umbracoPathSegment, + Constants.System.UmbracoPathSegment, null!, string.Empty, "Index", @@ -82,7 +53,7 @@ private void MapMinimalBackOffice(IEndpointRouteBuilder endpoints) endpoints.MapControllerRoute( "catch-all-sections-to-client", - new StringBuilder(_umbracoPathSegment).Append("/{**slug}").ToString(), + $"{Constants.System.UmbracoPathSegment}/{{**slug}}", new { Controller = ControllerExtensions.GetControllerName<BackOfficeDefaultController>(), diff --git a/src/Umbraco.Cms.Api.Management/Routing/PreviewRoutes.cs b/src/Umbraco.Cms.Api.Management/Routing/PreviewRoutes.cs index 6f190107754b..1327e1e14466 100644 --- a/src/Umbraco.Cms.Api.Management/Routing/PreviewRoutes.cs +++ b/src/Umbraco.Cms.Api.Management/Routing/PreviewRoutes.cs @@ -1,13 +1,9 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Routing; -using Microsoft.Extensions.Options; using Umbraco.Cms.Api.Management.Preview; using Umbraco.Cms.Core; -using Umbraco.Cms.Core.Configuration.Models; -using Umbraco.Cms.Core.Hosting; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Web.Common.Routing; -using Umbraco.Extensions; namespace Umbraco.Cms.Api.Management.Routing; @@ -17,36 +13,23 @@ namespace Umbraco.Cms.Api.Management.Routing; public sealed class PreviewRoutes : IAreaRoutes { private readonly IRuntimeState _runtimeState; - private readonly string _umbracoPathSegment; - public PreviewRoutes( - IOptions<GlobalSettings> globalSettings, - IHostingEnvironment hostingEnvironment, - IRuntimeState runtimeState) - { - _runtimeState = runtimeState; - _umbracoPathSegment = globalSettings.Value.GetUmbracoMvcArea(hostingEnvironment); - } + public PreviewRoutes(IRuntimeState runtimeState) + => _runtimeState = runtimeState; public void CreateRoutes(IEndpointRouteBuilder endpoints) { - switch (_runtimeState.Level) + if (_runtimeState.Level is RuntimeLevel.Install or RuntimeLevel.Upgrade or RuntimeLevel.Run) { - case RuntimeLevel.Install: - case RuntimeLevel.Upgrade: - case RuntimeLevel.Run: - endpoints.MapHub<PreviewHub>(GetPreviewHubRoute()); - break; - case RuntimeLevel.BootFailed: - case RuntimeLevel.Unknown: - case RuntimeLevel.Boot: - break; + endpoints.MapHub<PreviewHub>(GetPreviewHubRoute()); } } /// <summary> - /// Returns the path to the signalR hub used for preview + /// Gets the path to the SignalR hub used for preview. /// </summary> - /// <returns>Path to signalR hub</returns> - public string GetPreviewHubRoute() => $"/{_umbracoPathSegment}/{nameof(PreviewHub)}"; + /// <returns> + /// The path to the SignalR hub used for preview. + /// </returns> + public string GetPreviewHubRoute() => $"/{Constants.System.UmbracoPathSegment}/{nameof(PreviewHub)}"; } diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/Document/PublishDocumentWithDescendantsRequestModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/Document/PublishDocumentWithDescendantsRequestModel.cs index 2557adf9a415..bfd9aa0e3bef 100644 --- a/src/Umbraco.Cms.Api.Management/ViewModels/Document/PublishDocumentWithDescendantsRequestModel.cs +++ b/src/Umbraco.Cms.Api.Management/ViewModels/Document/PublishDocumentWithDescendantsRequestModel.cs @@ -4,7 +4,5 @@ public class PublishDocumentWithDescendantsRequestModel { public bool IncludeUnpublishedDescendants { get; set; } - public bool ForceRepublish { get; set; } - public required IEnumerable<string> Cultures { get; set; } } diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/PublishedCache/RebuildStatusModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/PublishedCache/RebuildStatusModel.cs new file mode 100644 index 000000000000..b966cf4edb3c --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/ViewModels/PublishedCache/RebuildStatusModel.cs @@ -0,0 +1,6 @@ +namespace Umbraco.Cms.Api.Management.ViewModels.PublishedCache; + +public class RebuildStatusModel +{ + public bool IsRebuilding { get; set; } +} diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/TemporaryFile/TemporaryFileConfigurationResponseModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/TemporaryFile/TemporaryFileConfigurationResponseModel.cs index 369f6fa75692..d544b89286d4 100644 --- a/src/Umbraco.Cms.Api.Management/ViewModels/TemporaryFile/TemporaryFileConfigurationResponseModel.cs +++ b/src/Umbraco.Cms.Api.Management/ViewModels/TemporaryFile/TemporaryFileConfigurationResponseModel.cs @@ -8,5 +8,5 @@ public class TemporaryFileConfigurationResponseModel public string[] AllowedUploadedFileExtensions { get; set; } = Array.Empty<string>(); - public int? MaxFileSize { get; set; } + public long? MaxFileSize { get; set; } } diff --git a/src/Umbraco.Cms.Imaging.ImageSharp2/Umbraco.Cms.Imaging.ImageSharp2.csproj b/src/Umbraco.Cms.Imaging.ImageSharp2/Umbraco.Cms.Imaging.ImageSharp2.csproj index 16bac191d5ec..7563b27d7426 100644 --- a/src/Umbraco.Cms.Imaging.ImageSharp2/Umbraco.Cms.Imaging.ImageSharp2.csproj +++ b/src/Umbraco.Cms.Imaging.ImageSharp2/Umbraco.Cms.Imaging.ImageSharp2.csproj @@ -4,7 +4,7 @@ <Description>Adds imaging support using ImageSharp/ImageSharp.Web version 2 to Umbraco CMS.</Description> </PropertyGroup> <ItemGroup> - <PackageReference Include="SixLabors.ImageSharp" VersionOverride="[2.1.9, 3)" /> + <PackageReference Include="SixLabors.ImageSharp" VersionOverride="[2.1.10, 3)" /> <PackageReference Include="SixLabors.ImageSharp.Web" VersionOverride="[2.0.2, 3)" /> </ItemGroup> diff --git a/src/Umbraco.Cms.Persistence.EFCore/Locking/SqliteEFCoreDistributedLockingMechanism.cs b/src/Umbraco.Cms.Persistence.EFCore/Locking/SqliteEFCoreDistributedLockingMechanism.cs index b6e2fa0b7b0e..13db11510616 100644 --- a/src/Umbraco.Cms.Persistence.EFCore/Locking/SqliteEFCoreDistributedLockingMechanism.cs +++ b/src/Umbraco.Cms.Persistence.EFCore/Locking/SqliteEFCoreDistributedLockingMechanism.cs @@ -16,10 +16,10 @@ namespace Umbraco.Cms.Persistence.EFCore.Locking; internal sealed class SqliteEFCoreDistributedLockingMechanism<T> : IDistributedLockingMechanism where T : DbContext { - private ConnectionStrings _connectionStrings; - private GlobalSettings _globalSettings; private readonly ILogger<SqliteEFCoreDistributedLockingMechanism<T>> _logger; private readonly Lazy<IEFCoreScopeAccessor<T>> _efCoreScopeAccessor; + private GlobalSettings _globalSettings; + private ConnectionStrings _connectionStrings; public SqliteEFCoreDistributedLockingMechanism( ILogger<SqliteEFCoreDistributedLockingMechanism<T>> logger, @@ -31,29 +31,26 @@ public SqliteEFCoreDistributedLockingMechanism( _efCoreScopeAccessor = efCoreScopeAccessor; _globalSettings = globalSettings.CurrentValue; _connectionStrings = connectionStrings.CurrentValue; - globalSettings.OnChange(x=>_globalSettings = x); - connectionStrings.OnChange(x=>_connectionStrings = x); + + globalSettings.OnChange(x => _globalSettings = x); + connectionStrings.OnChange(x => _connectionStrings = x); } public bool HasActiveRelatedScope => _efCoreScopeAccessor.Value.AmbientScope is not null; /// <inheritdoc /> - public bool Enabled => _connectionStrings.IsConnectionStringConfigured() && - string.Equals(_connectionStrings.ProviderName, "Microsoft.Data.Sqlite", StringComparison.InvariantCultureIgnoreCase) && _efCoreScopeAccessor.Value.AmbientScope is not null; + public bool Enabled + => _connectionStrings.IsConnectionStringConfigured() && + string.Equals(_connectionStrings.ProviderName, Constants.ProviderNames.SQLLite, StringComparison.InvariantCultureIgnoreCase) && + _efCoreScopeAccessor.Value.AmbientScope is not null; // With journal_mode=wal we can always read a snapshot. public IDistributedLock ReadLock(int lockId, TimeSpan? obtainLockTimeout = null) - { - obtainLockTimeout ??= _globalSettings.DistributedLockingReadLockDefaultTimeout; - return new SqliteDistributedLock(this, lockId, DistributedLockType.ReadLock, obtainLockTimeout.Value); - } + => new SqliteDistributedLock(this, lockId, DistributedLockType.ReadLock, obtainLockTimeout ?? _globalSettings.DistributedLockingReadLockDefaultTimeout); // With journal_mode=wal only a single write transaction can exist at a time. public IDistributedLock WriteLock(int lockId, TimeSpan? obtainLockTimeout = null) - { - obtainLockTimeout ??= _globalSettings.DistributedLockingWriteLockDefaultTimeout; - return new SqliteDistributedLock(this, lockId, DistributedLockType.WriteLock, obtainLockTimeout.Value); - } + => new SqliteDistributedLock(this, lockId, DistributedLockType.WriteLock, obtainLockTimeout ?? _globalSettings.DistributedLockingWriteLockDefaultTimeout); private sealed class SqliteDistributedLock : IDistributedLock { @@ -104,9 +101,9 @@ public SqliteDistributedLock( public DistributedLockType LockType { get; } - public void Dispose() => + public void Dispose() // Mostly no op, cleaned up by completing transaction in scope. - _parent._logger.LogDebug("Dropped {lockType} for id {id}", LockType, LockId); + => _parent._logger.LogDebug("Dropped {lockType} for id {id}", LockType, LockId); public override string ToString() => $"SqliteDistributedLock({LockId})"; @@ -115,15 +112,17 @@ public override string ToString() // Mostly no-op just check that we didn't end up ReadUncommitted for real. private void ObtainReadLock() { - IEfCoreScope<T>? efCoreScope = _parent._efCoreScopeAccessor.Value.AmbientScope ?? throw new PanicException("No current ambient scope"); + IEfCoreScope<T>? efCoreScope = _parent._efCoreScopeAccessor.Value.AmbientScope + ?? throw new PanicException("No current ambient scope"); - efCoreScope.ExecuteWithContextAsync<Task>(async database => + efCoreScope.ExecuteWithContextAsync<Task>(database => { if (database.Database.CurrentTransaction is null) { - throw new InvalidOperationException( - "SqliteDistributedLockingMechanism requires a transaction to function."); + throw new InvalidOperationException("SqliteDistributedLockingMechanism requires a transaction to function."); } + + return Task.CompletedTask; }); } @@ -131,14 +130,14 @@ private void ObtainReadLock() // lock occurs for entire database as opposed to row/table. private void ObtainWriteLock() { - IEfCoreScope<T>? efCoreScope = _parent._efCoreScopeAccessor.Value.AmbientScope ?? throw new PanicException("No ambient scope"); + IEfCoreScope<T>? efCoreScope = _parent._efCoreScopeAccessor.Value.AmbientScope + ?? throw new PanicException("No ambient scope"); efCoreScope.ExecuteWithContextAsync<Task>(async database => { if (database.Database.CurrentTransaction is null) { - throw new InvalidOperationException( - "SqliteDistributedLockingMechanism requires a transaction to function."); + throw new InvalidOperationException("SqliteDistributedLockingMechanism requires a transaction to function."); } var query = @$"UPDATE umbracoLock SET value = (CASE WHEN (value=1) THEN -1 ELSE 1 END) WHERE id = {LockId.ToString(CultureInfo.InvariantCulture)}"; diff --git a/src/Umbraco.Cms.StaticAssets/umbraco/UmbracoLogin/Index.cshtml b/src/Umbraco.Cms.StaticAssets/umbraco/UmbracoLogin/Index.cshtml index 90bf8d3598e5..8b4c1f328b11 100644 --- a/src/Umbraco.Cms.StaticAssets/umbraco/UmbracoLogin/Index.cshtml +++ b/src/Umbraco.Cms.StaticAssets/umbraco/UmbracoLogin/Index.cshtml @@ -1,4 +1,3 @@ -@using Microsoft.AspNetCore.Mvc.TagHelpers @using Microsoft.Extensions.Options; @using Umbraco.Cms.Api.Management.Controllers.Security @using Umbraco.Cms.Api.Management.Extensions @@ -11,10 +10,10 @@ @using Umbraco.Cms.Core.Serialization @using Umbraco.Cms.Web.Common.Hosting @using Umbraco.Extensions +@inject IOptions<GlobalSettings> GlobalSettings @inject IOptions<SecuritySettings> SecuritySettings @inject IEmailSender EmailSender @inject IHostingEnvironment HostingEnvironment -@inject IOptions<GlobalSettings> GlobalSettings @inject IProfilerHtml ProfilerHtml @inject IBackOfficeExternalLoginProviders ExternalLogins @inject IBackOfficePathGenerator BackOfficePathGenerator @@ -22,7 +21,7 @@ @inject IJsonSerializer JsonSerializer @{ bool.TryParse(Context.Request.Query["umbDebug"], out var isDebug); - var backOfficePath = GlobalSettings.Value.GetBackOfficePath(HostingEnvironment); + var backOfficePath = HostingEnvironment.GetBackOfficePath(); var loginLogoImage = Url.RouteUrl(BackOfficeGraphicsController.LoginLogoRouteName, new {Version= "1"}); var loginLogoImageAlternative = Url.RouteUrl(BackOfficeGraphicsController.LoginLogoAlternativeRouteName, new {Version= "1"}); var loginBackgroundImage = Url.RouteUrl(BackOfficeGraphicsController.LoginBackGroundRouteName, new {Version= "1"}); @@ -37,7 +36,7 @@ <html lang="@GlobalSettings.Value.DefaultUILanguage"> <head> <meta charset="UTF-8"/> - <base href="@backOfficePath.EnsureEndsWith('/')"/> + <base href="@backOfficePath.EnsureEndsWith('/')" /> <link rel="icon" type="image/svg+xml" href="~/umbraco/login/favicon.svg"/> <meta name="viewport" content="width=device-width, initial-scale=1.0"/> <meta name="mobile-web-app-capable" content="yes"/> diff --git a/src/Umbraco.Cms.StaticAssets/umbraco/UmbracoWebsite/Maintenance.cshtml b/src/Umbraco.Cms.StaticAssets/umbraco/UmbracoWebsite/Maintenance.cshtml index 94de5f3c5235..1d8ae6f01ef8 100644 --- a/src/Umbraco.Cms.StaticAssets/umbraco/UmbracoWebsite/Maintenance.cshtml +++ b/src/Umbraco.Cms.StaticAssets/umbraco/UmbracoWebsite/Maintenance.cshtml @@ -1,12 +1,8 @@ -@using Microsoft.Extensions.Options -@using Umbraco.Cms.Core.Configuration.Models @using Umbraco.Cms.Core.Hosting @using Umbraco.Cms.Core.Routing -@using Umbraco.Extensions -@inject IHostingEnvironment hostingEnvironment -@inject IOptions<GlobalSettings> globalSettings +@inject IHostingEnvironment HostingEnvironment @{ - var backOfficePath = globalSettings.Value.GetBackOfficePath(hostingEnvironment); + var backOfficePath = HostingEnvironment.GetBackOfficePath(); } <!doctype html> <html class="no-js" lang="en"> @@ -17,7 +13,7 @@ <title>Website is Under Maintainance</title> - <link rel="stylesheet" href="@WebPath.Combine(backOfficePath.TrimStart("~") , "website", "/nonodes.css")" /> + <link rel="stylesheet" href="@WebPath.Combine(backOfficePath , "website", "/nonodes.css")" /> <style type="text/css"> body { color:initial; diff --git a/src/Umbraco.Cms.StaticAssets/umbraco/UmbracoWebsite/NoNodes.cshtml b/src/Umbraco.Cms.StaticAssets/umbraco/UmbracoWebsite/NoNodes.cshtml index b6bbef0e3a7e..51ab51987bb1 100644 --- a/src/Umbraco.Cms.StaticAssets/umbraco/UmbracoWebsite/NoNodes.cshtml +++ b/src/Umbraco.Cms.StaticAssets/umbraco/UmbracoWebsite/NoNodes.cshtml @@ -1,14 +1,10 @@ -@using Microsoft.Extensions.Options +@model Umbraco.Cms.Web.Website.Models.NoNodesViewModel @using Umbraco.Cms.Api.Management.Controllers.Security -@using Umbraco.Cms.Core.Configuration.Models @using Umbraco.Cms.Core.Hosting @using Umbraco.Cms.Core.Routing -@using Umbraco.Extensions -@model Umbraco.Cms.Web.Website.Models.NoNodesViewModel -@inject IHostingEnvironment hostingEnvironment -@inject IOptions<GlobalSettings> globalSettings +@inject IHostingEnvironment HostingEnvironment @{ - var backOfficePath = globalSettings.Value.GetBackOfficePath(hostingEnvironment); + var backOfficePath = HostingEnvironment.GetBackOfficePath(); var logoImage = Url.RouteUrl(BackOfficeGraphicsController.LogoRouteName, new {Version= "1"}); } <!doctype html> @@ -20,7 +16,7 @@ <title>Umbraco: No Published Content</title> - <link rel="stylesheet" href="@WebPath.Combine(backOfficePath.TrimStart("~") , "website", "/nonodes.css")" /> + <link rel="stylesheet" href="@WebPath.Combine(backOfficePath , "website", "/nonodes.css")" /> </head> <body> diff --git a/src/Umbraco.Cms.StaticAssets/umbraco/UmbracoWebsite/NotFound.cshtml b/src/Umbraco.Cms.StaticAssets/umbraco/UmbracoWebsite/NotFound.cshtml index 38a9d559fdfe..6e90209cb5d1 100644 --- a/src/Umbraco.Cms.StaticAssets/umbraco/UmbracoWebsite/NotFound.cshtml +++ b/src/Umbraco.Cms.StaticAssets/umbraco/UmbracoWebsite/NotFound.cshtml @@ -1,12 +1,8 @@ -@using Microsoft.Extensions.Options -@using Umbraco.Cms.Core.Configuration.Models @using Umbraco.Cms.Core.Hosting @using Umbraco.Cms.Core.Routing -@using Umbraco.Extensions -@inject IHostingEnvironment hostingEnvironment -@inject IOptions<GlobalSettings> globalSettings +@inject IHostingEnvironment HostingEnvironment @{ - var backOfficePath = globalSettings.Value.GetBackOfficePath(hostingEnvironment); + var backOfficePath = HostingEnvironment.GetBackOfficePath(); } <!doctype html> <html class="no-js" lang="en"> @@ -17,7 +13,7 @@ <title>Page Not Found</title> - <link rel="stylesheet" href="@WebPath.Combine(backOfficePath.TrimStart("~") , "website", "/nonodes.css")" /> + <link rel="stylesheet" href="@WebPath.Combine(backOfficePath, "website", "/nonodes.css")" /> <style type="text/css"> body { color:initial; @@ -39,17 +35,16 @@ <article> <div> <h1>Page Not Found</h1> - @if (hostingEnvironment.IsDebugMode) + @if (HostingEnvironment.IsDebugMode) { - var reason = (string?)Context.Items["reason"]; var message = (string?)Context.Items["message"]; - if (!reason.IsNullOrWhiteSpace()) + if (!string.IsNullOrWhiteSpace(reason)) { <h3>@reason</h3> } - if (!message.IsNullOrWhiteSpace()) + if (!string.IsNullOrWhiteSpace(message)) { <p>@message</p> } diff --git a/src/Umbraco.Core/Configuration/ContentSettingsExtensions.cs b/src/Umbraco.Core/Configuration/ContentSettingsExtensions.cs index 23a67f3267bc..9c1f5faef9b6 100644 --- a/src/Umbraco.Core/Configuration/ContentSettingsExtensions.cs +++ b/src/Umbraco.Core/Configuration/ContentSettingsExtensions.cs @@ -5,24 +5,26 @@ namespace Umbraco.Extensions; public static class ContentSettingsExtensions { /// <summary> - /// Determines if file extension is allowed for upload based on (optional) white list and black list - /// held in settings. - /// Allow upload if extension is whitelisted OR if there is no whitelist and extension is NOT blacklisted. + /// Determines if file extension is allowed for upload based on (optional) allow list and deny list held in settings. + /// Disallowed file extensions are only considered if there are no allowed file extensions. /// </summary> - public static bool IsFileAllowedForUpload(this ContentSettings contentSettings, string extension) => - contentSettings.AllowedUploadedFileExtensions.Any(x => x.InvariantEquals(extension)) || - (contentSettings.AllowedUploadedFileExtensions.Any() == false && - contentSettings.DisallowedUploadedFileExtensions.Any(x => x.InvariantEquals(extension)) == false); + /// <param name="contentSettings">The content settings.</param> + /// <param name="extension">The file extension.</param> + /// <returns> + /// <c>true</c> if the file extension is allowed for upload; otherwise, <c>false</c>. + /// </returns> + public static bool IsFileAllowedForUpload(this ContentSettings contentSettings, string extension) + => contentSettings.AllowedUploadedFileExtensions.Any(x => x.InvariantEquals(extension)) || + (contentSettings.AllowedUploadedFileExtensions.Any() == false && contentSettings.DisallowedUploadedFileExtensions.Any(x => x.InvariantEquals(extension)) == false); /// <summary> - /// Gets the auto-fill configuration for a specified property alias. + /// Gets the auto-fill configuration for a specified property alias. /// </summary> - /// <param name="contentSettings"></param> + /// <param name="contentSettings">The content settings.</param> /// <param name="propertyTypeAlias">The property type alias.</param> - /// <returns>The auto-fill configuration for the specified property alias, or null.</returns> + /// <returns> + /// The auto-fill configuration for the specified property alias or <c>null</c> if not configured. + /// </returns> public static ImagingAutoFillUploadField? GetConfig(this ContentSettings contentSettings, string propertyTypeAlias) - { - ImagingAutoFillUploadField[] autoFillConfigs = contentSettings.Imaging.AutoFillImageProperties; - return autoFillConfigs?.FirstOrDefault(x => x.Alias == propertyTypeAlias); - } + => contentSettings.Imaging.AutoFillImageProperties.FirstOrDefault(x => x.Alias == propertyTypeAlias); } diff --git a/src/Umbraco.Core/Configuration/GlobalSettingsExtensions.cs b/src/Umbraco.Core/Configuration/GlobalSettingsExtensions.cs index 67966c984981..1fc15c05a3cd 100644 --- a/src/Umbraco.Core/Configuration/GlobalSettingsExtensions.cs +++ b/src/Umbraco.Core/Configuration/GlobalSettingsExtensions.cs @@ -6,25 +6,15 @@ namespace Umbraco.Extensions; public static class GlobalSettingsExtensions { - private static string? _mvcArea; - private static string? _backOfficePath; - /// <summary> /// Returns the absolute path for the Umbraco back office /// </summary> /// <param name="globalSettings"></param> /// <param name="hostingEnvironment"></param> /// <returns></returns> + [Obsolete("The UmbracoPath setting is removed, use IHostingEnvironment.GetBackOfficePath() instead. Scheduled for removal in Umbraco 17.")] public static string GetBackOfficePath(this GlobalSettings globalSettings, IHostingEnvironment hostingEnvironment) - { - if (_backOfficePath != null) - { - return _backOfficePath; - } - - _backOfficePath = hostingEnvironment.ToAbsolute(Constants.System.DefaultUmbracoPath); - return _backOfficePath; - } + => hostingEnvironment.GetBackOfficePath(); /// <summary> /// This returns the string of the MVC Area route. @@ -38,38 +28,7 @@ public static string GetBackOfficePath(this GlobalSettings globalSettings, IHost /// with something /// like "MyVirtualDirectory-Umbraco" instead of just "Umbraco". /// </remarks> + [Obsolete("The UmbracoPath setting is removed, use Constants.System.UmbracoPathSegment as area name instead. Scheduled for removal in Umbraco 17.")] public static string GetUmbracoMvcArea(this GlobalSettings globalSettings, IHostingEnvironment hostingEnvironment) - { - if (_mvcArea != null) - { - return _mvcArea; - } - - _mvcArea = globalSettings.GetUmbracoMvcAreaNoCache(hostingEnvironment); - - return _mvcArea; - } - - internal static string GetUmbracoMvcAreaNoCache( - this GlobalSettings globalSettings, - IHostingEnvironment hostingEnvironment) - { - var path = string.IsNullOrEmpty(Constants.System.DefaultUmbracoPath) - ? string.Empty - : hostingEnvironment.ToAbsolute(Constants.System.DefaultUmbracoPath); - - if (path.IsNullOrWhiteSpace()) - { - throw new InvalidOperationException("Cannot create an MVC Area path without the umbracoPath specified"); - } - - // beware of TrimStart, see U4-2518 - if (path.StartsWith(hostingEnvironment.ApplicationVirtualPath)) - { - path = path[hostingEnvironment.ApplicationVirtualPath.Length..]; - } - - return path.TrimStart(Constants.CharArrays.Tilde).TrimStart(Constants.CharArrays.ForwardSlash).Replace('/', '-') - .Trim().ToLower(); - } + => Constants.System.UmbracoPathSegment; } diff --git a/src/Umbraco.Core/Configuration/Models/BasicAuthSettings.cs b/src/Umbraco.Core/Configuration/Models/BasicAuthSettings.cs index 00342cd45a9c..94cab1763f3c 100644 --- a/src/Umbraco.Core/Configuration/Models/BasicAuthSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/BasicAuthSettings.cs @@ -19,19 +19,19 @@ public class BasicAuthSettings [DefaultValue(StaticEnabled)] public bool Enabled { get; set; } = StaticEnabled; + public ISet<string> AllowedIPs { get; set; } = new HashSet<string>(); - public string[] AllowedIPs { get; set; } = Array.Empty<string>(); - public SharedSecret SharedSecret { get; set; } = new SharedSecret(); + public SharedSecret SharedSecret { get; set; } = new(); public bool RedirectToLoginPage { get; set; } = false; - } public class SharedSecret { - private const string StaticHeaderName = "X-Authentication-Shared-Secret"; + private const string StaticHeaderName = "X-Authentication-Shared-Secret"; [DefaultValue(StaticHeaderName)] public string? HeaderName { get; set; } = StaticHeaderName; + public string? Value { get; set; } } diff --git a/src/Umbraco.Core/Configuration/Models/ContentDashboardSettings.cs b/src/Umbraco.Core/Configuration/Models/ContentDashboardSettings.cs index dfac63763cd0..b11dcd246cc3 100644 --- a/src/Umbraco.Core/Configuration/Models/ContentDashboardSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/ContentDashboardSettings.cs @@ -31,6 +31,5 @@ public class ContentDashboardSettings /// <summary> /// Gets the allowed addresses to retrieve data for the content dashboard. /// </summary> - /// <value>The URLs.</value> - public string[]? ContentDashboardUrlAllowlist { get; set; } + public ISet<string> ContentDashboardUrlAllowlist { get; set; } = new HashSet<string>(); } diff --git a/src/Umbraco.Core/Configuration/Models/ContentImagingSettings.cs b/src/Umbraco.Core/Configuration/Models/ContentImagingSettings.cs index 4634f6efb9c6..13d28ad26ddb 100644 --- a/src/Umbraco.Core/Configuration/Models/ContentImagingSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/ContentImagingSettings.cs @@ -12,7 +12,7 @@ public class ContentImagingSettings { internal const string StaticImageFileTypes = "jpeg,jpg,gif,bmp,png,tiff,tif,webp"; - private static readonly ImagingAutoFillUploadField[] DefaultImagingAutoFillUploadField = + private static readonly ISet<ImagingAutoFillUploadField> DefaultImagingAutoFillUploadField = new HashSet<ImagingAutoFillUploadField> { new() { @@ -28,10 +28,11 @@ public class ContentImagingSettings /// Gets or sets a value for the collection of accepted image file extensions. /// </summary> [DefaultValue(StaticImageFileTypes)] - public string[] ImageFileTypes { get; set; } = StaticImageFileTypes.Split(','); + public ISet<string> ImageFileTypes { get; set; } = new HashSet<string>(StaticImageFileTypes.Split(Constants.CharArrays.Comma)); /// <summary> /// Gets or sets a value for the imaging autofill following media file upload fields. /// </summary> - public ImagingAutoFillUploadField[] AutoFillImageProperties { get; set; } = DefaultImagingAutoFillUploadField; + /// <value> + public ISet<ImagingAutoFillUploadField> AutoFillImageProperties { get; set; } = DefaultImagingAutoFillUploadField; } diff --git a/src/Umbraco.Core/Configuration/Models/ContentSettings.cs b/src/Umbraco.Core/Configuration/Models/ContentSettings.cs index 0b2789180ede..3e89369f000d 100644 --- a/src/Umbraco.Core/Configuration/Models/ContentSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/ContentSettings.cs @@ -50,7 +50,7 @@ public class ContentSettings /// <summary> /// Gets or sets a value for the collection of error pages. /// </summary> - public ContentErrorPage[] Error404Collection { get; set; } = Array.Empty<ContentErrorPage>(); + public ISet<ContentErrorPage> Error404Collection { get; set; } = new HashSet<ContentErrorPage>(); /// <summary> /// Gets or sets a value for the preview badge mark-up. @@ -134,18 +134,18 @@ public class ContentSettings /// <summary> /// Gets or sets a value for the collection of file extensions that are allowed for upload. /// </summary> - public string[] AllowedUploadedFileExtensions { get; set; } = Array.Empty<string>(); + public ISet<string> AllowedUploadedFileExtensions { get; set; } = new HashSet<string>(); /// <summary> /// Gets or sets a value for the collection of file extensions that are disallowed for upload. /// </summary> [DefaultValue(StaticDisallowedUploadFiles)] - public string[] DisallowedUploadedFileExtensions { get; set; } = StaticDisallowedUploadFiles.Split(','); + public ISet<string> DisallowedUploadedFileExtensions { get; set; } = new HashSet<string>(StaticDisallowedUploadFiles.Split(Constants.CharArrays.Comma)); /// <summary> /// Gets or sets the allowed external host for media. If empty only relative paths are allowed. /// </summary> - public string[] AllowedMediaHosts { get; set; } = Array.Empty<string>(); + public ISet<string> AllowedMediaHosts { get; set; } = new HashSet<string>(); /// <summary> /// Gets or sets a value indicating whether to show domain warnings. diff --git a/src/Umbraco.Core/Configuration/Models/DeliveryApiSettings.cs b/src/Umbraco.Core/Configuration/Models/DeliveryApiSettings.cs index 07dc943c24c7..b1944dad5f3a 100644 --- a/src/Umbraco.Core/Configuration/Models/DeliveryApiSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/DeliveryApiSettings.cs @@ -39,8 +39,10 @@ public class DeliveryApiSettings /// Gets or sets the aliases of the content types that may never be exposed through the Delivery API. Content of these /// types will never be returned from any Delivery API endpoint, nor added to the query index. /// </summary> - /// <value>The content type aliases that are not to be exposed.</value> - public string[] DisallowedContentTypeAliases { get; set; } = Array.Empty<string>(); + /// <value> + /// The content type aliases that are not to be exposed. + /// </value> + public ISet<string> DisallowedContentTypeAliases { get; set; } = new HashSet<string>(); /// <summary> /// Gets or sets a value indicating whether the Delivery API should output rich text values as JSON instead of HTML. @@ -139,15 +141,21 @@ public class AuthorizationCodeFlowSettings /// <summary> /// Gets or sets the URLs allowed to use as redirect targets after a successful login (session authorization). /// </summary> - /// <value>The URLs allowed as redirect targets.</value> - public Uri[] LoginRedirectUrls { get; set; } = Array.Empty<Uri>(); + /// <value> + /// The URLs allowed as redirect targets. + /// </value> + public ISet<Uri> LoginRedirectUrls { get; set; } = new HashSet<Uri>(); /// <summary> /// Gets or sets the URLs allowed to use as redirect targets after a successful logout (session termination). /// </summary> - /// <value>The URLs allowed as redirect targets.</value> - /// <remarks>These are only required if logout is to be used.</remarks> - public Uri[] LogoutRedirectUrls { get; set; } = Array.Empty<Uri>(); + /// <value> + /// The URLs allowed as redirect targets. + /// </value> + /// <remarks> + /// These are only required if logout is to be used. + /// </remarks> + public ISet<Uri> LogoutRedirectUrls { get; set; } = new HashSet<Uri>(); } /// <summary> diff --git a/src/Umbraco.Core/Configuration/Models/HealthChecksNotificationSettings.cs b/src/Umbraco.Core/Configuration/Models/HealthChecksNotificationSettings.cs index 30f6380e8116..e72a6674a4f6 100644 --- a/src/Umbraco.Core/Configuration/Models/HealthChecksNotificationSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/HealthChecksNotificationSettings.cs @@ -33,12 +33,10 @@ public class HealthChecksNotificationSettings /// <summary> /// Gets or sets a value for the collection of health check notification methods. /// </summary> - public IDictionary<string, HealthChecksNotificationMethodSettings> NotificationMethods { get; set; } = - new Dictionary<string, HealthChecksNotificationMethodSettings>(); + public IDictionary<string, HealthChecksNotificationMethodSettings> NotificationMethods { get; set; } = new Dictionary<string, HealthChecksNotificationMethodSettings>(); /// <summary> /// Gets or sets a value for the collection of health checks that are disabled for notifications. /// </summary> - public List<DisabledHealthCheckSettings> DisabledChecks { get; set; } = - new List<DisabledHealthCheckSettings>(); + public IList<DisabledHealthCheckSettings> DisabledChecks { get; set; } = new List<DisabledHealthCheckSettings>(); } diff --git a/src/Umbraco.Core/Configuration/Models/HealthChecksSettings.cs b/src/Umbraco.Core/Configuration/Models/HealthChecksSettings.cs index 638baeb562d4..319fa8e44d30 100644 --- a/src/Umbraco.Core/Configuration/Models/HealthChecksSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/HealthChecksSettings.cs @@ -12,8 +12,7 @@ public class HealthChecksSettings /// <summary> /// Gets or sets a value for the collection of healthchecks that are disabled. /// </summary> - public List<DisabledHealthCheckSettings> DisabledChecks { get; set; } = - new List<DisabledHealthCheckSettings>(); + public IList<DisabledHealthCheckSettings> DisabledChecks { get; set; } = new List<DisabledHealthCheckSettings>(); /// <summary> /// Gets or sets a value for the healthcheck notification settings. diff --git a/src/Umbraco.Core/Configuration/Models/HelpPageSettings.cs b/src/Umbraco.Core/Configuration/Models/HelpPageSettings.cs index 01d028f883ac..6bad1eb973e5 100644 --- a/src/Umbraco.Core/Configuration/Models/HelpPageSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/HelpPageSettings.cs @@ -6,5 +6,5 @@ public class HelpPageSettings /// <summary> /// Gets or sets the allowed addresses to retrieve data for the content dashboard. /// </summary> - public string[]? HelpPageUrlAllowList { get; set; } + public ISet<string> HelpPageUrlAllowList { get; set; } = new HashSet<string>(); } diff --git a/src/Umbraco.Core/Configuration/Models/InstallDefaultDataSettings.cs b/src/Umbraco.Core/Configuration/Models/InstallDefaultDataSettings.cs index 25789b397bdb..66b871dd2f0d 100644 --- a/src/Umbraco.Core/Configuration/Models/InstallDefaultDataSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/InstallDefaultDataSettings.cs @@ -70,5 +70,5 @@ public class InstallDefaultDataSettings /// https://github.com/umbraco/Umbraco-CMS/blob/v9/dev/src/Umbraco.Infrastructure/Migrations/Install/DatabaseDataCreator.cs. /// </para> /// </remarks> - public IList<string> Values { get; set; } = new List<string>(); + public ISet<string> Values { get; set; } = new HashSet<string>(); } diff --git a/src/Umbraco.Core/Configuration/Models/MarketplaceSettings.cs b/src/Umbraco.Core/Configuration/Models/MarketplaceSettings.cs index 73f7da185fcf..092e420e3c12 100644 --- a/src/Umbraco.Core/Configuration/Models/MarketplaceSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/MarketplaceSettings.cs @@ -12,5 +12,5 @@ public class MarketplaceSettings /// <summary> /// Gets or sets the additional parameters that are sent to the Marketplace. /// </summary> - public Dictionary<string, string> AdditionalParameters { get; set; } = new (); + public IDictionary<string, string> AdditionalParameters { get; set; } = new Dictionary<string, string>(); } diff --git a/src/Umbraco.Core/Configuration/Models/RequestHandlerSettings.cs b/src/Umbraco.Core/Configuration/Models/RequestHandlerSettings.cs index a1d8f95a373a..d4e7f66c8158 100644 --- a/src/Umbraco.Core/Configuration/Models/RequestHandlerSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/RequestHandlerSettings.cs @@ -2,7 +2,6 @@ // See LICENSE for more details. using System.ComponentModel; -using Umbraco.Cms.Core.Configuration.UmbracoSettings; using Umbraco.Extensions; namespace Umbraco.Cms.Core.Configuration.Models; @@ -99,5 +98,5 @@ public class RequestHandlerSettings /// <summary> /// Add additional character replacements, or override defaults /// </summary> - public IEnumerable<CharItem>? UserDefinedCharCollection { get; set; } + public ISet<CharItem> UserDefinedCharCollection { get; set; } = new HashSet<CharItem>(); } diff --git a/src/Umbraco.Core/Configuration/Models/RuntimeSettings.cs b/src/Umbraco.Core/Configuration/Models/RuntimeSettings.cs index e49251590622..a1598fd37703 100644 --- a/src/Umbraco.Core/Configuration/Models/RuntimeSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/RuntimeSettings.cs @@ -19,15 +19,10 @@ public class RuntimeSettings public RuntimeMode Mode { get; set; } = RuntimeMode.BackofficeDevelopment; /// <summary> - /// Gets or sets a value for the maximum query string length. - /// </summary> [Obsolete("No longer used and will be removed in Umbraco 16.")] - public int? MaxQueryStringLength { get; set; } - - /// <summary> /// Gets or sets a value for the maximum request length in kb. /// </summary> - public int? MaxRequestLength { get; set; } + public long? MaxRequestLength { get; set; } /// <summary> /// Gets or sets the timespan temporary files are kept, before they are removed by a background task. diff --git a/src/Umbraco.Core/Configuration/Models/TypeFinderSettings.cs b/src/Umbraco.Core/Configuration/Models/TypeFinderSettings.cs index 70df241ca193..cd2d44642b5d 100644 --- a/src/Umbraco.Core/Configuration/Models/TypeFinderSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/TypeFinderSettings.cs @@ -21,10 +21,10 @@ public class TypeFinderSettings /// By default the entry assemblies for scanning plugin types is the Umbraco DLLs. If you require /// scanning for plugins based on different root referenced assemblies you can add the assembly name to this list. /// </summary> - public IEnumerable<string>? AdditionalEntryAssemblies { get; set; } + public ISet<string> AdditionalEntryAssemblies { get; set; } = new HashSet<string>(); /// <summary> /// Gets or sets a value for the assemblies that will be excluded from scanning. /// </summary> - public string[] AdditionalAssemblyExclusionEntries { get; set; } = Array.Empty<string>(); + public ISet<string> AdditionalAssemblyExclusionEntries { get; set; } = new HashSet<string>(); } diff --git a/src/Umbraco.Core/Constants-System.cs b/src/Umbraco.Core/Constants-System.cs index cf7c1de325fe..62f96bb9cb11 100644 --- a/src/Umbraco.Core/Constants-System.cs +++ b/src/Umbraco.Core/Constants-System.cs @@ -3,90 +3,114 @@ namespace Umbraco.Cms.Core; public static partial class Constants { /// <summary> - /// Defines the identifiers for Umbraco system nodes. + /// Defines the Umbraco system constants. /// </summary> public static class System { /// <summary> - /// The integer identifier for global system root node. + /// The integer identifier for global system root node. /// </summary> public const int Root = -1; /// <summary> - /// The string identifier for global system root node. + /// The string identifier for global system root node. /// </summary> - /// <remarks>Use this instead of re-creating the string everywhere.</remarks> + /// <remarks> + /// Use this instead of re-creating the string everywhere. + /// </remarks> public const string RootString = "-1"; /// <summary> - /// The GUID identifier for global system root node. + /// The GUID identifier for global system root node. /// </summary> public static readonly Guid? RootKey = null; /// <summary> - /// The integer identifier for content's recycle bin. + /// The integer identifier for content's recycle bin. /// </summary> public const int RecycleBinContent = -20; /// <summary> - /// The GUId identifier for content's recycle bin. + /// The string identifier for content's recycle bin. /// </summary> - public static readonly Guid RecycleBinContentKey = new("0F582A79-1E41-4CF0-BFA0-76340651891A"); + /// <remarks> + /// Use this instead of re-creating the string everywhere. + /// </remarks> + public const string RecycleBinContentString = "-20"; /// <summary> - /// The string identifier for content's recycle bin. + /// The GUID identifier for content's recycle bin. /// </summary> - /// <remarks>Use this instead of re-creating the string everywhere.</remarks> - public const string RecycleBinContentString = "-20"; + public static readonly Guid RecycleBinContentKey = new("0F582A79-1E41-4CF0-BFA0-76340651891A"); /// <summary> - /// The string path prefix of the content's recycle bin. + /// The string path prefix of the content's recycle bin. /// </summary> /// <remarks> - /// <para>Everything that is in the content recycle bin, has a path that starts with the prefix.</para> - /// <para>Use this instead of re-creating the string everywhere.</para> + /// <para>Everything that is in the content recycle bin, has a path that starts with the prefix.</para> + /// <para>Use this instead of re-creating the string everywhere.</para> /// </remarks> public const string RecycleBinContentPathPrefix = "-1,-20,"; /// <summary> - /// The integer identifier for media's recycle bin. + /// The integer identifier for media's recycle bin. /// </summary> public const int RecycleBinMedia = -21; /// <summary> - /// The GUID identifier for media's recycle bin. + /// The string identifier for media's recycle bin. /// </summary> - public static readonly Guid RecycleBinMediaKey = new("BF7C7CBC-952F-4518-97A2-69E9C7B33842"); + /// <remarks> + /// Use this instead of re-creating the string everywhere. + /// </remarks> + public const string RecycleBinMediaString = "-21"; /// <summary> - /// The string identifier for media's recycle bin. + /// The GUID identifier for media's recycle bin. /// </summary> - /// <remarks>Use this instead of re-creating the string everywhere.</remarks> - public const string RecycleBinMediaString = "-21"; + public static readonly Guid RecycleBinMediaKey = new("BF7C7CBC-952F-4518-97A2-69E9C7B33842"); /// <summary> - /// The string path prefix of the media's recycle bin. + /// The string path prefix of the media's recycle bin. /// </summary> /// <remarks> - /// <para>Everything that is in the media recycle bin, has a path that starts with the prefix.</para> - /// <para>Use this instead of re-creating the string everywhere.</para> + /// <para>Everything that is in the media recycle bin, has a path that starts with the prefix.</para> + /// <para>Use this instead of re-creating the string everywhere.</para> /// </remarks> public const string RecycleBinMediaPathPrefix = "-1,-21,"; + /// <summary> + /// The default label data type identifier. + /// </summary> public const int DefaultLabelDataTypeId = -92; + /// <summary> + /// The default Umbraco database name. + /// </summary> public const string UmbracoDefaultDatabaseName = "Umbraco"; + /// <summary> + /// The Umbraco connection name. + /// </summary> public const string UmbracoConnectionName = "umbracoDbDSN"; - public const string DefaultUmbracoPath = "~/umbraco"; /// <summary> - /// The DataDirectory name. + /// The Umbraco path segment. + /// </summary> + public const string UmbracoPathSegment = "umbraco"; + + /// <summary> + /// The default Umbraco virtual path. + /// </summary> + public const string DefaultUmbracoPath = "~/" + UmbracoPathSegment; + + /// <summary> + /// The application domain data name for <c>DataDirectory</c>. /// </summary> public const string DataDirectoryName = "DataDirectory"; /// <summary> - /// The DataDirectory placeholder. + /// The <c>DataDirectory</c> placeholder used to resolve paths in connection strings. /// </summary> public const string DataDirectoryPlaceholder = "|DataDirectory|"; diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/da.xml b/src/Umbraco.Core/EmbeddedResources/Lang/da.xml index 7f0c7228e438..03a4ab185e72 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/da.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/da.xml @@ -114,14 +114,23 @@ Mange hilsner fra Umbraco robotten <key alias="entriesExceed"><![CDATA[Maksimum %0% element(er), <strong>%1%</strong> for mange.]]></key> <key alias="entriesAreasMismatch">Ét eller flere områder lever ikke op til kravene for antal indholdselementer.</key> <key alias="invalidMediaType">Den valgte medie type er ugyldig.</key> + <key alias="invalidContentType">Det valgte indhold er af en ugyldig type.</key> + <key alias="missingContent">Det valgte indhold eksistere ikke.</key> <key alias="multipleMediaNotAllowed">Det er kun tilladt at vælge ét medie.</key> - <key alias="invalidStartNode">Valgt medie kommer fra en ugyldig mappe.</key> + <key alias="invalidStartNode">Valgt indhold kommer fra en ugyldig mappe.</key> <key alias="outOfRangeMinimum">Værdien %0% er mindre end det tilladte minimum af %1%.</key> <key alias="outOfRangeMaximum">Værdien %0% er større end det tilladte maksimum af %1%.</key> + <key alias="outOfRangeSingleItemMinimum">Den ene enhed givet er mindre end det tilladte minimum af %1%.</key> + <key alias="outOfRangeMultipleItemsMinimum">De %0% enheder givet er mindre end det tilladte minimum af %1%.</key> + <key alias="outOfRangeMultipleItemsMaximum">De %0% enheder givet er større end det tilladte minimum af %1%.</key> <key alias="invalidStep">Værdien %0% passer ikke med den konfigureret trin værdi af %1% og mindste værdi af %2%.</key> <key alias="unexpectedRange">Værdien %0% forventes ikke at indeholde et spænd.</key> + <key alias="stringLengthExceeded">Tekststrengen er længere end den tilladte længde på %0% tegn.</key> <key alias="invalidRange">Værdien %0% forventes at have en værdi der er større end fra værdien.</key> + <key alias="invalidObjectType">Det valgte indhold er af den forkerte type.</key> <key alias="notOneOfOptions">"Værdien '%0%' er ikke en af de tilgængelige valgmuligheder.</key> + <key alias="multipleNotOneOfOptions">Værdierne '%0%' er ikke tilstede i de tilgængelige valgmuligheder.</key> + <key alias="invalidColor">"Den valgte farve '%0%' er ikke en af de tilgængelige valgmuligheder.</key> </area> <area alias="recycleBin"> <key alias="contentTrashed">Slettet indhold med Id: {0} Relateret til original "parent" med id: {1}</key> diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/en.xml b/src/Umbraco.Core/EmbeddedResources/Lang/en.xml index 8925328ee7f6..730fa1a6e9be 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/en.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/en.xml @@ -378,6 +378,7 @@ <key alias="invalidPattern">Value is invalid, it does not match the correct pattern</key> <key alias="entriesShort"><![CDATA[Minimum %0% entries, requires <strong>%1%</strong> more.]]></key> <key alias="entriesExceed"><![CDATA[Maximum %0% entries, <strong>%1%</strong> too many.]]></key> + <key alias="stringLengthExceeded">The string length exceeds the maximum length of %0% characters.</key> <key alias="entriesAreasMismatch">The content amount requirements are not met for one or more areas.</key> <key alias="invalidMemberGroupName">Invalid member group name</key> <key alias="invalidUserGroupName">Invalid user group name</key> @@ -389,12 +390,21 @@ <key alias="duplicateUsername">Username '%0%' is already taken</key> <key alias="outOfRangeMinimum">The value %0% is less than the allowed minimum value of %1%</key> <key alias="outOfRangeMaximum">The value %0% is greater than the allowed maximum value of %1%</key> + <key alias="outOfRangeSingleItemMinimum">The 1 item provided is less than the allowed minimum of %1%</key> + <key alias="outOfRangeMultipleItemsMinimum">The %0% items provided are less than the allowed minimum of %1%</key> + <key alias="outOfRangeMultipleItemsMaximum">The %0% items provided are greater than the allowed maximum of %1%</key> <key alias="invalidStep">The value %0% does not correspond with the configured step value of %1% and minimum value of %2%</key> <key alias="unexpectedRange">The value %0% is not expected to contain a range</key> <key alias="invalidRange">The value %0% is not expected to have a to value less than the from value</key> <key alias="invalidMediaType">The chosen media type is invalid.</key> + <key alias="invalidContentType">The chosen content is of invalid type.</key> + <key alias="missingContent">The chosen content does not exist.</key> <key alias="multipleMediaNotAllowed">Multiple selected media is not allowed.</key> - <key alias="invalidStartNode">The selected media is from the wrong folder.</key> + <key alias="notOneOfOptions">The value '%0%' is not one of the available options.</key> + <key alias="multipleNotOneOfOptions">The values '%0%' are not found in the the available options.</key> + <key alias="invalidColor">"The selected colour '%0%' is not one of the available options.</key> + <key alias="invalidStartNode">The selected item is from the wrong folder.</key> + <key alias="invalidObjectType">The selected item is of the wrong type.</key> <key alias="notOneOfOptions">"The value '%0%' is not one of the available options.</key> </area> <area alias="healthcheck"> diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml b/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml index 8233697b39dc..2327ac21d01e 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml @@ -387,16 +387,25 @@ <key alias="duplicateUsername">Username '%0%' is already taken</key> <key alias="outOfRangeMinimum">The value %0% is less than the allowed minimum value of %1%</key> <key alias="outOfRangeMaximum">The value %0% is greater than the allowed maximum value of %1%</key> + <key alias="outOfRangeSingleItemMinimum">The 1 item provided is less than the allowed minimum of %1%</key> + <key alias="outOfRangeMultipleItemsMinimum">The %0% items provided are less than the allowed minimum of %1%</key> + <key alias="outOfRangeMultipleItemsMaximum">The %0% items provided are greater than the allowed maximum of %1%</key> <key alias="invalidStep">The value %0% does not correspond with the configured step value of %1% and minimum value of %2%</key> <key alias="unexpectedRange">The value %0% is not expected to contain a range</key> <key alias="invalidRange">The value %0% is not expected to have a to value less than the from value</key> <key alias="entriesShort"><![CDATA[Minimum %0% entries, requires <strong>%1%</strong> more.]]></key> <key alias="entriesExceed"><![CDATA[Maximum %0% entries, <strong>%1%</strong> too many.]]></key> + <key alias="stringLengthExceeded">The string length exceeds the maximum length of %0% characters.</key> <key alias="entriesAreasMismatch">The content amount requirements are not met for one or more areas.</key> <key alias="invalidMediaType">The chosen media type is invalid.</key> + <key alias="invalidContentType">The chosen content is of invalid type.</key> + <key alias="missingContent">The chosen content does not exist.</key> <key alias="multipleMediaNotAllowed">Multiple selected media is not allowed.</key> - <key alias="invalidStartNode">The selected media is from the wrong folder.</key> + <key alias="invalidStartNode">The selected item is from the wrong folder.</key> + <key alias="invalidObjectType">The selected item is of the wrong type.</key> <key alias="notOneOfOptions">"The value '%0%' is not one of the available options.</key> + <key alias="multipleNotOneOfOptions">The values '%0%' are not found in the the available options.</key> + <key alias="invalidColor">The selected color '%0%' is not one of the available options.</key> </area> <area alias="healthcheck"> <!-- The following keys get these tokens passed in: diff --git a/src/Umbraco.Core/HealthChecks/Checks/Security/BaseHttpHeaderCheck.cs b/src/Umbraco.Core/HealthChecks/Checks/Security/BaseHttpHeaderCheck.cs index b9d14a913168..751998c85a84 100644 --- a/src/Umbraco.Core/HealthChecks/Checks/Security/BaseHttpHeaderCheck.cs +++ b/src/Umbraco.Core/HealthChecks/Checks/Security/BaseHttpHeaderCheck.cs @@ -62,15 +62,14 @@ protected BaseHttpHeaderCheck( /// <summary> /// Get the status for this health check /// </summary> - public override async Task<IEnumerable<HealthCheckStatus>> GetStatusAsync() => - await Task.WhenAll(CheckForHeader()); + public override async Task<IEnumerable<HealthCheckStatus>> GetStatusAsync() + => [await CheckForHeader()]; /// <summary> /// Executes the action and returns it's status /// </summary> public override HealthCheckStatus ExecuteAction(HealthCheckAction action) - => throw new InvalidOperationException( - "HTTP Header action requested is either not executable or does not exist"); + => throw new InvalidOperationException("HTTP Header action requested is either not executable or does not exist"); /// <summary> /// The actual health check method. diff --git a/src/Umbraco.Core/HealthChecks/Checks/Security/ExcessiveHeadersCheck.cs b/src/Umbraco.Core/HealthChecks/Checks/Security/ExcessiveHeadersCheck.cs index e52746392909..9bcee6f5d3ac 100644 --- a/src/Umbraco.Core/HealthChecks/Checks/Security/ExcessiveHeadersCheck.cs +++ b/src/Umbraco.Core/HealthChecks/Checks/Security/ExcessiveHeadersCheck.cs @@ -35,8 +35,8 @@ public ExcessiveHeadersCheck(ILocalizedTextService textService, IHostingEnvironm /// <summary> /// Get the status for this health check /// </summary> - public override async Task<IEnumerable<HealthCheckStatus>> GetStatusAsync() => - await Task.WhenAll(CheckForHeaders()); + public override async Task<IEnumerable<HealthCheckStatus>> GetStatusAsync() + => [await CheckForHeaders()]; /// <summary> /// Executes the action and returns it's status diff --git a/src/Umbraco.Core/HealthChecks/Checks/Security/HttpsCheck.cs b/src/Umbraco.Core/HealthChecks/Checks/Security/HttpsCheck.cs index e28b821842cc..c1deb71a49fb 100644 --- a/src/Umbraco.Core/HealthChecks/Checks/Security/HttpsCheck.cs +++ b/src/Umbraco.Core/HealthChecks/Checks/Security/HttpsCheck.cs @@ -46,16 +46,16 @@ public HttpsCheck( _hostingEnvironment = hostingEnvironment; } /// <inheritdoc /> - public override async Task<IEnumerable<HealthCheckStatus>> GetStatusAsync() => - await Task.WhenAll( - CheckIfCurrentSchemeIsHttps(), - CheckHttpsConfigurationSetting(), - CheckForValidCertificate()); + public override async Task<IEnumerable<HealthCheckStatus>> GetStatusAsync() + => [ + await CheckIfCurrentSchemeIsHttps(), + await CheckHttpsConfigurationSetting(), + await CheckForValidCertificate() + ]; /// <inheritdoc /> public override HealthCheckStatus ExecuteAction(HealthCheckAction action) - => throw new InvalidOperationException( - "HttpsCheck action requested is either not executable or does not exist"); + => throw new InvalidOperationException("HttpsCheck action requested is either not executable or does not exist"); private static bool ServerCertificateCustomValidation( HttpRequestMessage requestMessage, diff --git a/src/Umbraco.Core/Hosting/HostingEnvironmentExtensions.cs b/src/Umbraco.Core/Hosting/HostingEnvironmentExtensions.cs new file mode 100644 index 000000000000..c9ff32b29d52 --- /dev/null +++ b/src/Umbraco.Core/Hosting/HostingEnvironmentExtensions.cs @@ -0,0 +1,18 @@ +namespace Umbraco.Cms.Core.Hosting; + +/// <summary> +/// Extension methods for <see cref="IHostingEnvironment" />. +/// </summary> +public static class HostingEnvironmentExtensions +{ + private static string? _backOfficePath; + + /// <summary> + /// Gets the absolute URL path of the back office. + /// </summary> + /// <param name="hostingEnvironment">The hosting environment.</param> + /// <returns>The absolute URL path of the back office.</returns> + public static string GetBackOfficePath(this IHostingEnvironment hostingEnvironment) + // Store the resolved URL path to avoid repeated conversion + => _backOfficePath ??= hostingEnvironment.ToAbsolute(Core.Constants.System.DefaultUmbracoPath); +} diff --git a/src/Umbraco.Core/Manifest/PackageManifest.cs b/src/Umbraco.Core/Manifest/PackageManifest.cs index 8ff77729b05a..f07bbccdb51f 100644 --- a/src/Umbraco.Core/Manifest/PackageManifest.cs +++ b/src/Umbraco.Core/Manifest/PackageManifest.cs @@ -12,6 +12,9 @@ public class PackageManifest public bool AllowTelemetry { get; set; } = true; + [Obsolete("Use AllowTelemetry instead. This property will be removed in future versions.")] + public bool AllowPackageTelemetry { get; set; } = true; + public required object[] Extensions { get; set; } public PackageManifestImportmap? Importmap { get; set; } diff --git a/src/Umbraco.Core/Models/ContentPropertySettings.cs b/src/Umbraco.Core/Models/ContentPropertySettings.cs index 46b130bc9efe..7953a5a1d5b4 100644 --- a/src/Umbraco.Core/Models/ContentPropertySettings.cs +++ b/src/Umbraco.Core/Models/ContentPropertySettings.cs @@ -10,4 +10,6 @@ public class ContentPropertySettings public ISet<string> ReservedFieldNames => _reservedFieldNames; public bool AddReservedFieldName(string name) => _reservedFieldNames.Add(name); + + public void AddReservedFieldNames(ISet<string> names) => _reservedFieldNames.UnionWith(names); } diff --git a/src/Umbraco.Core/Models/MediaPropertySettings.cs b/src/Umbraco.Core/Models/MediaPropertySettings.cs index db3eafa218af..e13512aa267c 100644 --- a/src/Umbraco.Core/Models/MediaPropertySettings.cs +++ b/src/Umbraco.Core/Models/MediaPropertySettings.cs @@ -10,4 +10,6 @@ public class MediaPropertySettings public ISet<string> ReservedFieldNames => _reservedFieldNames; public bool AddReservedFieldName(string name) => _reservedFieldNames.Add(name); + + public void AddReservedFieldNames(ISet<string> names) => _reservedFieldNames.UnionWith(names); } diff --git a/src/Umbraco.Core/Models/MemberPropertySettings.cs b/src/Umbraco.Core/Models/MemberPropertySettings.cs index b7e8b754c896..b5271fb75cf2 100644 --- a/src/Umbraco.Core/Models/MemberPropertySettings.cs +++ b/src/Umbraco.Core/Models/MemberPropertySettings.cs @@ -10,4 +10,6 @@ public class MemberPropertySettings public ISet<string> ReservedFieldNames => _reservedFieldNames; public bool AddReservedFieldName(string name) => _reservedFieldNames.Add(name); + + public void AddReservedFieldNames(ISet<string> names) => _reservedFieldNames.UnionWith(names); } diff --git a/src/Umbraco.Core/Models/PublishedContent/IPublishedMember.cs b/src/Umbraco.Core/Models/PublishedContent/IPublishedMember.cs index 9095a4aaa389..0d028961f6d2 100644 --- a/src/Umbraco.Core/Models/PublishedContent/IPublishedMember.cs +++ b/src/Umbraco.Core/Models/PublishedContent/IPublishedMember.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Models.PublishedContent; +namespace Umbraco.Cms.Core.Models.PublishedContent; public interface IPublishedMember : IPublishedContent { diff --git a/src/Umbraco.Core/PropertyEditors/MultiNodePickerConfiguration.cs b/src/Umbraco.Core/PropertyEditors/MultiNodePickerConfiguration.cs index 4ccd5399cef1..4729474029ce 100644 --- a/src/Umbraco.Core/PropertyEditors/MultiNodePickerConfiguration.cs +++ b/src/Umbraco.Core/PropertyEditors/MultiNodePickerConfiguration.cs @@ -17,6 +17,9 @@ public class MultiNodePickerConfiguration : IIgnoreUserStartNodesConfig [ConfigurationField("maxNumber")] public int MaxNumber { get; set; } + [ConfigurationField("filter")] + public string? Filter { get; set; } + [ConfigurationField(Constants.DataTypes.ReservedPreValueKeys.IgnoreUserStartNodes)] public bool IgnoreUserStartNodes { get; set; } } diff --git a/src/Umbraco.Core/PropertyEditors/TextOnlyValueEditor.cs b/src/Umbraco.Core/PropertyEditors/TextOnlyValueEditor.cs index 0b0d34b1ef25..8a2ff20da678 100644 --- a/src/Umbraco.Core/PropertyEditors/TextOnlyValueEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/TextOnlyValueEditor.cs @@ -1,8 +1,13 @@ +using System.ComponentModel.DataAnnotations; +using Microsoft.Extensions.DependencyInjection; +using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.Validation; using Umbraco.Cms.Core.Serialization; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Strings; +using Umbraco.Extensions; namespace Umbraco.Cms.Core.PropertyEditors; @@ -12,23 +17,27 @@ namespace Umbraco.Cms.Core.PropertyEditors; /// </summary> public class TextOnlyValueEditor : DataValueEditor { - [Obsolete($"Use the constructor that does not accept {nameof(ILocalizedTextService)}. Will be removed in V15.")] public TextOnlyValueEditor( DataEditorAttribute attribute, ILocalizedTextService localizedTextService, IShortStringHelper shortStringHelper, IJsonSerializer jsonSerializer, IIOHelper ioHelper) - : this(attribute, shortStringHelper, jsonSerializer, ioHelper) - { - } + : base(shortStringHelper, jsonSerializer, ioHelper, attribute) => + Validators.Add(new LengthValidator(localizedTextService)); + [Obsolete($"Use the constructor that accepts {nameof(ILocalizedTextService)}. Will be removed in V16.")] public TextOnlyValueEditor( DataEditorAttribute attribute, IShortStringHelper shortStringHelper, IJsonSerializer jsonSerializer, IIOHelper ioHelper) - : base(shortStringHelper, jsonSerializer, ioHelper, attribute) + : this( + attribute, + StaticServiceProvider.Instance.GetRequiredService<ILocalizedTextService>(), + shortStringHelper, + jsonSerializer, + ioHelper) { } @@ -65,4 +74,48 @@ public override object ToEditor(IProperty property, string? culture = null, stri " can only be used with string based property editors"); } } + + /// <summary> + /// A common length validator for both textbox and text area. + /// </summary> + internal class LengthValidator : IValueValidator + { + private readonly ILocalizedTextService _localizedTextService; + + public LengthValidator(ILocalizedTextService localizedTextService) + { + _localizedTextService = localizedTextService; + } + + public IEnumerable<ValidationResult> Validate(object? value, string? valueType, object? dataTypeConfiguration, + PropertyValidationContext validationContext) + { + int? maxCharacters = dataTypeConfiguration switch + { + TextAreaConfiguration areaConfiguration => areaConfiguration.MaxChars, + TextboxConfiguration textboxConfiguration => textboxConfiguration.MaxChars, + _ => null, + }; + + if (maxCharacters is null) + { + return []; + } + + if (value is string typedValue && typedValue.Length > maxCharacters) + { + return + [ + new ValidationResult( + _localizedTextService.Localize( + "validation", + "stringLengthExceeded", + [maxCharacters.ToString()]), + ["value'"]) + ]; + } + + return []; + } + } } diff --git a/src/Umbraco.Core/PropertyEditors/Validation/TypedJsonValidatorRunner.cs b/src/Umbraco.Core/PropertyEditors/Validation/TypedJsonValidatorRunner.cs index e49d754d8242..029c2bf11c42 100644 --- a/src/Umbraco.Core/PropertyEditors/Validation/TypedJsonValidatorRunner.cs +++ b/src/Umbraco.Core/PropertyEditors/Validation/TypedJsonValidatorRunner.cs @@ -20,13 +20,22 @@ public class TypedJsonValidatorRunner<TValue, TConfiguration> : IValueValidator private readonly IJsonSerializer _jsonSerializer; private readonly ITypedJsonValidator<TValue, TConfiguration>[] _validators; + /// <summary> + /// Initializes a new instance of the <see cref="TypedJsonValidatorRunner{TValue, TConfiguration}"/> class. + /// </summary> + /// <param name="jsonSerializer">The JSON serializer.</param> + /// <param name="validators">The collection of validators to run.</param> public TypedJsonValidatorRunner(IJsonSerializer jsonSerializer, params ITypedJsonValidator<TValue, TConfiguration>[] validators) { _jsonSerializer = jsonSerializer; _validators = validators; } - public IEnumerable<ValidationResult> Validate(object? value, string? valueType, object? dataTypeConfiguration, + /// <inheritdoc/> + public IEnumerable<ValidationResult> Validate( + object? value, + string? valueType, + object? dataTypeConfiguration, PropertyValidationContext validationContext) { var validationResults = new List<ValidationResult>(); @@ -36,7 +45,8 @@ public IEnumerable<ValidationResult> Validate(object? value, string? valueType, return validationResults; } - if (value is null || _jsonSerializer.TryDeserialize(value, out TValue? deserializedValue) is false) + TValue? deserializedValue = null; + if (value is not null && _jsonSerializer.TryDeserialize(value, out deserializedValue) is false) { return validationResults; } diff --git a/src/Umbraco.Core/PropertyEditors/Validation/ValidationHelper.cs b/src/Umbraco.Core/PropertyEditors/Validation/ValidationHelper.cs index afaba8fd5748..1c33b5fd1221 100644 --- a/src/Umbraco.Core/PropertyEditors/Validation/ValidationHelper.cs +++ b/src/Umbraco.Core/PropertyEditors/Validation/ValidationHelper.cs @@ -1,3 +1,5 @@ +using Umbraco.Cms.Core.Services.Navigation; + namespace Umbraco.Cms.Core.PropertyEditors.Validation; /// <summary> @@ -19,6 +21,56 @@ public static bool IsValueValidForStep(decimal value, decimal min, decimal step) return true; // Outside of the range, so we expect another validator will have picked this up. } + if (step == 0) + { + return true; // A step of zero would trigger a divide by zero error in evaluating. So we always pass validation for zero, as effectively any step value is valid. + } + return (value - min) % step == 0; } + + /// <summary> + /// Checks if all provided entities has the start node as an ancestor. + /// </summary> + /// <param name="entityKeys">Keys to check.</param> + /// <param name="startNode">The configured start node.</param> + /// <param name="navigationQueryService">The navigation query service to use for the checks.</param> + /// <returns>True if the startnode key is in the ancestry tree.</returns> + public static bool HasValidStartNode(IEnumerable<Guid> entityKeys, Guid startNode, INavigationQueryService navigationQueryService) + { + List<Guid> uniqueParentKeys = []; + foreach (Guid distinctMediaKey in entityKeys.Distinct()) + { + if (navigationQueryService.TryGetParentKey(distinctMediaKey, out Guid? parentKey) is false) + { + continue; + } + + // If there is a start node, the media must have a parent. + if (parentKey is null) + { + return false; + } + + uniqueParentKeys.Add(parentKey.Value); + } + + IEnumerable<Guid> parentKeysNotInStartNode = uniqueParentKeys.Where(x => x != startNode); + foreach (Guid parentKey in parentKeysNotInStartNode) + { + if (navigationQueryService.TryGetAncestorsKeys(parentKey, out IEnumerable<Guid> foundAncestorKeys) is false) + { + // We couldn't find the parent node, so we fail. + return false; + } + + Guid[] ancestorKeys = foundAncestorKeys.ToArray(); + if (ancestorKeys.Length == 0 || ancestorKeys.Contains(startNode) is false) + { + return false; + } + } + + return true; + } } diff --git a/src/Umbraco.Core/PropertyEditors/Validators/DictionaryConfigurationValidatorBase.cs b/src/Umbraco.Core/PropertyEditors/Validators/DictionaryConfigurationValidatorBase.cs index 0ed0af57e26b..ce9dc4dff03f 100644 --- a/src/Umbraco.Core/PropertyEditors/Validators/DictionaryConfigurationValidatorBase.cs +++ b/src/Umbraco.Core/PropertyEditors/Validators/DictionaryConfigurationValidatorBase.cs @@ -21,7 +21,7 @@ protected static bool TryGetConfiguredValue<TValue>(object? dataTypeConfiguratio return false; } - if (configuration.TryGetValue(key, out object? obj) && obj is TValue castValue) + if (configuration.TryGetValue(key, out object? obj) && TryCastValue(obj, out TValue? castValue)) { value = castValue; return true; @@ -30,4 +30,24 @@ protected static bool TryGetConfiguredValue<TValue>(object? dataTypeConfiguratio value = default; return false; } + + private static bool TryCastValue<TValue>(object? value, [NotNullWhen(true)] out TValue? castValue) + { + if (value is TValue valueAsType) + { + castValue = valueAsType; + return true; + } + + // Special case for floating point numbers - when deserialized these will be integers if whole numbers rather + // than double. + if (typeof(TValue) == typeof(double) && value is int valueAsInt) + { + castValue = (TValue)(object)Convert.ToDouble(valueAsInt); + return true; + } + + castValue = default; + return false; + } } diff --git a/src/Umbraco.Core/PropertyEditors/Validators/MultipleValueValidator.cs b/src/Umbraco.Core/PropertyEditors/Validators/MultipleValueValidator.cs index 193937b9f6d3..ed1d11ad1867 100644 --- a/src/Umbraco.Core/PropertyEditors/Validators/MultipleValueValidator.cs +++ b/src/Umbraco.Core/PropertyEditors/Validators/MultipleValueValidator.cs @@ -1,11 +1,34 @@ -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; +using Microsoft.Extensions.DependencyInjection; +using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Models.Validation; +using Umbraco.Cms.Core.Services; using Umbraco.Extensions; namespace Umbraco.Cms.Core.PropertyEditors.Validators; +/// <summary> +/// A value validator for property editors that handle multiple values from a configured list of options. +/// </summary> public class MultipleValueValidator : IValueValidator { + private readonly ILocalizedTextService _localizedTextService; + + /// <summary> + /// Initializes a new instance of the <see cref="MultipleValueValidator"/> class. + /// </summary> + [Obsolete("Please use the constructor that takes all parameters. Scheduled for removal in Umbraco 17.")] + public MultipleValueValidator() + : this(StaticServiceProvider.Instance.GetRequiredService<ILocalizedTextService>()) + { + } + + /// <summary> + /// Initializes a new instance of the <see cref="MultipleValueValidator"/> class. + /// </summary> + public MultipleValueValidator(ILocalizedTextService localizedTextService) => _localizedTextService = localizedTextService; + + /// <inheritdoc/> public IEnumerable<ValidationResult> Validate(object? value, string? valueType, object? dataTypeConfiguration, PropertyValidationContext validationContext) { // don't validate if empty @@ -24,13 +47,21 @@ public IEnumerable<ValidationResult> Validate(object? value, string? valueType, yield break; } - foreach (var selectedValue in values) + var invalidValues = values + .Where(x => valueListConfiguration.Items.Contains(x) is false) + .ToList(); + + if (invalidValues.Count == 1) + { + yield return new ValidationResult( + _localizedTextService.Localize("validation", "notOneOfOptions", [invalidValues[0]]), + ["value"]); + } + else if (invalidValues.Count > 1) { - if (valueListConfiguration.Items.Contains(selectedValue) is false) - { - yield return new ValidationResult( - $"The value {selectedValue} is not a part of the pre-values", ["items"]); - } + yield return new ValidationResult( + _localizedTextService.Localize("validation", "multipleNotOneOfOptions", [string.Join(", ", invalidValues)]), + ["value"]); } } } diff --git a/src/Umbraco.Core/PublishedCache/IDatabaseCacheRebuilder.cs b/src/Umbraco.Core/PublishedCache/IDatabaseCacheRebuilder.cs index f6fd19eafca8..c4b7e2883a06 100644 --- a/src/Umbraco.Core/PublishedCache/IDatabaseCacheRebuilder.cs +++ b/src/Umbraco.Core/PublishedCache/IDatabaseCacheRebuilder.cs @@ -1,8 +1,33 @@ -namespace Umbraco.Cms.Core.PublishedCache; +namespace Umbraco.Cms.Core.PublishedCache; +/// <summary> +/// Defines operations for rebuild of the published content cache in the database. +/// </summary> public interface IDatabaseCacheRebuilder { + /// <summary> + /// Indicates if the database cache is in the process of being rebuilt. + /// </summary> + /// <returns></returns> + bool IsRebuilding() => false; + + /// <summary> + /// Rebuilds the database cache. + /// </summary> + [Obsolete("Use the overload with the useBackgroundThread parameter. Scheduled for removal in Umbraco 17.")] void Rebuild(); + /// <summary> + /// Rebuilds the database cache, optionally using a background thread. + /// </summary> + /// <param name="useBackgroundThread">Flag indicating whether to use a background thread for the operation and immediately return to the caller.</param> + void Rebuild(bool useBackgroundThread) +#pragma warning disable CS0618 // Type or member is obsolete + => Rebuild(); +#pragma warning restore CS0618 // Type or member is obsolete + + /// <summary> + /// Rebuids the database cache if the configured serializer has changed. + /// </summary> void RebuildDatabaseCacheIfSerializerChanged(); } diff --git a/src/Umbraco.Core/Routing/UmbracoRequestPaths.cs b/src/Umbraco.Core/Routing/UmbracoRequestPaths.cs index a5083a77c974..ee5fa98f7481 100644 --- a/src/Umbraco.Core/Routing/UmbracoRequestPaths.cs +++ b/src/Umbraco.Core/Routing/UmbracoRequestPaths.cs @@ -10,30 +10,27 @@ namespace Umbraco.Cms.Core.Routing; /// </summary> public class UmbracoRequestPaths { - private readonly string _apiMvcPath; private readonly string _appPath; - private readonly string _backOfficeMvcPath; private readonly string _backOfficePath; - private readonly string _managementApiPath; private readonly string _defaultUmbPath; private readonly string _defaultUmbPathWithSlash; - private readonly string _installPath; + private readonly string _backOfficeMvcPath; private readonly string _previewMvcPath; private readonly string _surfaceMvcPath; + private readonly string _apiMvcPath; + private readonly string _managementApiPath; + private readonly string _installPath; private readonly IOptions<UmbracoRequestPathsOptions> _umbracoRequestPathsOptions; /// <summary> - /// Initializes a new instance of the <see cref="UmbracoRequestPaths" /> class. + /// Initializes a new instance of the <see cref="UmbracoRequestPaths" /> class. /// </summary> - public UmbracoRequestPaths(IOptions<GlobalSettings> globalSettings, IHostingEnvironment hostingEnvironment, IOptions<UmbracoRequestPathsOptions> umbracoRequestPathsOptions) + public UmbracoRequestPaths(IHostingEnvironment hostingEnvironment, IOptions<UmbracoRequestPathsOptions> umbracoRequestPathsOptions) { _appPath = hostingEnvironment.ApplicationVirtualPath; + _backOfficePath = hostingEnvironment.GetBackOfficePath().EnsureStartsWith('/').TrimStart(_appPath).EnsureStartsWith('/'); - _backOfficePath = globalSettings.Value.GetBackOfficePath(hostingEnvironment) - .EnsureStartsWith('/').TrimStart(_appPath).EnsureStartsWith('/'); - - string mvcArea = globalSettings.Value.GetUmbracoMvcArea(hostingEnvironment); - + const string mvcArea = Constants.System.UmbracoPathSegment; _defaultUmbPath = "/" + mvcArea; _defaultUmbPathWithSlash = "/" + mvcArea + "/"; _backOfficeMvcPath = "/" + mvcArea + "/BackOffice/"; @@ -42,6 +39,7 @@ public UmbracoRequestPaths(IOptions<GlobalSettings> globalSettings, IHostingEnvi _apiMvcPath = "/" + mvcArea + "/Api/"; _managementApiPath = "/" + mvcArea + Constants.Web.ManagementApiPath; _installPath = hostingEnvironment.ToAbsolute(Constants.SystemDirectories.Install); + _umbracoRequestPathsOptions = umbracoRequestPathsOptions; } diff --git a/src/Umbraco.Core/Security/Authorization/FeatureAuthorizer.cs b/src/Umbraco.Core/Security/Authorization/FeatureAuthorizer.cs index 1d238070fb97..a15cbf501664 100644 --- a/src/Umbraco.Core/Security/Authorization/FeatureAuthorizer.cs +++ b/src/Umbraco.Core/Security/Authorization/FeatureAuthorizer.cs @@ -10,6 +10,6 @@ internal sealed class FeatureAuthorizer : IFeatureAuthorizer public FeatureAuthorizer(UmbracoFeatures umbracoFeatures) => _umbracoFeatures = umbracoFeatures; /// <inheritdoc /> - public async Task<bool> IsDeniedAsync(Type type) => - await Task.FromResult(_umbracoFeatures.IsControllerEnabled(type) is false); + public Task<bool> IsDeniedAsync(Type type) + => Task.FromResult(_umbracoFeatures.IsControllerEnabled(type) is false); } diff --git a/src/Umbraco.Core/Services/AuditService.cs b/src/Umbraco.Core/Services/AuditService.cs index 737314201324..855e3261cd2a 100644 --- a/src/Umbraco.Core/Services/AuditService.cs +++ b/src/Umbraco.Core/Services/AuditService.cs @@ -236,7 +236,7 @@ public IEnumerable<IAuditItem> GetPagedItemsByUser( } } - public async Task<PagedModel<IAuditItem>> GetItemsByKeyAsync( + public Task<PagedModel<IAuditItem>> GetItemsByKeyAsync( Guid entityKey, UmbracoObjectTypes entityType, int skip, @@ -258,7 +258,7 @@ public async Task<PagedModel<IAuditItem>> GetItemsByKeyAsync( Attempt<int> keyToIdAttempt = _entityService.GetId(entityKey, entityType); if (keyToIdAttempt.Success is false) { - return await Task.FromResult(new PagedModel<IAuditItem> { Items = Enumerable.Empty<IAuditItem>(), Total = 0 }); + return Task.FromResult(new PagedModel<IAuditItem> { Items = Enumerable.Empty<IAuditItem>(), Total = 0 }); } using (ScopeProvider.CreateCoreScope(autoComplete: true)) @@ -268,7 +268,7 @@ public async Task<PagedModel<IAuditItem>> GetItemsByKeyAsync( PaginationHelper.ConvertSkipTakeToPaging(skip, take, out var pageNumber, out var pageSize); IEnumerable<IAuditItem> auditItems = _auditRepository.GetPagedResultsByQuery(query, pageNumber, pageSize, out var totalRecords, orderDirection, auditTypeFilter, customFilter); - return new PagedModel<IAuditItem> { Items = auditItems, Total = totalRecords }; + return Task.FromResult(new PagedModel<IAuditItem> { Items = auditItems, Total = totalRecords }); } } @@ -294,7 +294,7 @@ public async Task<PagedModel<IAuditItem>> GetPagedItemsByUserAsync( if (user is null) { - return await Task.FromResult(new PagedModel<IAuditItem>()); + return new PagedModel<IAuditItem>(); } using (ScopeProvider.CreateCoreScope(autoComplete: true)) @@ -304,7 +304,7 @@ public async Task<PagedModel<IAuditItem>> GetPagedItemsByUserAsync( PaginationHelper.ConvertSkipTakeToPaging(skip, take, out var pageNumber, out var pageSize); IEnumerable<IAuditItem> auditItems = _auditRepository.GetPagedResultsByQuery(query, pageNumber, pageSize, out var totalRecords, orderDirection, auditTypeFilter, customFilter); - return await Task.FromResult(new PagedModel<IAuditItem> { Items = auditItems, Total = totalRecords }); + return new PagedModel<IAuditItem> { Items = auditItems, Total = totalRecords }; } } diff --git a/src/Umbraco.Core/Services/ContentBlueprintEditingService.cs b/src/Umbraco.Core/Services/ContentBlueprintEditingService.cs index 44fdc4bacab5..b33d19a4d6a9 100644 --- a/src/Umbraco.Core/Services/ContentBlueprintEditingService.cs +++ b/src/Umbraco.Core/Services/ContentBlueprintEditingService.cs @@ -29,10 +29,10 @@ public ContentBlueprintEditingService( : base(contentService, contentTypeService, propertyEditorCollection, dataTypeService, logger, scopeProvider, userIdKeyResolver, validationService, optionsMonitor, relationService) => _containerService = containerService; - public async Task<IContent?> GetAsync(Guid key) + public Task<IContent?> GetAsync(Guid key) { IContent? blueprint = ContentService.GetBlueprintById(key); - return await Task.FromResult(blueprint); + return Task.FromResult(blueprint); } public async Task<Attempt<PagedModel<IContent>?, ContentEditingOperationStatus>> GetPagedByContentTypeAsync(Guid contentTypeKey, int skip, int take) diff --git a/src/Umbraco.Core/Services/ContentEditingService.cs b/src/Umbraco.Core/Services/ContentEditingService.cs index 1dc54426d000..08af77d3e7ed 100644 --- a/src/Umbraco.Core/Services/ContentEditingService.cs +++ b/src/Umbraco.Core/Services/ContentEditingService.cs @@ -1,4 +1,4 @@ -using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Models; @@ -58,10 +58,10 @@ public ContentEditingService( _languageService = languageService; } - public async Task<IContent?> GetAsync(Guid key) + public Task<IContent?> GetAsync(Guid key) { IContent? content = ContentService.GetById(key); - return await Task.FromResult(content); + return Task.FromResult(content); } [Obsolete("Please use the validate update method that is not obsoleted. Will be removed in V16.")] diff --git a/src/Umbraco.Core/Services/ContentEditingServiceBase.cs b/src/Umbraco.Core/Services/ContentEditingServiceBase.cs index 63232f72624b..3c038cd33b2f 100644 --- a/src/Umbraco.Core/Services/ContentEditingServiceBase.cs +++ b/src/Umbraco.Core/Services/ContentEditingServiceBase.cs @@ -1,4 +1,4 @@ -using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Models; @@ -185,7 +185,7 @@ private async Task<Attempt<ContentValidationResult, ContentEditingOperationStatu TContent? content = ContentService.GetById(key); if (content == null) { - return await Task.FromResult(Attempt.FailWithStatus(ContentEditingOperationStatus.NotFound, content)); + return Attempt.FailWithStatus(ContentEditingOperationStatus.NotFound, content); } // checking the trash status is not done when it is irrelevant @@ -195,7 +195,7 @@ private async Task<Attempt<ContentValidationResult, ContentEditingOperationStatu ContentEditingOperationStatus status = trashStatusRequirement is ContentTrashStatusRequirement.MustBeTrashed ? ContentEditingOperationStatus.NotInTrash : ContentEditingOperationStatus.InTrash; - return await Task.FromResult(Attempt.FailWithStatus<TContent?, ContentEditingOperationStatus>(status, content)); + return Attempt.FailWithStatus<TContent?, ContentEditingOperationStatus>(status, content); } if (disabledWhenReferenced && _relationService.IsRelated(content.Id)) @@ -217,12 +217,12 @@ private async Task<Attempt<ContentValidationResult, ContentEditingOperationStatu TContent? content = ContentService.GetById(key); if (content is null) { - return await Task.FromResult(Attempt.FailWithStatus(ContentEditingOperationStatus.NotFound, content)); + return Attempt.FailWithStatus(ContentEditingOperationStatus.NotFound, content); } if (mustBeInRecycleBin && content.Trashed is false) { - return await Task.FromResult(Attempt.FailWithStatus<TContent?, ContentEditingOperationStatus>(ContentEditingOperationStatus.NotInTrash, content)); + return Attempt.FailWithStatus<TContent?, ContentEditingOperationStatus>(ContentEditingOperationStatus.NotInTrash, content); } TContentType contentType = ContentTypeService.Get(content.ContentType.Key)!; @@ -265,7 +265,7 @@ private async Task<Attempt<ContentValidationResult, ContentEditingOperationStatu TContent? content = ContentService.GetById(key); if (content is null) { - return await Task.FromResult(Attempt.FailWithStatus(ContentEditingOperationStatus.NotFound, content)); + return Attempt.FailWithStatus(ContentEditingOperationStatus.NotFound, content); } TContentType contentType = ContentTypeService.Get(content.ContentType.Key)!; @@ -370,7 +370,7 @@ protected ContentEditingOperationStatus OperationResultToOperationStatus(Operati return contentType; } - protected virtual async Task<(int? ParentId, ContentEditingOperationStatus OperationStatus)> TryGetAndValidateParentIdAsync(Guid? parentKey, TContentType contentType) + protected virtual Task<(int? ParentId, ContentEditingOperationStatus OperationStatus)> TryGetAndValidateParentIdAsync(Guid? parentKey, TContentType contentType) { TContent? parent = parentKey.HasValue ? ContentService.GetById(parentKey.Value) @@ -378,19 +378,19 @@ protected ContentEditingOperationStatus OperationResultToOperationStatus(Operati if (parentKey.HasValue && parent == null) { - return await Task.FromResult<(int? ParentId, ContentEditingOperationStatus OperationStatus)>((null, ContentEditingOperationStatus.ParentNotFound)); + return Task.FromResult<(int?, ContentEditingOperationStatus)>((null, ContentEditingOperationStatus.ParentNotFound)); } if (parent == null && contentType.AllowedAsRoot == false) { - return (null, ContentEditingOperationStatus.NotAllowed); + return Task.FromResult<(int?, ContentEditingOperationStatus)>((null, ContentEditingOperationStatus.NotAllowed)); } if (parent != null) { if (parent.Trashed) { - return (null, ContentEditingOperationStatus.InTrash); + return Task.FromResult<(int?, ContentEditingOperationStatus)>((null, ContentEditingOperationStatus.InTrash)); } TContentType? parentContentType = ContentTypeService.Get(parent.ContentType.Key); @@ -399,11 +399,11 @@ protected ContentEditingOperationStatus OperationResultToOperationStatus(Operati if (allowedContentTypeKeys.Contains(contentType.Key) == false) { - return (null, ContentEditingOperationStatus.NotAllowed); + return Task.FromResult<(int?, ContentEditingOperationStatus)>((null, ContentEditingOperationStatus.NotAllowed)); } } - return (parent?.Id ?? Constants.System.Root, ContentEditingOperationStatus.Success); + return Task.FromResult<(int?, ContentEditingOperationStatus)>((parent?.Id ?? Constants.System.Root, ContentEditingOperationStatus.Success)); } private void UpdateNames(ContentEditingModelBase contentEditingModelBase, TContent content, TContentType contentType) diff --git a/src/Umbraco.Core/Services/ContentEditingServiceWithSortingBase.cs b/src/Umbraco.Core/Services/ContentEditingServiceWithSortingBase.cs index 47335566bd46..530f56e19cee 100644 --- a/src/Umbraco.Core/Services/ContentEditingServiceWithSortingBase.cs +++ b/src/Umbraco.Core/Services/ContentEditingServiceWithSortingBase.cs @@ -1,4 +1,4 @@ -using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Models; @@ -62,7 +62,7 @@ protected async Task<ContentEditingOperationStatus> HandleSortAsync( if (contentId.HasValue is false) { - return await Task.FromResult(ContentEditingOperationStatus.NotFound); + return ContentEditingOperationStatus.NotFound; } const int pageSize = 500; diff --git a/src/Umbraco.Core/Services/ContentPermissionService.cs b/src/Umbraco.Core/Services/ContentPermissionService.cs index e8b2d45fcb2a..71ac02d62c18 100644 --- a/src/Umbraco.Core/Services/ContentPermissionService.cs +++ b/src/Umbraco.Core/Services/ContentPermissionService.cs @@ -32,7 +32,7 @@ public ContentPermissionService( } /// <inheritdoc/> - public async Task<ContentAuthorizationStatus> AuthorizeAccessAsync( + public Task<ContentAuthorizationStatus> AuthorizeAccessAsync( IUser user, IEnumerable<Guid> contentKeys, ISet<string> permissionsToCheck) @@ -41,21 +41,21 @@ public async Task<ContentAuthorizationStatus> AuthorizeAccessAsync( if (contentItems.Length == 0) { - return ContentAuthorizationStatus.NotFound; + return Task.FromResult(ContentAuthorizationStatus.NotFound); } if (contentItems.Any(contentItem => user.HasPathAccess(contentItem, _entityService, _appCaches) == false)) { - return ContentAuthorizationStatus.UnauthorizedMissingPathAccess; + return Task.FromResult(ContentAuthorizationStatus.UnauthorizedMissingPathAccess); } - return HasPermissionAccess(user, contentItems.Select(c => c.Path), permissionsToCheck) + return Task.FromResult(HasPermissionAccess(user, contentItems.Select(c => c.Path), permissionsToCheck) ? ContentAuthorizationStatus.Success - : ContentAuthorizationStatus.UnauthorizedMissingPermissionAccess; + : ContentAuthorizationStatus.UnauthorizedMissingPermissionAccess); } /// <inheritdoc/> - public async Task<ContentAuthorizationStatus> AuthorizeDescendantsAccessAsync( + public Task<ContentAuthorizationStatus> AuthorizeDescendantsAccessAsync( IUser user, Guid parentKey, ISet<string> permissionsToCheck) @@ -69,7 +69,7 @@ public async Task<ContentAuthorizationStatus> AuthorizeDescendantsAccessAsync( if (contentItem is null) { - return ContentAuthorizationStatus.NotFound; + return Task.FromResult(ContentAuthorizationStatus.NotFound); } while (page * pageSize < total) @@ -97,41 +97,41 @@ public async Task<ContentAuthorizationStatus> AuthorizeDescendantsAccessAsync( } } - return denied.Count == 0 + return Task.FromResult(denied.Count == 0 ? ContentAuthorizationStatus.Success - : ContentAuthorizationStatus.UnauthorizedMissingDescendantAccess; + : ContentAuthorizationStatus.UnauthorizedMissingDescendantAccess); } /// <inheritdoc/> - public async Task<ContentAuthorizationStatus> AuthorizeRootAccessAsync(IUser user, ISet<string> permissionsToCheck) + public Task<ContentAuthorizationStatus> AuthorizeRootAccessAsync(IUser user, ISet<string> permissionsToCheck) { var hasAccess = user.HasContentRootAccess(_entityService, _appCaches); if (hasAccess == false) { - return ContentAuthorizationStatus.UnauthorizedMissingRootAccess; + return Task.FromResult(ContentAuthorizationStatus.UnauthorizedMissingRootAccess); } // In this case, we have to use the Root id as path (i.e. -1) since we don't have a content item - return HasPermissionAccess(user, new[] { Constants.System.RootString }, permissionsToCheck) + return Task.FromResult(HasPermissionAccess(user, new[] { Constants.System.RootString }, permissionsToCheck) ? ContentAuthorizationStatus.Success - : ContentAuthorizationStatus.UnauthorizedMissingPermissionAccess; + : ContentAuthorizationStatus.UnauthorizedMissingPermissionAccess); } /// <inheritdoc/> - public async Task<ContentAuthorizationStatus> AuthorizeBinAccessAsync(IUser user, ISet<string> permissionsToCheck) + public Task<ContentAuthorizationStatus> AuthorizeBinAccessAsync(IUser user, ISet<string> permissionsToCheck) { var hasAccess = user.HasContentBinAccess(_entityService, _appCaches); if (hasAccess == false) { - return ContentAuthorizationStatus.UnauthorizedMissingBinAccess; + return Task.FromResult(ContentAuthorizationStatus.UnauthorizedMissingBinAccess); } // In this case, we have to use the Recycle Bin id as path (i.e. -20) since we don't have a content item - return HasPermissionAccess(user, new[] { Constants.System.RecycleBinContentString }, permissionsToCheck) + return Task.FromResult(HasPermissionAccess(user, new[] { Constants.System.RecycleBinContentString }, permissionsToCheck) ? ContentAuthorizationStatus.Success - : ContentAuthorizationStatus.UnauthorizedMissingPermissionAccess; + : ContentAuthorizationStatus.UnauthorizedMissingPermissionAccess); } /// <inheritdoc/> diff --git a/src/Umbraco.Core/Services/ContentTypeEditing/ContentTypeEditingService.cs b/src/Umbraco.Core/Services/ContentTypeEditing/ContentTypeEditingService.cs index e44b66b971b8..a63d566c3560 100644 --- a/src/Umbraco.Core/Services/ContentTypeEditing/ContentTypeEditingService.cs +++ b/src/Umbraco.Core/Services/ContentTypeEditing/ContentTypeEditingService.cs @@ -3,7 +3,6 @@ using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.ContentEditing; using Umbraco.Cms.Core.Models.ContentTypeEditing; -using Umbraco.Cms.Core.PropertyEditors; using Umbraco.Cms.Core.Services.OperationStatus; using Umbraco.Cms.Core.Strings; using Umbraco.Extensions; @@ -16,6 +15,7 @@ internal sealed class ContentTypeEditingService : ContentTypeEditingServiceBase< { private readonly ITemplateService _templateService; private readonly IElementSwitchValidator _elementSwitchValidator; + private readonly IReservedFieldNamesService _reservedFieldNamesService; private readonly IContentTypeService _contentTypeService; public ContentTypeEditingService( @@ -24,12 +24,33 @@ public ContentTypeEditingService( IDataTypeService dataTypeService, IEntityService entityService, IShortStringHelper shortStringHelper, - IElementSwitchValidator elementSwitchValidator) + IElementSwitchValidator elementSwitchValidator, + IReservedFieldNamesService reservedFieldNamesService) : base(contentTypeService, contentTypeService, dataTypeService, entityService, shortStringHelper) { _contentTypeService = contentTypeService; _templateService = templateService; _elementSwitchValidator = elementSwitchValidator; + _reservedFieldNamesService = reservedFieldNamesService; + } + + [Obsolete("Use the constructor that is not marked obsolete, will be removed in v17")] + public ContentTypeEditingService( + IContentTypeService contentTypeService, + ITemplateService templateService, + IDataTypeService dataTypeService, + IEntityService entityService, + IShortStringHelper shortStringHelper, + IElementSwitchValidator elementSwitchValidator) + : this( + contentTypeService, + templateService, + dataTypeService, + entityService, + shortStringHelper, + elementSwitchValidator, + StaticServiceProvider.Instance.GetRequiredService<IReservedFieldNamesService>()) + { } [Obsolete("Use the constructor that is not marked obsolete, will be removed in v16")] @@ -39,11 +60,15 @@ public ContentTypeEditingService( IDataTypeService dataTypeService, IEntityService entityService, IShortStringHelper shortStringHelper) - : base(contentTypeService, contentTypeService, dataTypeService, entityService, shortStringHelper) + : this( + contentTypeService, + templateService, + dataTypeService, + entityService, + shortStringHelper, + StaticServiceProvider.Instance.GetRequiredService<IElementSwitchValidator>(), + StaticServiceProvider.Instance.GetRequiredService<IReservedFieldNamesService>()) { - _contentTypeService = contentTypeService; - _templateService = templateService; - _elementSwitchValidator = StaticServiceProvider.Instance.GetRequiredService<IElementSwitchValidator>(); } public async Task<Attempt<IContentType?, ContentTypeOperationStatus>> CreateAsync(ContentTypeCreateModel model, Guid userKey) @@ -184,4 +209,6 @@ protected override IContentType CreateContentType(IShortStringHelper shortString protected override UmbracoObjectTypes ContentTypeObjectType => UmbracoObjectTypes.DocumentType; protected override UmbracoObjectTypes ContainerObjectType => UmbracoObjectTypes.DocumentTypeContainer; + + protected override ISet<string> GetReservedFieldNames() => _reservedFieldNamesService.GetDocumentReservedFieldNames(); } diff --git a/src/Umbraco.Core/Services/ContentTypeEditing/ContentTypeEditingServiceBase.cs b/src/Umbraco.Core/Services/ContentTypeEditing/ContentTypeEditingServiceBase.cs index 8e602a01f124..a86d9186e0b4 100644 --- a/src/Umbraco.Core/Services/ContentTypeEditing/ContentTypeEditingServiceBase.cs +++ b/src/Umbraco.Core/Services/ContentTypeEditing/ContentTypeEditingServiceBase.cs @@ -1,6 +1,5 @@ using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.ContentTypeEditing; -using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Cms.Core.Services.OperationStatus; using Umbraco.Cms.Core.Strings; using Umbraco.Extensions; @@ -18,7 +17,6 @@ internal abstract class ContentTypeEditingServiceBase<TContentType, TContentType private readonly IDataTypeService _dataTypeService; private readonly IEntityService _entityService; private readonly IShortStringHelper _shortStringHelper; - protected ContentTypeEditingServiceBase( IContentTypeService contentTypeService, TContentTypeService concreteContentTypeService, @@ -91,7 +89,12 @@ protected async Task<IEnumerable<ContentTypeAvailableCompositionsResult>> FindAv return Attempt.FailWithStatus<TContentType?, ContentTypeOperationStatus>(operationStatus, null); } - await AdditionalCreateValidationAsync(model); + // perform additional, content type specific validation + operationStatus = await AdditionalCreateValidationAsync(model); + if (operationStatus is not ContentTypeOperationStatus.Success) + { + return Attempt.FailWithStatus<TContentType?, ContentTypeOperationStatus>(operationStatus, null); + } // get the ID of the parent to create the content type under (we already validated that it exists) var parentId = GetParentId(model, containerKey) ?? throw new ArgumentException("Parent ID could not be found", nameof(model)); @@ -414,13 +417,13 @@ private bool IsReservedContentTypeAlias(string alias) return reservedAliases.InvariantContains(alias); } + protected abstract ISet<string> GetReservedFieldNames(); + private bool ContainsReservedPropertyTypeAlias(ContentTypeEditingModelBase<TPropertyTypeModel, TPropertyTypeContainer> model) { // Because of models builder you cannot have an alias that already exists in IPublishedContent, for instance Path. // Since MyModel.Path would conflict with IPublishedContent.Path. - var reservedPropertyTypeNames = typeof(IPublishedContent).GetPublicProperties().Select(x => x.Name) - .Union(typeof(IPublishedContent).GetPublicMethods().Select(x => x.Name)) - .ToArray(); + ISet<string> reservedPropertyTypeNames = GetReservedFieldNames(); return model.Properties.Any(propertyType => reservedPropertyTypeNames.InvariantContains(propertyType.Alias)); } diff --git a/src/Umbraco.Core/Services/ContentTypeEditing/MediaTypeEditingService.cs b/src/Umbraco.Core/Services/ContentTypeEditing/MediaTypeEditingService.cs index 11def4a3338b..069f72cb1284 100644 --- a/src/Umbraco.Core/Services/ContentTypeEditing/MediaTypeEditingService.cs +++ b/src/Umbraco.Core/Services/ContentTypeEditing/MediaTypeEditingService.cs @@ -1,4 +1,6 @@ -using Umbraco.Cms.Core.Media; +using Microsoft.Extensions.DependencyInjection; +using Umbraco.Cms.Core.DependencyInjection; +using Umbraco.Cms.Core.Media; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.ContentTypeEditing; using Umbraco.Cms.Core.PropertyEditors; @@ -13,7 +15,25 @@ internal sealed class MediaTypeEditingService : ContentTypeEditingServiceBase<IM private readonly IMediaTypeService _mediaTypeService; private readonly IDataTypeService _dataTypeService; private readonly IImageUrlGenerator _imageUrlGenerator; + private readonly IReservedFieldNamesService _reservedFieldNamesService; + public MediaTypeEditingService( + IContentTypeService contentTypeService, + IMediaTypeService mediaTypeService, + IDataTypeService dataTypeService, + IEntityService entityService, + IShortStringHelper shortStringHelper, + IImageUrlGenerator imageUrlGenerator, + IReservedFieldNamesService reservedFieldNamesService) + : base(contentTypeService, mediaTypeService, dataTypeService, entityService, shortStringHelper) + { + _mediaTypeService = mediaTypeService; + _dataTypeService = dataTypeService; + _imageUrlGenerator = imageUrlGenerator; + _reservedFieldNamesService = reservedFieldNamesService; + } + + [Obsolete("Use the non obsolete constructor instead. Scheduled for removal in v16")] public MediaTypeEditingService( IContentTypeService contentTypeService, IMediaTypeService mediaTypeService, @@ -26,6 +46,7 @@ public MediaTypeEditingService( _mediaTypeService = mediaTypeService; _dataTypeService = dataTypeService; _imageUrlGenerator = imageUrlGenerator; + _reservedFieldNamesService = StaticServiceProvider.Instance.GetRequiredService<IReservedFieldNamesService>(); } public async Task<Attempt<IMediaType?, ContentTypeOperationStatus>> CreateAsync(MediaTypeCreateModel model, Guid userKey) @@ -145,6 +166,8 @@ protected override IMediaType CreateContentType(IShortStringHelper shortStringHe protected override UmbracoObjectTypes ContainerObjectType => UmbracoObjectTypes.MediaTypeContainer; + protected override ISet<string> GetReservedFieldNames() => _reservedFieldNamesService.GetMediaReservedFieldNames(); + private async Task<IDictionary<IMediaType, IEnumerable<string>>> FetchAllowedFileExtensionsByMediaTypeAsync(IEnumerable<IMediaType> mediaTypes) { var allowedFileExtensionsByMediaType = new Dictionary<IMediaType, IEnumerable<string>>(); diff --git a/src/Umbraco.Core/Services/ContentTypeEditing/MemberTypeEditingService.cs b/src/Umbraco.Core/Services/ContentTypeEditing/MemberTypeEditingService.cs index 60707728af14..dd566b6a2b5d 100644 --- a/src/Umbraco.Core/Services/ContentTypeEditing/MemberTypeEditingService.cs +++ b/src/Umbraco.Core/Services/ContentTypeEditing/MemberTypeEditingService.cs @@ -1,4 +1,6 @@ -using Umbraco.Cms.Core.Models; +using Microsoft.Extensions.DependencyInjection; +using Umbraco.Cms.Core.DependencyInjection; +using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.ContentTypeEditing; using Umbraco.Cms.Core.Models.Membership; using Umbraco.Cms.Core.Services.OperationStatus; @@ -10,7 +12,24 @@ internal sealed class MemberTypeEditingService : ContentTypeEditingServiceBase<I { private readonly IMemberTypeService _memberTypeService; private readonly IUserService _userService; + private readonly IReservedFieldNamesService _reservedFieldNamesService; + public MemberTypeEditingService( + IContentTypeService contentTypeService, + IMemberTypeService memberTypeService, + IDataTypeService dataTypeService, + IEntityService entityService, + IShortStringHelper shortStringHelper, + IUserService userService, + IReservedFieldNamesService reservedFieldNamesService) + : base(contentTypeService, memberTypeService, dataTypeService, entityService, shortStringHelper) + { + _memberTypeService = memberTypeService; + _userService = userService; + _reservedFieldNamesService = reservedFieldNamesService; + } + + [Obsolete("Use the non obsolete constructor instead. Scheduled for removal in v16")] public MemberTypeEditingService( IContentTypeService contentTypeService, IMemberTypeService memberTypeService, @@ -22,6 +41,7 @@ public MemberTypeEditingService( { _memberTypeService = memberTypeService; _userService = userService; + _reservedFieldNamesService = StaticServiceProvider.Instance.GetRequiredService<IReservedFieldNamesService>(); } public async Task<Attempt<IMemberType?, ContentTypeOperationStatus>> CreateAsync(MemberTypeCreateModel model, Guid userKey) @@ -79,6 +99,8 @@ protected override IMemberType CreateContentType(IShortStringHelper shortStringH protected override UmbracoObjectTypes ContainerObjectType => throw new NotSupportedException("Member type tree does not support containers"); + protected override ISet<string> GetReservedFieldNames() => _reservedFieldNamesService.GetMemberReservedFieldNames(); + private void UpdatePropertyTypeVisibility(IMemberType memberType, MemberTypeModelBase model) { foreach (MemberTypePropertyTypeModel propertyType in model.Properties) diff --git a/src/Umbraco.Core/Services/ContentTypeServiceBaseOfTRepositoryTItemTService.cs b/src/Umbraco.Core/Services/ContentTypeServiceBaseOfTRepositoryTItemTService.cs index 46399f6b638d..dca44e5d7189 100644 --- a/src/Umbraco.Core/Services/ContentTypeServiceBaseOfTRepositoryTItemTService.cs +++ b/src/Umbraco.Core/Services/ContentTypeServiceBaseOfTRepositoryTItemTService.cs @@ -1183,8 +1183,13 @@ public async Task<PagedModel<TItem>> GetAllAllowedAsRootAsync(int skip, int take return pagedModel; } + /// <inheritdoc /> public async Task<Attempt<PagedModel<TItem>?, ContentTypeOperationStatus>> GetAllowedChildrenAsync(Guid key, int skip, int take) + => await GetAllowedChildrenAsync(key, null, skip, take); + + /// <inheritdoc /> + public async Task<Attempt<PagedModel<TItem>?, ContentTypeOperationStatus>> GetAllowedChildrenAsync(Guid key, Guid? parentContentKey, int skip, int take) { using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true); TItem? parent = Get(key); @@ -1197,7 +1202,7 @@ public async Task<PagedModel<TItem>> GetAllAllowedAsRootAsync(int skip, int take IEnumerable<ContentTypeSort> allowedContentTypes = parent.AllowedContentTypes; foreach (IContentTypeFilter filter in _contentTypeFilters) { - allowedContentTypes = await filter.FilterAllowedChildrenAsync(allowedContentTypes, key); + allowedContentTypes = await filter.FilterAllowedChildrenAsync(allowedContentTypes, key, parentContentKey); } PagedModel<TItem> result; diff --git a/src/Umbraco.Core/Services/ContentVersionService.cs b/src/Umbraco.Core/Services/ContentVersionService.cs index a0b64a764a30..0451409a4bda 100644 --- a/src/Umbraco.Core/Services/ContentVersionService.cs +++ b/src/Umbraco.Core/Services/ContentVersionService.cs @@ -112,46 +112,45 @@ public IReadOnlyCollection<ContentVersionMeta> PerformContentVersionCleanup(Date public void SetPreventCleanup(int versionId, bool preventCleanup, int userId = Constants.Security.SuperUserId) => HandleSetPreventCleanup(versionId, preventCleanup, userId); - public async Task<Attempt<PagedModel<ContentVersionMeta>?, ContentVersionOperationStatus>> GetPagedContentVersionsAsync(Guid contentId, string? culture, int skip, int take) + public Task<Attempt<PagedModel<ContentVersionMeta>?, ContentVersionOperationStatus>> GetPagedContentVersionsAsync(Guid contentId, string? culture, int skip, int take) { - IEntitySlim? document = await Task.FromResult(_entityService.Get(contentId, UmbracoObjectTypes.Document)); + IEntitySlim? document = _entityService.Get(contentId, UmbracoObjectTypes.Document); if (document is null) { - return Attempt<PagedModel<ContentVersionMeta>?, ContentVersionOperationStatus>.Fail(ContentVersionOperationStatus.ContentNotFound); + return Task.FromResult(Attempt<PagedModel<ContentVersionMeta>?, ContentVersionOperationStatus>.Fail(ContentVersionOperationStatus.ContentNotFound)); } if (PaginationConverter.ConvertSkipTakeToPaging(skip, take, out var pageNumber, out var pageSize) == false) { - return Attempt<PagedModel<ContentVersionMeta>?, ContentVersionOperationStatus>.Fail(ContentVersionOperationStatus.InvalidSkipTake); + return Task.FromResult(Attempt<PagedModel<ContentVersionMeta>?, ContentVersionOperationStatus>.Fail(ContentVersionOperationStatus.InvalidSkipTake)); } IEnumerable<ContentVersionMeta>? versions = - await Task.FromResult(HandleGetPagedContentVersions( + HandleGetPagedContentVersions( document.Id, pageNumber, pageSize, out var total, - culture)); + culture); if (versions is null) { - return Attempt<PagedModel<ContentVersionMeta>?, ContentVersionOperationStatus>.Fail(ContentVersionOperationStatus.NotFound); + return Task.FromResult(Attempt<PagedModel<ContentVersionMeta>?, ContentVersionOperationStatus>.Fail(ContentVersionOperationStatus.NotFound)); } - return Attempt<PagedModel<ContentVersionMeta>?, ContentVersionOperationStatus>.Succeed( - ContentVersionOperationStatus.Success, new PagedModel<ContentVersionMeta>(total, versions)); + return Task.FromResult(Attempt<PagedModel<ContentVersionMeta>?, ContentVersionOperationStatus>.Succeed( + ContentVersionOperationStatus.Success, new PagedModel<ContentVersionMeta>(total, versions))); } - public async Task<Attempt<IContent?, ContentVersionOperationStatus>> GetAsync(Guid versionId) + public Task<Attempt<IContent?, ContentVersionOperationStatus>> GetAsync(Guid versionId) { - IContent? version = await Task.FromResult(_contentService.GetVersion(versionId.ToInt())); + IContent? version = _contentService.GetVersion(versionId.ToInt()); if (version is null) { - return Attempt<IContent?, ContentVersionOperationStatus>.Fail(ContentVersionOperationStatus - .NotFound); + return Task.FromResult(Attempt<IContent?, ContentVersionOperationStatus>.Fail(ContentVersionOperationStatus.NotFound)); } - return Attempt<IContent?, ContentVersionOperationStatus>.Succeed(ContentVersionOperationStatus.Success, version); + return Task.FromResult(Attempt<IContent?, ContentVersionOperationStatus>.Succeed(ContentVersionOperationStatus.Success, version)); } public async Task<Attempt<ContentVersionOperationStatus>> SetPreventCleanupAsync(Guid versionId, bool preventCleanup, Guid userKey) diff --git a/src/Umbraco.Core/Services/DataTypeService.cs b/src/Umbraco.Core/Services/DataTypeService.cs index f22948968a56..60e28240a0b9 100644 --- a/src/Umbraco.Core/Services/DataTypeService.cs +++ b/src/Umbraco.Core/Services/DataTypeService.cs @@ -690,17 +690,17 @@ public IReadOnlyDictionary<Udi, IEnumerable<string>> GetReferences(int id) } /// <inheritdoc /> - public async Task<Attempt<IReadOnlyDictionary<Udi, IEnumerable<string>>, DataTypeOperationStatus>> GetReferencesAsync(Guid id) + public Task<Attempt<IReadOnlyDictionary<Udi, IEnumerable<string>>, DataTypeOperationStatus>> GetReferencesAsync(Guid id) { using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete:true); IDataType? dataType = GetDataTypeFromRepository(id); if (dataType == null) { - return Attempt.FailWithStatus<IReadOnlyDictionary<Udi, IEnumerable<string>>, DataTypeOperationStatus>(DataTypeOperationStatus.NotFound, new Dictionary<Udi, IEnumerable<string>>()); + return Task.FromResult(Attempt.FailWithStatus<IReadOnlyDictionary<Udi, IEnumerable<string>>, DataTypeOperationStatus>(DataTypeOperationStatus.NotFound, new Dictionary<Udi, IEnumerable<string>>())); } IReadOnlyDictionary<Udi, IEnumerable<string>> usages = _dataTypeRepository.FindUsages(dataType.Id); - return await Task.FromResult(Attempt.SucceedWithStatus(DataTypeOperationStatus.Success, usages)); + return Task.FromResult(Attempt.SucceedWithStatus(DataTypeOperationStatus.Success, usages)); } public IReadOnlyDictionary<Udi, IEnumerable<string>> GetListViewReferences(int id) diff --git a/src/Umbraco.Core/Services/DictionaryItemService.cs b/src/Umbraco.Core/Services/DictionaryItemService.cs index 38e37afedb4e..467f4ea26ac6 100644 --- a/src/Umbraco.Core/Services/DictionaryItemService.cs +++ b/src/Umbraco.Core/Services/DictionaryItemService.cs @@ -1,4 +1,4 @@ -using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Notifications; @@ -33,42 +33,42 @@ public DictionaryItemService( } /// <inheritdoc /> - public async Task<IDictionaryItem?> GetAsync(Guid id) + public Task<IDictionaryItem?> GetAsync(Guid id) { using (ScopeProvider.CreateCoreScope(autoComplete: true)) { IDictionaryItem? item = _dictionaryRepository.Get(id); - return await Task.FromResult(item); + return Task.FromResult(item); } } /// <inheritdoc /> - public async Task<IDictionaryItem?> GetAsync(string key) + public Task<IDictionaryItem?> GetAsync(string key) { using (ScopeProvider.CreateCoreScope(autoComplete: true)) { IDictionaryItem? item = _dictionaryRepository.Get(key); - return await Task.FromResult(item); + return Task.FromResult(item); } } /// <inheritdoc /> - public async Task<IEnumerable<IDictionaryItem>> GetManyAsync(params Guid[] ids) + public Task<IEnumerable<IDictionaryItem>> GetManyAsync(params Guid[] ids) { using (ScopeProvider.CreateCoreScope(autoComplete: true)) { IEnumerable<IDictionaryItem> items = _dictionaryRepository.GetMany(ids).ToArray(); - return await Task.FromResult(items); + return Task.FromResult(items); } } /// <inheritdoc /> - public async Task<IEnumerable<IDictionaryItem>> GetManyAsync(params string[] keys) + public Task<IEnumerable<IDictionaryItem>> GetManyAsync(params string[] keys) { using (ScopeProvider.CreateCoreScope(autoComplete: true)) { IEnumerable<IDictionaryItem> items = _dictionaryRepository.GetManyByKeys(keys).ToArray(); - return await Task.FromResult(items); + return Task.FromResult(items); } } @@ -106,12 +106,12 @@ public async Task<int> CountChildrenAsync(Guid parentId) => await CountByQueryAsync(Query<IDictionaryItem>().Where(x => x.ParentId == parentId)); /// <inheritdoc /> - public async Task<IEnumerable<IDictionaryItem>> GetDescendantsAsync(Guid? parentId, string? filter = null) + public Task<IEnumerable<IDictionaryItem>> GetDescendantsAsync(Guid? parentId, string? filter = null) { using (ScopeProvider.CreateCoreScope(autoComplete: true)) { IDictionaryItem[] items = _dictionaryRepository.GetDictionaryItemDescendants(parentId, filter).ToArray(); - return await Task.FromResult(items); + return Task.FromResult<IEnumerable<IDictionaryItem>>(items); } } @@ -123,12 +123,12 @@ public async Task<int> CountRootAsync() => await CountByQueryAsync(Query<IDictionaryItem>().Where(x => x.ParentId == null)); /// <inheritdoc/> - public async Task<bool> ExistsAsync(string key) + public Task<bool> ExistsAsync(string key) { using (ScopeProvider.CreateCoreScope(autoComplete: true)) { IDictionaryItem? item = _dictionaryRepository.Get(key); - return await Task.FromResult(item != null); + return Task.FromResult(item != null); } } @@ -203,7 +203,7 @@ public async Task<Attempt<IDictionaryItem, DictionaryItemOperationStatus>> Updat Audit(AuditType.Delete, "Delete DictionaryItem", currentUserId, dictionaryItem.Id, nameof(DictionaryItem)); scope.Complete(); - return await Task.FromResult(Attempt.SucceedWithStatus<IDictionaryItem?, DictionaryItemOperationStatus>(DictionaryItemOperationStatus.Success, dictionaryItem)); + return Attempt.SucceedWithStatus<IDictionaryItem?, DictionaryItemOperationStatus>(DictionaryItemOperationStatus.Success, dictionaryItem); } } @@ -265,16 +265,16 @@ public async Task<Attempt<IDictionaryItem, DictionaryItemOperationStatus>> MoveA Audit(AuditType.Move, "Move DictionaryItem", currentUserId, dictionaryItem.Id, nameof(DictionaryItem)); scope.Complete(); - return await Task.FromResult(Attempt.SucceedWithStatus(DictionaryItemOperationStatus.Success, dictionaryItem)); + return Attempt.SucceedWithStatus(DictionaryItemOperationStatus.Success, dictionaryItem); } } - private async Task<int> CountByQueryAsync(IQuery<IDictionaryItem> query) + private Task<int> CountByQueryAsync(IQuery<IDictionaryItem> query) { using (ScopeProvider.CreateCoreScope(autoComplete: true)) { var items = _dictionaryRepository.Count(query); - return await Task.FromResult(items); + return Task.FromResult(items); } } @@ -326,16 +326,16 @@ private async Task<Attempt<IDictionaryItem, DictionaryItemOperationStatus>> Save Audit(auditType, auditMessage, currentUserId, dictionaryItem.Id, nameof(DictionaryItem)); scope.Complete(); - return await Task.FromResult(Attempt.SucceedWithStatus(DictionaryItemOperationStatus.Success, dictionaryItem)); + return Attempt.SucceedWithStatus(DictionaryItemOperationStatus.Success, dictionaryItem); } } - private async Task<IEnumerable<IDictionaryItem>> GetByQueryAsync(IQuery<IDictionaryItem> query) + private Task<IEnumerable<IDictionaryItem>> GetByQueryAsync(IQuery<IDictionaryItem> query) { using (ScopeProvider.CreateCoreScope(autoComplete: true)) { IDictionaryItem[] items = _dictionaryRepository.Get(query).ToArray(); - return await Task.FromResult(items); + return Task.FromResult<IEnumerable<IDictionaryItem>>(items); } } diff --git a/src/Umbraco.Core/Services/DomainService.cs b/src/Umbraco.Core/Services/DomainService.cs index 58f776a45a56..16eb225aa5a8 100644 --- a/src/Umbraco.Core/Services/DomainService.cs +++ b/src/Umbraco.Core/Services/DomainService.cs @@ -159,23 +159,23 @@ public IEnumerable<IDomain> GetAssignedDomains(int contentId, bool includeWildca } /// <inheritdoc /> - public async Task<IEnumerable<IDomain>> GetAssignedDomainsAsync(Guid contentKey, bool includeWildcards) + public Task<IEnumerable<IDomain>> GetAssignedDomainsAsync(Guid contentKey, bool includeWildcards) { IContent? content = _contentService.GetById(contentKey); if (content == null) { - return await Task.FromResult(Enumerable.Empty<IDomain>()); + return Task.FromResult(Enumerable.Empty<IDomain>()); } using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true); - return _domainRepository.GetAssignedDomains(content.Id, includeWildcards); + return Task.FromResult(_domainRepository.GetAssignedDomains(content.Id, includeWildcards)); } /// <inheritdoc /> - public async Task<IEnumerable<IDomain>> GetAllAsync(bool includeWildcards) + public Task<IEnumerable<IDomain>> GetAllAsync(bool includeWildcards) { using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true); - return await Task.FromResult(_domainRepository.GetAll(includeWildcards)); + return Task.FromResult(_domainRepository.GetAll(includeWildcards)); } /// <inheritdoc /> diff --git a/src/Umbraco.Core/Services/EntityTypeContainerService.cs b/src/Umbraco.Core/Services/EntityTypeContainerService.cs index 962d853ce818..4a403e0c1fa6 100644 --- a/src/Umbraco.Core/Services/EntityTypeContainerService.cs +++ b/src/Umbraco.Core/Services/EntityTypeContainerService.cs @@ -1,4 +1,4 @@ -using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Entities; @@ -43,11 +43,11 @@ protected EntityTypeContainerService( } /// <inheritdoc /> - public async Task<EntityContainer?> GetAsync(Guid id) + public Task<EntityContainer?> GetAsync(Guid id) { using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true); ReadLock(scope); - return await Task.FromResult(_entityContainerRepository.Get(id)); + return Task.FromResult(_entityContainerRepository.Get(id)); } @@ -67,12 +67,12 @@ public async Task<IEnumerable<EntityContainer>> GetAllAsync() } /// <inheritdoc /> - public async Task<EntityContainer?> GetParentAsync(EntityContainer container) - => await Task.FromResult(GetParent(container)); + public Task<EntityContainer?> GetParentAsync(EntityContainer container) + => Task.FromResult(GetParent(container)); /// <inheritdoc /> - public async Task<EntityContainer?> GetParentAsync(TTreeEntity entity) - => await Task.FromResult(GetParent(entity)); + public Task<EntityContainer?> GetParentAsync(TTreeEntity entity) + => Task.FromResult(GetParent(entity)); /// <inheritdoc /> public async Task<Attempt<EntityContainer?, EntityContainerOperationStatus>> CreateAsync(Guid? key, string name, Guid? parentKey, Guid userKey) diff --git a/src/Umbraco.Core/Services/FileSystem/FolderServiceOperationBase.cs b/src/Umbraco.Core/Services/FileSystem/FolderServiceOperationBase.cs index 68523d14433f..95766a9a6faf 100644 --- a/src/Umbraco.Core/Services/FileSystem/FolderServiceOperationBase.cs +++ b/src/Umbraco.Core/Services/FileSystem/FolderServiceOperationBase.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models.FileSystem; +using Umbraco.Cms.Core.Models.FileSystem; using Umbraco.Cms.Core.Persistence.Repositories; using Umbraco.Cms.Core.Scoping; using Umbraco.Extensions; @@ -32,7 +32,7 @@ protected FolderServiceOperationBase(TRepository repository, ICoreScopeProvider protected abstract TOperationStatus InvalidName { get; } - protected async Task<Attempt<TFolderModel?, TOperationStatus>> HandleCreateAsync(string name, string? parentPath) + protected Task<Attempt<TFolderModel?, TOperationStatus>> HandleCreateAsync(string name, string? parentPath) { using ICoreScope scope = _scopeProvider.CreateCoreScope(); @@ -40,7 +40,7 @@ protected FolderServiceOperationBase(TRepository repository, ICoreScopeProvider TOperationStatus validateResult = ValidateCreate(name, path, parentPath); if (Success.Equals(validateResult) is false) { - return Attempt.FailWithStatus<TFolderModel?, TOperationStatus>(validateResult, null); + return Task.FromResult(Attempt.FailWithStatus<TFolderModel?, TOperationStatus>(validateResult, null)); } _repository.AddFolder(path); @@ -53,34 +53,34 @@ protected FolderServiceOperationBase(TRepository repository, ICoreScopeProvider Path = path, ParentPath = GetParentFolderPath(path) }; - return await Task.FromResult(Attempt.SucceedWithStatus<TFolderModel?, TOperationStatus>(Success, result)); + return Task.FromResult(Attempt.SucceedWithStatus<TFolderModel?, TOperationStatus>(Success, result)); } - protected async Task<TOperationStatus> HandleDeleteAsync(string path) + protected Task<TOperationStatus> HandleDeleteAsync(string path) { using ICoreScope scope = _scopeProvider.CreateCoreScope(); TOperationStatus validateResult = ValidateDelete(path); if (Success.Equals(validateResult) is false) { - return await Task.FromResult(validateResult); + return Task.FromResult(validateResult); } _repository.DeleteFolder(path); scope.Complete(); - return Success; + return Task.FromResult(Success); } - protected async Task<TFolderModel?> HandleGetAsync(string path) + protected Task<TFolderModel?> HandleGetAsync(string path) { using ICoreScope scope = _scopeProvider.CreateCoreScope(); // There's not much we can actually get when it's a folder, so it more a matter of ensuring the folder exists and returning a model. if (_repository.FolderExists(path) is false) { - return await Task.FromResult<TFolderModel?>(null); + return Task.FromResult<TFolderModel?>(null); } var result = new TFolderModel @@ -91,7 +91,8 @@ protected async Task<TOperationStatus> HandleDeleteAsync(string path) }; scope.Complete(); - return result; + + return Task.FromResult<TFolderModel?>(result); } private TOperationStatus ValidateCreate(string name, string path, string? parentPath) diff --git a/src/Umbraco.Core/Services/Filters/IContentTypeFilter.cs b/src/Umbraco.Core/Services/Filters/IContentTypeFilter.cs index e0f582723ca2..552714989c83 100644 --- a/src/Umbraco.Core/Services/Filters/IContentTypeFilter.cs +++ b/src/Umbraco.Core/Services/Filters/IContentTypeFilter.cs @@ -13,13 +13,28 @@ public interface IContentTypeFilter /// <param name="contentTypes">Retrieved collection of content types.</param> /// <returns>Filtered collection of content types.</returns> Task<IEnumerable<TItem>> FilterAllowedAtRootAsync<TItem>(IEnumerable<TItem> contentTypes) - where TItem : IContentTypeComposition; + where TItem : IContentTypeComposition + => Task.FromResult(contentTypes); /// <summary> /// Filters the content types retrieved for being allowed as children of a parent content type. /// </summary> /// <param name="contentTypes">Retrieved collection of content types.</param> - /// <param name="parentKey">The parent content type key.</param> + /// <param name="parentKey">The parent content key.</param> /// <returns>Filtered collection of content types.</returns> - Task<IEnumerable<ContentTypeSort>> FilterAllowedChildrenAsync(IEnumerable<ContentTypeSort> contentTypes, Guid parentKey); + [Obsolete("Please use the method overload taking all parameters. Scheduled for removal in Umbraco 17.")] + Task<IEnumerable<ContentTypeSort>> FilterAllowedChildrenAsync(IEnumerable<ContentTypeSort> contentTypes, Guid parentKey) + => Task.FromResult(contentTypes); + + /// <summary> + /// Filters the content types retrieved for being allowed as children of a parent content type. + /// </summary> + /// <param name="contentTypes">Retrieved collection of content types.</param> + /// <param name="parentContentTypeKey">The parent content type key.</param> + /// <param name="parentContentKey">The parent content key (provided to allow for custom filtering of the returned list of children based on the content context).</param> + /// <returns>Filtered collection of content types.</returns> + Task<IEnumerable<ContentTypeSort>> FilterAllowedChildrenAsync(IEnumerable<ContentTypeSort> contentTypes, Guid parentContentTypeKey, Guid? parentContentKey) +#pragma warning disable CS0618 // Type or member is obsolete + => FilterAllowedChildrenAsync(contentTypes, parentContentTypeKey); +#pragma warning restore CS0618 // Type or member is obsolete } diff --git a/src/Umbraco.Core/Services/IContentTypeServiceBase.cs b/src/Umbraco.Core/Services/IContentTypeServiceBase.cs index e8b2f044ce28..2bbcc692c89c 100644 --- a/src/Umbraco.Core/Services/IContentTypeServiceBase.cs +++ b/src/Umbraco.Core/Services/IContentTypeServiceBase.cs @@ -159,12 +159,20 @@ Task<Attempt<ContentTypeOperationStatus>> UpdateAsync(TItem item, Guid performin /// <summary> /// Returns all the content type allowed as root. /// </summary> - /// <returns></returns> Task<PagedModel<TItem>> GetAllAllowedAsRootAsync(int skip, int take); /// <summary> /// Returns all content types allowed as children for a given content type key. /// </summary> - /// <returns></returns> + /// <param name="key">The content type key.</param> Task<Attempt<PagedModel<TItem>?, ContentTypeOperationStatus>> GetAllowedChildrenAsync(Guid key, int skip, int take); + + /// <summary> + /// Returns all content types allowed as children for a given content type key. + /// </summary> + /// <param name="key">The content type key.</param> + /// <param name="parentContentKey">The parent content key.</param> + Task<Attempt<PagedModel<TItem>?, ContentTypeOperationStatus>> GetAllowedChildrenAsync(Guid key, Guid? parentContentKey, int skip, int take) + => GetAllowedChildrenAsync(key, skip, take); + } diff --git a/src/Umbraco.Core/Services/ITwoFactorLoginService.cs b/src/Umbraco.Core/Services/ITwoFactorLoginService.cs index 5b10221578b4..a5ad0d84e833 100644 --- a/src/Umbraco.Core/Services/ITwoFactorLoginService.cs +++ b/src/Umbraco.Core/Services/ITwoFactorLoginService.cs @@ -29,7 +29,7 @@ public interface ITwoFactorLoginService : IService /// The returned type can be anything depending on the setup providers. You will need to cast it to the type handled by /// the provider. /// </remarks> - [Obsolete("Use IUserTwoFactorLoginService.GetSetupInfoWithStatusAsync. This will be removed in Umbraco 15.")] + [Obsolete("Use IUserTwoFactorLoginService.GetSetupInfoAsync. This will be removed in Umbraco 15.")] Task<object?> GetSetupInfoAsync(Guid userOrMemberKey, string providerName); /// <summary> @@ -60,13 +60,13 @@ public interface ITwoFactorLoginService : IService /// <summary> /// Disables 2FA with Code. /// </summary> - [Obsolete("Use IUserTwoFactorLoginService.DisableByCodeWithStatusAsync. This will be removed in Umbraco 15.")] + [Obsolete("Use IUserTwoFactorLoginService.DisableByCodeAsync. This will be removed in Umbraco 15.")] Task<bool> DisableWithCodeAsync(string providerName, Guid userOrMemberKey, string code); /// <summary> /// Validates and Saves. /// </summary> - [Obsolete("Use IUserTwoFactorLoginService.ValidateAndSaveWithStatusAsync. This will be removed in Umbraco 15.")] + [Obsolete("Use IUserTwoFactorLoginService.ValidateAndSaveAsync. This will be removed in Umbraco 15.")] Task<bool> ValidateAndSaveAsync(string providerName, Guid userKey, string secret, string code); } diff --git a/src/Umbraco.Core/Services/LanguageService.cs b/src/Umbraco.Core/Services/LanguageService.cs index 343a76236729..01a249dbd0ed 100644 --- a/src/Umbraco.Core/Services/LanguageService.cs +++ b/src/Umbraco.Core/Services/LanguageService.cs @@ -1,4 +1,4 @@ -using System.Globalization; +using System.Globalization; using Microsoft.Extensions.Logging; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; @@ -34,46 +34,46 @@ public LanguageService( } /// <inheritdoc /> - public async Task<ILanguage?> GetAsync(string isoCode) + public Task<ILanguage?> GetAsync(string isoCode) { using (ScopeProvider.CreateCoreScope(autoComplete: true)) { - return await Task.FromResult(_languageRepository.GetByIsoCode(isoCode)); + return Task.FromResult(_languageRepository.GetByIsoCode(isoCode)); } } /// <inheritdoc /> - public async Task<ILanguage?> GetDefaultLanguageAsync() + public Task<ILanguage?> GetDefaultLanguageAsync() { using (ScopeProvider.CreateCoreScope(autoComplete: true)) { - return await Task.FromResult(_languageRepository.GetByIsoCode(_languageRepository.GetDefaultIsoCode())); + return Task.FromResult(_languageRepository.GetByIsoCode(_languageRepository.GetDefaultIsoCode())); } } /// <inheritdoc /> - public async Task<string> GetDefaultIsoCodeAsync() + public Task<string> GetDefaultIsoCodeAsync() { using (ScopeProvider.CreateCoreScope(autoComplete: true)) { - return await Task.FromResult(_languageRepository.GetDefaultIsoCode()); + return Task.FromResult(_languageRepository.GetDefaultIsoCode()); } } /// <inheritdoc /> - public async Task<IEnumerable<ILanguage>> GetAllAsync() + public Task<IEnumerable<ILanguage>> GetAllAsync() { using (ScopeProvider.CreateCoreScope(autoComplete: true)) { - return await Task.FromResult(_languageRepository.GetMany()); + return Task.FromResult(_languageRepository.GetMany()); } } - public async Task<string[]> GetIsoCodesByIdsAsync(ICollection<int> ids) + public Task<string[]> GetIsoCodesByIdsAsync(ICollection<int> ids) { using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete:true); - return await Task.FromResult(_languageRepository.GetIsoCodesByIds(ids, throwOnNotFound: true)); + return Task.FromResult(_languageRepository.GetIsoCodesByIds(ids, throwOnNotFound: true)); } public async Task<IEnumerable<ILanguage>> GetMultipleAsync(IEnumerable<string> isoCodes) => (await GetAllAsync()).Where(x => isoCodes.Contains(x.IsoCode)); @@ -163,7 +163,7 @@ public async Task<Attempt<ILanguage, LanguageOperationStatus>> CreateAsync(ILang var currentUserId = await _userIdKeyResolver.GetAsync(userKey); Audit(AuditType.Delete, "Delete Language", currentUserId, language.Id, UmbracoObjectTypes.Language.GetName()); scope.Complete(); - return await Task.FromResult(Attempt.SucceedWithStatus<ILanguage?, LanguageOperationStatus>(LanguageOperationStatus.Success, language)); + return Attempt.SucceedWithStatus<ILanguage?, LanguageOperationStatus>(LanguageOperationStatus.Success, language); } } @@ -217,7 +217,7 @@ private async Task<Attempt<ILanguage, LanguageOperationStatus>> SaveAsync( Audit(auditType, auditMessage, currentUserId, language.Id, UmbracoObjectTypes.Language.GetName()); scope.Complete(); - return await Task.FromResult(Attempt.SucceedWithStatus(LanguageOperationStatus.Success, language)); + return Attempt.SucceedWithStatus(LanguageOperationStatus.Success, language); } } diff --git a/src/Umbraco.Core/Services/LogViewerService.cs b/src/Umbraco.Core/Services/LogViewerService.cs index aa1b1478ad3c..0780838416a9 100644 --- a/src/Umbraco.Core/Services/LogViewerService.cs +++ b/src/Umbraco.Core/Services/LogViewerService.cs @@ -31,7 +31,7 @@ public LogViewerService( } /// <inheritdoc/> - public async Task<Attempt<PagedModel<ILogEntry>?, LogViewerOperationStatus>> GetPagedLogsAsync( + public Task<Attempt<PagedModel<ILogEntry>?, LogViewerOperationStatus>> GetPagedLogsAsync( DateTimeOffset? startDate, DateTimeOffset? endDate, int skip, @@ -45,33 +45,33 @@ public LogViewerService( // We will need to stop the request if trying to do this on a 1GB file if (CanViewLogs(logTimePeriod) == false) { - return Attempt.FailWithStatus<PagedModel<ILogEntry>?, LogViewerOperationStatus>( + return Task.FromResult(Attempt.FailWithStatus<PagedModel<ILogEntry>?, LogViewerOperationStatus>( LogViewerOperationStatus.CancelledByLogsSizeValidation, - null); + null)); } PagedModel<ILogEntry> filteredLogs = GetFilteredLogs(logTimePeriod, filterExpression, logLevels, orderDirection, skip, take); - return Attempt.SucceedWithStatus<PagedModel<ILogEntry>?, LogViewerOperationStatus>( + return Task.FromResult(Attempt.SucceedWithStatus<PagedModel<ILogEntry>?, LogViewerOperationStatus>( LogViewerOperationStatus.Success, - filteredLogs); + filteredLogs)); } /// <inheritdoc/> - public async Task<PagedModel<ILogViewerQuery>> GetSavedLogQueriesAsync(int skip, int take) + public Task<PagedModel<ILogViewerQuery>> GetSavedLogQueriesAsync(int skip, int take) { using ICoreScope scope = _provider.CreateCoreScope(autoComplete: true); ILogViewerQuery[] savedLogQueries = _logViewerQueryRepository.GetMany().ToArray(); var pagedModel = new PagedModel<ILogViewerQuery>(savedLogQueries.Length, savedLogQueries.Skip(skip).Take(take)); - return await Task.FromResult(pagedModel); + return Task.FromResult(pagedModel); } /// <inheritdoc/> - public async Task<ILogViewerQuery?> GetSavedLogQueryByNameAsync(string name) + public Task<ILogViewerQuery?> GetSavedLogQueryByNameAsync(string name) { using ICoreScope scope = _provider.CreateCoreScope(autoComplete: true); - return await Task.FromResult(_logViewerQueryRepository.GetByName(name)); + return Task.FromResult(_logViewerQueryRepository.GetByName(name)); } /// <inheritdoc/> @@ -109,57 +109,57 @@ public async Task<PagedModel<ILogViewerQuery>> GetSavedLogQueriesAsync(int skip, } /// <inheritdoc/> - public async Task<Attempt<bool, LogViewerOperationStatus>> CanViewLogsAsync(DateTimeOffset? startDate, DateTimeOffset? endDate) + public Task<Attempt<bool, LogViewerOperationStatus>> CanViewLogsAsync(DateTimeOffset? startDate, DateTimeOffset? endDate) { LogTimePeriod logTimePeriod = GetTimePeriod(startDate, endDate); bool isAllowed = CanViewLogs(logTimePeriod); if (isAllowed == false) { - return Attempt.FailWithStatus(LogViewerOperationStatus.CancelledByLogsSizeValidation, isAllowed); + return Task.FromResult(Attempt.FailWithStatus(LogViewerOperationStatus.CancelledByLogsSizeValidation, isAllowed)); } - return Attempt.SucceedWithStatus(LogViewerOperationStatus.Success, isAllowed); + return Task.FromResult(Attempt.SucceedWithStatus(LogViewerOperationStatus.Success, isAllowed)); } /// <inheritdoc/> - public async Task<Attempt<LogLevelCounts?, LogViewerOperationStatus>> GetLogLevelCountsAsync(DateTimeOffset? startDate, DateTimeOffset? endDate) + public Task<Attempt<LogLevelCounts?, LogViewerOperationStatus>> GetLogLevelCountsAsync(DateTimeOffset? startDate, DateTimeOffset? endDate) { LogTimePeriod logTimePeriod = GetTimePeriod(startDate, endDate); // We will need to stop the request if trying to do this on a 1GB file if (CanViewLogs(logTimePeriod) == false) { - return Attempt.FailWithStatus<LogLevelCounts?, LogViewerOperationStatus>( + return Task.FromResult(Attempt.FailWithStatus<LogLevelCounts?, LogViewerOperationStatus>( LogViewerOperationStatus.CancelledByLogsSizeValidation, - null); + null)); } LogLevelCounts counter = _logViewerRepository.GetLogCount(logTimePeriod); - return Attempt.SucceedWithStatus<LogLevelCounts?, LogViewerOperationStatus>( + return Task.FromResult(Attempt.SucceedWithStatus<LogLevelCounts?, LogViewerOperationStatus>( LogViewerOperationStatus.Success, - counter); + counter)); } /// <inheritdoc/> - public async Task<Attempt<PagedModel<LogTemplate>, LogViewerOperationStatus>> GetMessageTemplatesAsync(DateTimeOffset? startDate, DateTimeOffset? endDate, int skip, int take) + public Task<Attempt<PagedModel<LogTemplate>, LogViewerOperationStatus>> GetMessageTemplatesAsync(DateTimeOffset? startDate, DateTimeOffset? endDate, int skip, int take) { LogTimePeriod logTimePeriod = GetTimePeriod(startDate, endDate); // We will need to stop the request if trying to do this on a 1GB file if (CanViewLogs(logTimePeriod) == false) { - return Attempt.FailWithStatus<PagedModel<LogTemplate>, LogViewerOperationStatus>( + return Task.FromResult(Attempt.FailWithStatus<PagedModel<LogTemplate>, LogViewerOperationStatus>( LogViewerOperationStatus.CancelledByLogsSizeValidation, - null!); + null!)); } LogTemplate[] messageTemplates = _logViewerRepository.GetMessageTemplates(logTimePeriod); - return Attempt.SucceedWithStatus( + return Task.FromResult(Attempt.SucceedWithStatus( LogViewerOperationStatus.Success, - new PagedModel<LogTemplate>(messageTemplates.Length, messageTemplates.Skip(skip).Take(take))); + new PagedModel<LogTemplate>(messageTemplates.Length, messageTemplates.Skip(skip).Take(take)))); } /// <inheritdoc/> diff --git a/src/Umbraco.Core/Services/MediaEditingService.cs b/src/Umbraco.Core/Services/MediaEditingService.cs index 2a27c048a66f..fda3b04cf678 100644 --- a/src/Umbraco.Core/Services/MediaEditingService.cs +++ b/src/Umbraco.Core/Services/MediaEditingService.cs @@ -1,4 +1,4 @@ -using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Models; @@ -40,10 +40,10 @@ public MediaEditingService( relationService) => _logger = logger; - public async Task<IMedia?> GetAsync(Guid key) + public Task<IMedia?> GetAsync(Guid key) { IMedia? media = ContentService.GetById(key); - return await Task.FromResult(media); + return Task.FromResult(media); } public async Task<Attempt<ContentValidationResult, ContentEditingOperationStatus>> ValidateUpdateAsync(Guid key, MediaUpdateModel updateModel) diff --git a/src/Umbraco.Core/Services/MediaPermissionService.cs b/src/Umbraco.Core/Services/MediaPermissionService.cs index 54c2a4941d6b..28276c0f56c3 100644 --- a/src/Umbraco.Core/Services/MediaPermissionService.cs +++ b/src/Umbraco.Core/Services/MediaPermissionService.cs @@ -23,34 +23,34 @@ public MediaPermissionService( } /// <inheritdoc/> - public async Task<MediaAuthorizationStatus> AuthorizeAccessAsync(IUser user, IEnumerable<Guid> mediaKeys) + public Task<MediaAuthorizationStatus> AuthorizeAccessAsync(IUser user, IEnumerable<Guid> mediaKeys) { foreach (Guid mediaKey in mediaKeys) { IMedia? media = _mediaService.GetById(mediaKey); if (media is null) { - return MediaAuthorizationStatus.NotFound; + return Task.FromResult(MediaAuthorizationStatus.NotFound); } if (user.HasPathAccess(media, _entityService, _appCaches) == false) { - return MediaAuthorizationStatus.UnauthorizedMissingPathAccess; + return Task.FromResult(MediaAuthorizationStatus.UnauthorizedMissingPathAccess); } } - return MediaAuthorizationStatus.Success; + return Task.FromResult(MediaAuthorizationStatus.Success); } /// <inheritdoc/> - public async Task<MediaAuthorizationStatus> AuthorizeRootAccessAsync(IUser user) - => user.HasMediaRootAccess(_entityService, _appCaches) + public Task<MediaAuthorizationStatus> AuthorizeRootAccessAsync(IUser user) + => Task.FromResult(user.HasMediaRootAccess(_entityService, _appCaches) ? MediaAuthorizationStatus.Success - : MediaAuthorizationStatus.UnauthorizedMissingRootAccess; + : MediaAuthorizationStatus.UnauthorizedMissingRootAccess); /// <inheritdoc/> - public async Task<MediaAuthorizationStatus> AuthorizeBinAccessAsync(IUser user) - => user.HasMediaBinAccess(_entityService, _appCaches) + public Task<MediaAuthorizationStatus> AuthorizeBinAccessAsync(IUser user) + => Task.FromResult(user.HasMediaBinAccess(_entityService, _appCaches) ? MediaAuthorizationStatus.Success - : MediaAuthorizationStatus.UnauthorizedMissingBinAccess; + : MediaAuthorizationStatus.UnauthorizedMissingBinAccess); } diff --git a/src/Umbraco.Core/Services/NoopSegmentService.cs b/src/Umbraco.Core/Services/NoopSegmentService.cs index 90a3076db4c2..5b1f65029b15 100644 --- a/src/Umbraco.Core/Services/NoopSegmentService.cs +++ b/src/Umbraco.Core/Services/NoopSegmentService.cs @@ -5,9 +5,9 @@ namespace Umbraco.Cms.Core.Services; public class NoopSegmentService : ISegmentService { - public async Task<Attempt<PagedModel<Segment>?, SegmentOperationStatus>> GetPagedSegmentsAsync(int skip = 0, int take = 100) + public Task<Attempt<PagedModel<Segment>?, SegmentOperationStatus>> GetPagedSegmentsAsync(int skip = 0, int take = 100) { - return await Task.FromResult(Attempt.SucceedWithStatus<PagedModel<Segment>?, SegmentOperationStatus>( + return Task.FromResult(Attempt.SucceedWithStatus<PagedModel<Segment>?, SegmentOperationStatus>( SegmentOperationStatus.Success, new PagedModel<Segment> { Total = 0, Items = Enumerable.Empty<Segment>() })); } diff --git a/src/Umbraco.Core/Services/PartialViewService.cs b/src/Umbraco.Core/Services/PartialViewService.cs index d4bfb5dc6d00..9543f45f2892 100644 --- a/src/Umbraco.Core/Services/PartialViewService.cs +++ b/src/Umbraco.Core/Services/PartialViewService.cs @@ -1,4 +1,4 @@ -using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Notifications; @@ -62,7 +62,7 @@ protected override IPartialView CreateEntity(string path, string? content) => new PartialView(path) { Content = content }; /// <inheritdoc /> - public async Task<PagedModel<PartialViewSnippetSlim>> GetSnippetsAsync(int skip, int take) + public Task<PagedModel<PartialViewSnippetSlim>> GetSnippetsAsync(int skip, int take) { using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true); var result = new PagedModel<PartialViewSnippetSlim>( @@ -72,15 +72,15 @@ public async Task<PagedModel<PartialViewSnippetSlim>> GetSnippetsAsync(int skip, .Take(take) .Select(snippet => new PartialViewSnippetSlim(snippet.Id, snippet.Name)) .ToArray()); - return await Task.FromResult(result); + return Task.FromResult(result); } /// <inheritdoc /> - public async Task<PartialViewSnippet?> GetSnippetAsync(string id) + public Task<PartialViewSnippet?> GetSnippetAsync(string id) { using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true); PartialViewSnippet? snippet = _snippetCollection.FirstOrDefault(s => s.Id == id); - return await Task.FromResult(snippet); + return Task.FromResult(snippet); } /// <inheritdoc /> diff --git a/src/Umbraco.Core/Services/PropertyValidationService.cs b/src/Umbraco.Core/Services/PropertyValidationService.cs index fbb159e135db..1bed040ba1c5 100644 --- a/src/Umbraco.Core/Services/PropertyValidationService.cs +++ b/src/Umbraco.Core/Services/PropertyValidationService.cs @@ -1,4 +1,4 @@ -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.DependencyInjection; @@ -88,7 +88,7 @@ public IEnumerable<ValidationResult> ValidatePropertyValue( { // Retrieve default messages used for required and regex validatation. We'll replace these // if set with custom ones if they've been provided for a given property. - var requiredDefaultMessages = new[] { Constants.Validation.ErrorMessages.Properties.Missing }; + var requiredDefaultMessages = new[] { Constants.Validation.ErrorMessages.Properties.Missing, Constants.Validation.ErrorMessages.Properties.Empty }; var formatDefaultMessages = new[] { Constants.Validation.ErrorMessages.Properties.PatternMismatch }; IDataValueEditor valueEditor = _valueEditorCache.GetValueEditor(editor, dataType); diff --git a/src/Umbraco.Core/Services/RelationService.cs b/src/Umbraco.Core/Services/RelationService.cs index 6c54ee6e54a5..f912da7283cc 100644 --- a/src/Umbraco.Core/Services/RelationService.cs +++ b/src/Umbraco.Core/Services/RelationService.cs @@ -126,22 +126,22 @@ public IEnumerable<IRelationType> GetAllRelationTypes(params int[] ids) /// Gets the Relation types in a paged manner. /// Currently implements the paging in memory on the name property because the underlying repository does not support paging yet /// </summary> - public async Task<PagedModel<IRelationType>> GetPagedRelationTypesAsync(int skip, int take, params int[] ids) + public Task<PagedModel<IRelationType>> GetPagedRelationTypesAsync(int skip, int take, params int[] ids) { using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true); if (take == 0) { - return new PagedModel<IRelationType>(CountRelationTypes(), Enumerable.Empty<IRelationType>()); + return Task.FromResult(new PagedModel<IRelationType>(CountRelationTypes(), Enumerable.Empty<IRelationType>())); } - IRelationType[] items = await Task.FromResult(_relationTypeRepository.GetMany(ids).ToArray()); + IRelationType[] items = _relationTypeRepository.GetMany(ids).ToArray(); - return new PagedModel<IRelationType>( + return Task.FromResult(new PagedModel<IRelationType>( items.Length, items.OrderBy(relationType => relationType.Name) .Skip(skip) - .Take(take)); + .Take(take))); } public int CountRelationTypes() @@ -308,21 +308,21 @@ public async Task<PagedModel<IRelation>> GetPagedByChildKeyAsync(Guid childKey, } } - public async Task<Attempt<PagedModel<IRelation>, RelationOperationStatus>> GetPagedByRelationTypeKeyAsync(Guid key, int skip, int take, Ordering? ordering = null) + public Task<Attempt<PagedModel<IRelation>, RelationOperationStatus>> GetPagedByRelationTypeKeyAsync(Guid key, int skip, int take, Ordering? ordering = null) { using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { IRelationType? relationType = _relationTypeRepository.Get(key); if (relationType is null) { - return await Task.FromResult(Attempt.FailWithStatus<PagedModel<IRelation>, RelationOperationStatus>(RelationOperationStatus.RelationTypeNotFound, null!)); + return Task.FromResult(Attempt.FailWithStatus<PagedModel<IRelation>, RelationOperationStatus>(RelationOperationStatus.RelationTypeNotFound, null!)); } PaginationHelper.ConvertSkipTakeToPaging(skip, take, out var pageNumber, out var pageSize); IQuery<IRelation> query = Query<IRelation>().Where(x => x.RelationTypeId == relationType.Id); IEnumerable<IRelation> relations = _relationRepository.GetPagedRelationsByQuery(query, pageNumber, pageSize, out var totalRecords, ordering); - return await Task.FromResult(Attempt.SucceedWithStatus(RelationOperationStatus.Success, new PagedModel<IRelation>(totalRecords, relations))); + return Task.FromResult(Attempt.SucceedWithStatus(RelationOperationStatus.Success, new PagedModel<IRelation>(totalRecords, relations))); } } @@ -663,7 +663,7 @@ private async Task<Attempt<IRelationType, RelationTypeOperationStatus>> SaveAsyn new RelationTypeSavedNotification(relationType, eventMessages).WithStateFrom(savingNotification)); } - return await Task.FromResult(Attempt.SucceedWithStatus(RelationTypeOperationStatus.Success, relationType)); + return Attempt.SucceedWithStatus(RelationTypeOperationStatus.Success, relationType); } /// <inheritdoc /> @@ -729,7 +729,7 @@ public void Delete(IRelationType relationType) Audit(AuditType.Delete, currentUser, relationType.Id, "Deleted relation type"); scope.Notifications.Publish(new RelationTypeDeletedNotification(relationType, eventMessages).WithStateFrom(deletingNotification)); scope.Complete(); - return await Task.FromResult(Attempt.SucceedWithStatus<IRelationType?, RelationTypeOperationStatus>(RelationTypeOperationStatus.Success, relationType)); + return Attempt.SucceedWithStatus<IRelationType?, RelationTypeOperationStatus>(RelationTypeOperationStatus.Success, relationType); } } diff --git a/src/Umbraco.Core/Services/TemplateService.cs b/src/Umbraco.Core/Services/TemplateService.cs index a3531f02e7d1..80391c28b5fe 100644 --- a/src/Umbraco.Core/Services/TemplateService.cs +++ b/src/Umbraco.Core/Services/TemplateService.cs @@ -1,4 +1,4 @@ -using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Models; @@ -86,7 +86,7 @@ public async Task<Attempt<ITemplate, TemplateOperationStatus>> CreateForContentT scope.Complete(); } - return await Task.FromResult(Attempt.SucceedWithStatus(TemplateOperationStatus.Success, template)); + return Attempt.SucceedWithStatus(TemplateOperationStatus.Success, template); } /// <inheritdoc /> @@ -126,11 +126,11 @@ private TemplateOperationStatus ValidateCreate(ITemplate templateToCreate) } /// <inheritdoc /> - public async Task<IEnumerable<ITemplate>> GetAllAsync(params string[] aliases) + public Task<IEnumerable<ITemplate>> GetAllAsync(params string[] aliases) { using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { - return await Task.FromResult(_templateRepository.GetAll(aliases).OrderBy(x => x.Name)); + return Task.FromResult<IEnumerable<ITemplate>>(_templateRepository.GetAll(aliases).OrderBy(x => x.Name)); } } @@ -151,48 +151,48 @@ public Task<IEnumerable<ITemplate>> GetAllAsync(params Guid[] keys) } /// <inheritdoc /> - public async Task<IEnumerable<ITemplate>> GetChildrenAsync(int masterTemplateId) + public Task<IEnumerable<ITemplate>> GetChildrenAsync(int masterTemplateId) { using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { - return await Task.FromResult(_templateRepository.GetChildren(masterTemplateId).OrderBy(x => x.Name)); + return Task.FromResult<IEnumerable<ITemplate>>(_templateRepository.GetChildren(masterTemplateId).OrderBy(x => x.Name)); } } /// <inheritdoc /> - public async Task<ITemplate?> GetAsync(string? alias) + public Task<ITemplate?> GetAsync(string? alias) { using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { - return await Task.FromResult(_templateRepository.Get(alias)); + return Task.FromResult(_templateRepository.Get(alias)); } } /// <inheritdoc /> - public async Task<ITemplate?> GetAsync(int id) + public Task<ITemplate?> GetAsync(int id) { using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { - return await Task.FromResult(_templateRepository.Get(id)); + return Task.FromResult(_templateRepository.Get(id)); } } /// <inheritdoc /> - public async Task<ITemplate?> GetAsync(Guid id) + public Task<ITemplate?> GetAsync(Guid id) { using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { IQuery<ITemplate>? query = Query<ITemplate>().Where(x => x.Key == id); - return await Task.FromResult(_templateRepository.Get(query)?.SingleOrDefault()); + return Task.FromResult(_templateRepository.Get(query)?.SingleOrDefault()); } } /// <inheritdoc /> - public async Task<IEnumerable<ITemplate>> GetDescendantsAsync(int masterTemplateId) + public Task<IEnumerable<ITemplate>> GetDescendantsAsync(int masterTemplateId) { using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { - return await Task.FromResult(_templateRepository.GetDescendants(masterTemplateId)); + return Task.FromResult(_templateRepository.GetDescendants(masterTemplateId)); } } @@ -286,31 +286,32 @@ private async Task<Attempt<ITemplate, TemplateOperationStatus>> SaveAsync(ITempl => await DeleteAsync(async () => await GetAsync(key), userKey); /// <inheritdoc /> - public async Task<Stream> GetFileContentStreamAsync(string filepath) + public Task<Stream> GetFileContentStreamAsync(string filepath) { using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { - return await Task.FromResult(_templateRepository.GetFileContentStream(filepath)); + return Task.FromResult(_templateRepository.GetFileContentStream(filepath)); } } /// <inheritdoc /> - public async Task SetFileContentAsync(string filepath, Stream content) + public Task SetFileContentAsync(string filepath, Stream content) { using (ICoreScope scope = ScopeProvider.CreateCoreScope()) { _templateRepository.SetFileContent(filepath, content); scope.Complete(); - await Task.CompletedTask; } + + return Task.CompletedTask; } /// <inheritdoc /> - public async Task<long> GetFileSizeAsync(string filepath) + public Task<long> GetFileSizeAsync(string filepath) { using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { - return await Task.FromResult(_templateRepository.GetFileSize(filepath)); + return Task.FromResult(_templateRepository.GetFileSize(filepath)); } } diff --git a/src/Umbraco.Core/Services/TrackedReferencesService.cs b/src/Umbraco.Core/Services/TrackedReferencesService.cs index 5d5d76f7f041..d8103e69824b 100644 --- a/src/Umbraco.Core/Services/TrackedReferencesService.cs +++ b/src/Umbraco.Core/Services/TrackedReferencesService.cs @@ -83,13 +83,13 @@ public PagedModel<RelationItemModel> GetPagedRelationsForItem(int id, long skip, return pagedModel; } - public async Task<PagedModel<RelationItemModel>> GetPagedRelationsForItemAsync(Guid key, long skip, long take, bool filterMustBeIsDependency) + public Task<PagedModel<RelationItemModel>> GetPagedRelationsForItemAsync(Guid key, long skip, long take, bool filterMustBeIsDependency) { using ICoreScope scope = _scopeProvider.CreateCoreScope(autoComplete: true); IEnumerable<RelationItemModel> items = _trackedReferencesRepository.GetPagedRelationsForItem(key, skip, take, filterMustBeIsDependency, out var totalItems); var pagedModel = new PagedModel<RelationItemModel>(totalItems, items); - return await Task.FromResult(pagedModel); + return Task.FromResult(pagedModel); } [Obsolete("Use overload that takes key instead of id. This will be removed in Umbraco 15.")] @@ -108,7 +108,7 @@ public PagedModel<RelationItemModel> GetPagedDescendantsInReferences(int parentI return pagedModel; } - public async Task<PagedModel<RelationItemModel>> GetPagedDescendantsInReferencesAsync(Guid parentKey, long skip, long take, bool filterMustBeIsDependency) + public Task<PagedModel<RelationItemModel>> GetPagedDescendantsInReferencesAsync(Guid parentKey, long skip, long take, bool filterMustBeIsDependency) { using ICoreScope scope = _scopeProvider.CreateCoreScope(autoComplete: true); @@ -120,7 +120,7 @@ public async Task<PagedModel<RelationItemModel>> GetPagedDescendantsInReferences out var totalItems); var pagedModel = new PagedModel<RelationItemModel>(totalItems, items); - return await Task.FromResult(pagedModel); + return Task.FromResult(pagedModel); } [Obsolete("Use overload that takes key instead of id. This will be removed in Umbraco 15.")] @@ -133,13 +133,13 @@ public PagedModel<RelationItemModel> GetPagedItemsWithRelations(int[] ids, long return pagedModel; } - public async Task<PagedModel<RelationItemModel>> GetPagedItemsWithRelationsAsync(ISet<Guid> keys, long skip, long take, bool filterMustBeIsDependency) + public Task<PagedModel<RelationItemModel>> GetPagedItemsWithRelationsAsync(ISet<Guid> keys, long skip, long take, bool filterMustBeIsDependency) { using ICoreScope scope = _scopeProvider.CreateCoreScope(autoComplete: true); IEnumerable<RelationItemModel> items = _trackedReferencesRepository.GetPagedItemsWithRelations(keys, skip, take, filterMustBeIsDependency, out var totalItems); var pagedModel = new PagedModel<RelationItemModel>(totalItems, items); - return await Task.FromResult(pagedModel); + return Task.FromResult(pagedModel); } public async Task<PagedModel<Guid>> GetPagedKeysWithDependentReferencesAsync(ISet<Guid> keys, Guid objectTypeId, long skip, long take) diff --git a/src/Umbraco.Core/Services/WebProfilerService.cs b/src/Umbraco.Core/Services/WebProfilerService.cs index e895bba6e7a7..9a42e0ee5785 100644 --- a/src/Umbraco.Core/Services/WebProfilerService.cs +++ b/src/Umbraco.Core/Services/WebProfilerService.cs @@ -15,30 +15,30 @@ public WebProfilerService(IWebProfilerRepository webProfilerRepository, IBackOff _backOfficeSecurityAccessor = backOfficeSecurityAccessor; } - public async Task<Attempt<bool, WebProfilerOperationStatus>> GetStatus() + public Task<Attempt<bool, WebProfilerOperationStatus>> GetStatus() { Attempt<int> userIdAttempt = GetExecutingUserId(); if (userIdAttempt.Success is false) { - return Attempt.FailWithStatus(WebProfilerOperationStatus.ExecutingUserNotFound, false); + return Task.FromResult(Attempt.FailWithStatus(WebProfilerOperationStatus.ExecutingUserNotFound, false)); } var result = _webProfilerRepository.GetStatus(userIdAttempt.Result); - return await Task.FromResult(Attempt.SucceedWithStatus(WebProfilerOperationStatus.Success, result)); + return Task.FromResult(Attempt.SucceedWithStatus(WebProfilerOperationStatus.Success, result)); } - public async Task<Attempt<bool, WebProfilerOperationStatus>> SetStatus(bool status) + public Task<Attempt<bool, WebProfilerOperationStatus>> SetStatus(bool status) { Attempt<int> userIdAttempt = GetExecutingUserId(); if (userIdAttempt.Success is false) { - return Attempt.FailWithStatus(WebProfilerOperationStatus.ExecutingUserNotFound, false); + return Task.FromResult(Attempt.FailWithStatus(WebProfilerOperationStatus.ExecutingUserNotFound, false)); } _webProfilerRepository.SetStatus(userIdAttempt.Result, status); - return await Task.FromResult(Attempt.SucceedWithStatus(WebProfilerOperationStatus.Success, status)); + return Task.FromResult(Attempt.SucceedWithStatus(WebProfilerOperationStatus.Success, status)); } private Attempt<int> GetExecutingUserId() diff --git a/src/Umbraco.Core/Telemetry/TelemetryService.cs b/src/Umbraco.Core/Telemetry/TelemetryService.cs index ee57c4deab74..c1ce0dfcc19d 100644 --- a/src/Umbraco.Core/Telemetry/TelemetryService.cs +++ b/src/Umbraco.Core/Telemetry/TelemetryService.cs @@ -61,7 +61,8 @@ public bool TryGetTelemetryReportData(out TelemetryReportData? telemetryReportDa }; } - private string? GetVersion() => _metricsConsentService.GetConsentLevel() == TelemetryLevel.Minimal + private string? GetVersion() + => _metricsConsentService.GetConsentLevel() == TelemetryLevel.Minimal ? null : _umbracoVersion.SemanticVersion.ToSemanticStringWithoutBuild(); diff --git a/src/Umbraco.Infrastructure/BackgroundJobs/Jobs/WebhookFiring.cs b/src/Umbraco.Infrastructure/BackgroundJobs/Jobs/WebhookFiring.cs index 07d8a09d9adc..6ff87ff3cffa 100644 --- a/src/Umbraco.Infrastructure/BackgroundJobs/Jobs/WebhookFiring.cs +++ b/src/Umbraco.Infrastructure/BackgroundJobs/Jobs/WebhookFiring.cs @@ -65,6 +65,7 @@ public async Task RunJobAsync() scope.Complete(); } + // Send webhook requests in parallel on a suppressed ExecutionContext to avoid deadlocks (each task will create its own root IScope) await Task.WhenAll(requests.Select(request => { using (ExecutionContext.SuppressFlow()) diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs index 74fa4f374df1..6e62e2c965e7 100644 --- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs +++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs @@ -50,7 +50,6 @@ using Umbraco.Cms.Infrastructure.Manifest; using Umbraco.Cms.Infrastructure.Migrations; using Umbraco.Cms.Infrastructure.Migrations.Install; -using Umbraco.Cms.Infrastructure.Migrations.PostMigrations; using Umbraco.Cms.Infrastructure.Persistence; using Umbraco.Cms.Infrastructure.Persistence.Mappers; using Umbraco.Cms.Infrastructure.Routing; @@ -148,8 +147,6 @@ public static IUmbracoBuilder AddCoreInitialServices(this IUmbracoBuilder builde builder.Services.AddSingleton<IMigrationPlanExecutor, MigrationPlanExecutor>(); builder.Services.AddSingleton<IMigrationBuilder>(factory => new MigrationBuilder(factory)); - builder.Services.AddSingleton<ICacheRebuilder, CacheRebuilder>(); - builder.Services.AddSingleton<IVariationContextAccessor, HybridVariationContextAccessor>(); builder.Services.AddSingleton<IBackOfficeVariationContextAccessor, HttpContextBackOfficeVariationContextAccessor>(); diff --git a/src/Umbraco.Infrastructure/Examine/ContentValueSetValidator.cs b/src/Umbraco.Infrastructure/Examine/ContentValueSetValidator.cs index c6e1de7ff27b..259a1984fffb 100644 --- a/src/Umbraco.Infrastructure/Examine/ContentValueSetValidator.cs +++ b/src/Umbraco.Infrastructure/Examine/ContentValueSetValidator.cs @@ -15,20 +15,19 @@ public class ContentValueSetValidator : ValueSetValidator, IContentValueSetValid private const string PathKey = "path"; private static readonly IEnumerable<string> ValidCategories = new[] { IndexTypes.Content, IndexTypes.Media }; private readonly IPublicAccessService? _publicAccessService; - private readonly IScopeProvider? _scopeProvider; + private readonly Scoping.IScopeProvider? _scopeProvider; // used for tests - public ContentValueSetValidator(bool publishedValuesOnly, int? parentId = null, IEnumerable<string>? includeItemTypes = null, IEnumerable<string>? excludeItemTypes = null) - : this(publishedValuesOnly, true, null, null, parentId, includeItemTypes, excludeItemTypes, null, null) + internal ContentValueSetValidator(bool publishedValuesOnly, int? parentId = null, IEnumerable<string>? includeItemTypes = null, IEnumerable<string>? excludeItemTypes = null) + : this(publishedValuesOnly, true, null, null, parentId, includeItemTypes, excludeItemTypes) { } - [Obsolete("Use the overload accepting includeFields and excludeFields instead. This overload will be removed in Umbraco 14.")] public ContentValueSetValidator( bool publishedValuesOnly, bool supportProtectedContent, IPublicAccessService? publicAccessService, - IScopeProvider? scopeProvider, + Scoping.IScopeProvider? scopeProvider, int? parentId, IEnumerable<string>? includeItemTypes, IEnumerable<string>? excludeItemTypes) @@ -41,26 +40,6 @@ public ContentValueSetValidator( _scopeProvider = scopeProvider; } - [Obsolete("This constructor is obsolete, the IScopeProvider will change to Infrastructure.Scoping.ScopeProvider instead, this will be removed in Umbraco 14.")] - public ContentValueSetValidator( - bool publishedValuesOnly, - bool supportProtectedContent, - IPublicAccessService? publicAccessService, - IScopeProvider? scopeProvider, - int? parentId = null, - IEnumerable<string>? includeItemTypes = null, - IEnumerable<string>? excludeItemTypes = null, - IEnumerable<string>? includeFields = null, - IEnumerable<string>? excludeFields = null) - : base(includeItemTypes, excludeItemTypes, includeFields, excludeFields) - { - PublishedValuesOnly = publishedValuesOnly; - SupportProtectedContent = supportProtectedContent; - ParentId = parentId; - _publicAccessService = publicAccessService; - _scopeProvider = scopeProvider; - } - protected override IEnumerable<string> ValidIndexCategories => ValidCategories; public bool PublishedValuesOnly { get; } diff --git a/src/Umbraco.Infrastructure/Examine/UmbracoIndexConfig.cs b/src/Umbraco.Infrastructure/Examine/UmbracoIndexConfig.cs index 2c6377768a50..9688d5672136 100644 --- a/src/Umbraco.Infrastructure/Examine/UmbracoIndexConfig.cs +++ b/src/Umbraco.Infrastructure/Examine/UmbracoIndexConfig.cs @@ -1,6 +1,6 @@ using Examine; -using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Core.Services; +using IScopeProvider = Umbraco.Cms.Infrastructure.Scoping.IScopeProvider; namespace Umbraco.Cms.Infrastructure.Examine; @@ -17,10 +17,10 @@ public UmbracoIndexConfig(IPublicAccessService publicAccessService, IScopeProvid protected IScopeProvider ScopeProvider { get; } public IContentValueSetValidator GetContentValueSetValidator() => - new ContentValueSetValidator(false, true, PublicAccessService, ScopeProvider); + new ContentValueSetValidator(false, true, PublicAccessService, ScopeProvider, null, null, null); public IContentValueSetValidator GetPublishedContentValueSetValidator() => - new ContentValueSetValidator(true, false, PublicAccessService, ScopeProvider); + new ContentValueSetValidator(true, false, PublicAccessService, ScopeProvider, null, null, null); /// <summary> /// Returns the <see cref="IValueSetValidator" /> for the member indexer diff --git a/src/Umbraco.Infrastructure/Install/InstallHelper.cs b/src/Umbraco.Infrastructure/Install/InstallHelper.cs index 33152fbfac9c..0f67d56bed57 100644 --- a/src/Umbraco.Infrastructure/Install/InstallHelper.cs +++ b/src/Umbraco.Infrastructure/Install/InstallHelper.cs @@ -50,7 +50,7 @@ public InstallHelper( _databaseProviderMetadata = databaseProviderMetadata; } - public async Task SetInstallStatusAsync(bool isCompleted, string errorMsg) + public Task SetInstallStatusAsync(bool isCompleted, string errorMsg) { try { @@ -92,6 +92,8 @@ public async Task SetInstallStatusAsync(bool isCompleted, string errorMsg) { _logger.LogError(ex, "An error occurred in InstallStatus trying to check upgrades"); } + + return Task.CompletedTask; } /// <summary> diff --git a/src/Umbraco.Infrastructure/Install/PackageMigrationRunner.cs b/src/Umbraco.Infrastructure/Install/PackageMigrationRunner.cs index 954ff0a9aa01..01e2fbe59518 100644 --- a/src/Umbraco.Infrastructure/Install/PackageMigrationRunner.cs +++ b/src/Umbraco.Infrastructure/Install/PackageMigrationRunner.cs @@ -50,15 +50,18 @@ public PackageMigrationRunner( _packageMigrationPlans = packageMigrationPlans.ToDictionary(x => x.Name); } + [Obsolete("Please use RunPackageMigrationsIfPendingAsync instead. Scheduled for removal in Umbraco 18.")] + public IEnumerable<ExecutedMigrationPlan> RunPackageMigrationsIfPending(string packageName) + => RunPackageMigrationsIfPendingAsync(packageName).GetAwaiter().GetResult(); + /// <summary> /// Runs all migration plans for a package name if any are pending. /// </summary> /// <param name="packageName"></param> /// <returns></returns> - public IEnumerable<ExecutedMigrationPlan> RunPackageMigrationsIfPending(string packageName) + public async Task<IEnumerable<ExecutedMigrationPlan>> RunPackageMigrationsIfPendingAsync(string packageName) { - IReadOnlyDictionary<string, string?>? keyValues = - _keyValueService.FindByKeyPrefix(Constants.Conventions.Migrations.KeyValuePrefix); + IReadOnlyDictionary<string, string?>? keyValues = _keyValueService.FindByKeyPrefix(Constants.Conventions.Migrations.KeyValuePrefix); IReadOnlyList<string> pendingMigrations = _pendingPackageMigrations.GetPendingPackageMigrations(keyValues); IEnumerable<string> packagePlans = _packageMigrationPlans.Values @@ -66,7 +69,7 @@ public IEnumerable<ExecutedMigrationPlan> RunPackageMigrationsIfPending(string p .Where(x => pendingMigrations.Contains(x.Name)) .Select(x => x.Name); - return RunPackagePlans(packagePlans); + return await RunPackagePlansAsync(packagePlans).ConfigureAwait(false); } /// <summary> @@ -81,7 +84,7 @@ public async Task<Attempt<bool, PackageMigrationOperationStatus>> RunPendingPack } // Run the migrations - IEnumerable<ExecutedMigrationPlan> executedMigrationPlans = RunPackageMigrationsIfPending(packageName); + IEnumerable<ExecutedMigrationPlan> executedMigrationPlans = await RunPackageMigrationsIfPendingAsync(packageName).ConfigureAwait(false); if (executedMigrationPlans.Any(plan => plan.Successful == false)) { @@ -91,6 +94,10 @@ public async Task<Attempt<bool, PackageMigrationOperationStatus>> RunPendingPack return Attempt.SucceedWithStatus(PackageMigrationOperationStatus.Success, true); } + [Obsolete("Please use RunPackageMigrationsIfPendingAsync instead. Scheduled for removal in Umbraco 18.")] + public IEnumerable<ExecutedMigrationPlan> RunPackagePlans(IEnumerable<string> plansToRun) + => RunPackagePlansAsync(plansToRun).GetAwaiter().GetResult(); + /// <summary> /// Runs the all specified package migration plans and publishes a <see cref="MigrationPlansExecutedNotification" /> /// if all are successful. @@ -98,7 +105,7 @@ public async Task<Attempt<bool, PackageMigrationOperationStatus>> RunPendingPack /// <param name="plansToRun"></param> /// <returns></returns> /// <exception cref="Exception">If any plan fails it will throw an exception.</exception> - public IEnumerable<ExecutedMigrationPlan> RunPackagePlans(IEnumerable<string> plansToRun) + public async Task<IEnumerable<ExecutedMigrationPlan>> RunPackagePlansAsync(IEnumerable<string> plansToRun) { List<ExecutedMigrationPlan> results = new(); @@ -120,7 +127,7 @@ public IEnumerable<ExecutedMigrationPlan> RunPackagePlans(IEnumerable<string> pl Upgrader upgrader = new(plan); // This may throw, if so the transaction will be rolled back - results.Add(upgrader.Execute(_migrationPlanExecutor, _scopeProvider, _keyValueService)); + results.Add(await upgrader.ExecuteAsync(_migrationPlanExecutor, _scopeProvider, _keyValueService).ConfigureAwait(false)); } } diff --git a/src/Umbraco.Infrastructure/Install/PremigrationUpgrader.cs b/src/Umbraco.Infrastructure/Install/PremigrationUpgrader.cs index 1be5cb1ee8e7..20142fc19cf7 100644 --- a/src/Umbraco.Infrastructure/Install/PremigrationUpgrader.cs +++ b/src/Umbraco.Infrastructure/Install/PremigrationUpgrader.cs @@ -36,42 +36,36 @@ public PremigrationUpgrader( _keyValueService = keyValueService; } - public Task HandleAsync(RuntimePremigrationsUpgradeNotification notification, CancellationToken cancellationToken) + public async Task HandleAsync(RuntimePremigrationsUpgradeNotification notification, CancellationToken cancellationToken) { // no connection string set if (_umbracoDatabaseFactory.Configured is false) { - return Task.CompletedTask; + return; } if (_databaseBuilder.IsUmbracoInstalled() is false) { - return Task.CompletedTask; + return; } var plan = new UmbracoPremigrationPlan(); if (HasMissingPremigrations(plan) is false) { - return Task.CompletedTask; + return; } - using (_profilingLogger.IsEnabled(LogLevel.Verbose) is false ? null : _profilingLogger.TraceDuration<UnattendedUpgrader>( - "Starting premigration upgrade.", - "Unattended premigration completed.")) + using (_profilingLogger.IsEnabled(LogLevel.Verbose) is false ? null : _profilingLogger.TraceDuration<UnattendedUpgrader>("Starting premigration upgrade.", "Unattended premigration completed.")) { - DatabaseBuilder.Result? result = _databaseBuilder.UpgradeSchemaAndData(plan); + DatabaseBuilder.Result? result = await _databaseBuilder.UpgradeSchemaAndDataAsync(plan).ConfigureAwait(false); if (result?.Success is false) { - var innerException = new IOException( - "An error occurred while running the premigration upgrade.\n" + result.Message); + var innerException = new IOException("An error occurred while running the premigration upgrade.\n" + result.Message); _runtimeState.Configure(RuntimeLevel.BootFailed, RuntimeLevelReason.BootFailedOnException, innerException); } - notification.UpgradeResult = - RuntimePremigrationsUpgradeNotification.PremigrationUpgradeResult.CoreUpgradeComplete; + notification.UpgradeResult = RuntimePremigrationsUpgradeNotification.PremigrationUpgradeResult.CoreUpgradeComplete; } - - return Task.CompletedTask; } private bool HasMissingPremigrations(UmbracoPremigrationPlan umbracoPremigrationPlan) diff --git a/src/Umbraco.Infrastructure/Install/UnattendedUpgrader.cs b/src/Umbraco.Infrastructure/Install/UnattendedUpgrader.cs index 9bf5c91eb81a..65d841917d6f 100644 --- a/src/Umbraco.Infrastructure/Install/UnattendedUpgrader.cs +++ b/src/Umbraco.Infrastructure/Install/UnattendedUpgrader.cs @@ -62,7 +62,7 @@ public UnattendedUpgrader( { } - public Task HandleAsync(RuntimeUnattendedUpgradeNotification notification, CancellationToken cancellationToken) + public async Task HandleAsync(RuntimeUnattendedUpgradeNotification notification, CancellationToken cancellationToken) { if (_runtimeState.RunUnattendedBootLogic()) { @@ -70,26 +70,26 @@ public Task HandleAsync(RuntimeUnattendedUpgradeNotification notification, Cance { case RuntimeLevelReason.UpgradeMigrations: { - RunUpgrade(notification); + await RunUpgradeAsync(notification); // If we errored out when upgrading don't do anything. if (notification.UnattendedUpgradeResult is RuntimeUnattendedUpgradeNotification.UpgradeResult.HasErrors) { - return Task.CompletedTask; + return; } // It's entirely possible that there's both a core upgrade and package migrations to run, so try and run package migrations too. // but only if upgrade unattended is enabled. if (_unattendedSettings.PackageMigrationsUnattended) { - RunPackageMigrations(notification); + await RunPackageMigrationsAsync(notification); } } break; case RuntimeLevelReason.UpgradePackageMigrations: { - RunPackageMigrations(notification); + await RunPackageMigrationsAsync(notification); } break; @@ -97,11 +97,9 @@ public Task HandleAsync(RuntimeUnattendedUpgradeNotification notification, Cance throw new InvalidOperationException("Invalid reason " + _runtimeState.Reason); } } - - return Task.CompletedTask; } - private void RunPackageMigrations(RuntimeUnattendedUpgradeNotification notification) + private async Task RunPackageMigrationsAsync(RuntimeUnattendedUpgradeNotification notification) { if (_runtimeState.StartupState.TryGetValue( RuntimeState.PendingPackageMigrationsStateKey, @@ -127,7 +125,7 @@ private void RunPackageMigrations(RuntimeUnattendedUpgradeNotification notificat try { - _packageMigrationRunner.RunPackagePlans(pendingMigrations); + await _packageMigrationRunner.RunPackagePlansAsync(pendingMigrations); notification.UnattendedUpgradeResult = RuntimeUnattendedUpgradeNotification.UpgradeResult .PackageMigrationComplete; } @@ -139,14 +137,14 @@ private void RunPackageMigrations(RuntimeUnattendedUpgradeNotification notificat } } - private void RunUpgrade(RuntimeUnattendedUpgradeNotification notification) + private async Task RunUpgradeAsync(RuntimeUnattendedUpgradeNotification notification) { var plan = new UmbracoPlan(_umbracoVersion); using (!_profilingLogger.IsEnabled(Core.Logging.LogLevel.Verbose) ? null : _profilingLogger.TraceDuration<UnattendedUpgrader>( "Starting unattended upgrade.", "Unattended upgrade completed.")) { - DatabaseBuilder.Result? result = _databaseBuilder.UpgradeSchemaAndData(plan); + DatabaseBuilder.Result? result = await _databaseBuilder.UpgradeSchemaAndDataAsync(plan); if (result?.Success == false) { var innerException = new UnattendedInstallException( diff --git a/src/Umbraco.Infrastructure/Installer/Steps/DatabaseUpgradeStep.cs b/src/Umbraco.Infrastructure/Installer/Steps/DatabaseUpgradeStep.cs index 4d1e99a27e2b..7bebc3d4fab5 100644 --- a/src/Umbraco.Infrastructure/Installer/Steps/DatabaseUpgradeStep.cs +++ b/src/Umbraco.Infrastructure/Installer/Steps/DatabaseUpgradeStep.cs @@ -1,4 +1,4 @@ -using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Configuration; using Umbraco.Cms.Core.Installer; @@ -35,21 +35,21 @@ public DatabaseUpgradeStep( public Task<Attempt<InstallationResult>> ExecuteAsync() => ExecuteInternalAsync(); - private Task<Attempt<InstallationResult>> ExecuteInternalAsync() + private async Task<Attempt<InstallationResult>> ExecuteInternalAsync() { _logger.LogInformation("Running 'Upgrade' service"); var plan = new UmbracoPlan(_umbracoVersion); // TODO: Clear CSRF cookies with notification. - DatabaseBuilder.Result? result = _databaseBuilder.UpgradeSchemaAndData(plan); + DatabaseBuilder.Result? result = await _databaseBuilder.UpgradeSchemaAndDataAsync(plan).ConfigureAwait(false); if (result?.Success == false) { - return Task.FromResult(FailWithMessage("The database failed to upgrade. ERROR: " + result.Message)); + return FailWithMessage("The database failed to upgrade. ERROR: " + result.Message); } - return Task.FromResult(Success()); + return Success(); } public Task<bool> RequiresExecutionAsync(InstallData model) => ShouldExecute(); diff --git a/src/Umbraco.Infrastructure/Manifest/PackageManifestService.cs b/src/Umbraco.Infrastructure/Manifest/PackageManifestService.cs index fbf00b7fa323..03a7b9d7c17a 100644 --- a/src/Umbraco.Infrastructure/Manifest/PackageManifestService.cs +++ b/src/Umbraco.Infrastructure/Manifest/PackageManifestService.cs @@ -1,4 +1,4 @@ -using Microsoft.Extensions.Options; +using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Manifest; @@ -29,17 +29,18 @@ public async Task<IEnumerable<PackageManifest>> GetAllPackageManifestsAsync() $"{nameof(PackageManifestService)}-PackageManifests", async () => { - Task<IEnumerable<PackageManifest>>[] tasks = _packageManifestReaders - .Select(x => x.ReadPackageManifestsAsync()) - .ToArray(); - await Task.WhenAll(tasks); + var packageManifests = new List<PackageManifest>(); + foreach (IPackageManifestReader packageManifestReader in _packageManifestReaders) + { + packageManifests.AddRange(await packageManifestReader.ReadPackageManifestsAsync()); + } - return tasks.SelectMany(x => x.Result); + return packageManifests; }, _runtimeSettings.Mode == RuntimeMode.Production ? TimeSpan.FromDays(30) : TimeSpan.FromSeconds(10)) - ?? Array.Empty<PackageManifest>(); + ?? Enumerable.Empty<PackageManifest>(); public async Task<IEnumerable<PackageManifest>> GetPublicPackageManifestsAsync() => (await GetAllPackageManifestsAsync()).Where(manifest => manifest.AllowPublicAccess); diff --git a/src/Umbraco.Infrastructure/Migrations/AsyncMigrationBase.Database.cs b/src/Umbraco.Infrastructure/Migrations/AsyncMigrationBase.Database.cs new file mode 100644 index 000000000000..21ea92377981 --- /dev/null +++ b/src/Umbraco.Infrastructure/Migrations/AsyncMigrationBase.Database.cs @@ -0,0 +1,147 @@ +using Umbraco.Cms.Infrastructure.Migrations.Expressions.Execute.Expressions; +using Umbraco.Cms.Infrastructure.Persistence.DatabaseModelDefinitions; +using Umbraco.Cms.Infrastructure.Persistence.SqlSyntax; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Infrastructure.Migrations; + +/// <summary> +/// Provides a base class to all migrations. +/// </summary> +public abstract partial class AsyncMigrationBase +{ + // provides extra methods for migrations + protected void AddColumn<T>(string columnName) + { + TableDefinition? table = DefinitionFactory.GetTableDefinition(typeof(T), SqlSyntax); + AddColumn(table, table.Name!, columnName); + } + + protected void AddColumnIfNotExists<T>(IEnumerable<ColumnInfo> columns, string columnName) + { + TableDefinition? table = DefinitionFactory.GetTableDefinition(typeof(T), SqlSyntax); + if (columns.Any(x => x.TableName.InvariantEquals(table.Name) && !x.ColumnName.InvariantEquals(columnName))) + { + AddColumn(table, table.Name!, columnName); + } + } + + protected void AddColumn<T>(string tableName, string columnName) + { + TableDefinition? table = DefinitionFactory.GetTableDefinition(typeof(T), SqlSyntax); + AddColumn(table, tableName, columnName); + } + + protected void AddColumnIfNotExists<T>(IEnumerable<ColumnInfo> columns, string tableName, string columnName) + { + TableDefinition? table = DefinitionFactory.GetTableDefinition(typeof(T), SqlSyntax); + if (columns.Any(x => x.TableName.InvariantEquals(tableName) && !x.ColumnName.InvariantEquals(columnName))) + { + AddColumn(table, tableName, columnName); + } + } + + protected void AddColumn<T>(string columnName, out IEnumerable<string> sqls) + { + TableDefinition? table = DefinitionFactory.GetTableDefinition(typeof(T), SqlSyntax); + AddColumn(table, table.Name!, columnName, out sqls); + } + + private void AddColumn(TableDefinition table, string tableName, string columnName) + { + if (ColumnExists(tableName, columnName)) + { + return; + } + + ColumnDefinition? column = table.Columns.First(x => x.Name == columnName); + var createSql = SqlSyntax.Format(column); + + Execute.Sql(string.Format(SqlSyntax.AddColumn, SqlSyntax.GetQuotedTableName(tableName), createSql)).Do(); + } + + protected void AddColumn<T>(string tableName, string columnName, out IEnumerable<string> sqls) + { + TableDefinition? table = DefinitionFactory.GetTableDefinition(typeof(T), SqlSyntax); + AddColumn(table, tableName, columnName, out sqls); + } + + protected void AlterColumn<T>(string tableName, string columnName) + { + TableDefinition? table = DefinitionFactory.GetTableDefinition(typeof(T), SqlSyntax); + ColumnDefinition? column = table.Columns.First(x => x.Name == columnName); + SqlSyntax.Format(column, SqlSyntax.GetQuotedTableName(tableName), out IEnumerable<string>? sqls); + foreach (var sql in sqls) + { + Execute.Sql(sql).Do(); + } + } + + private void AddColumn(TableDefinition table, string tableName, string columnName, out IEnumerable<string> sqls) + { + if (ColumnExists(tableName, columnName)) + { + sqls = Enumerable.Empty<string>(); + return; + } + + ColumnDefinition? column = table.Columns.First(x => x.Name == columnName); + var createSql = SqlSyntax.Format(column, SqlSyntax.GetQuotedTableName(tableName), out sqls); + Execute.Sql(string.Format(SqlSyntax.AddColumn, SqlSyntax.GetQuotedTableName(tableName), createSql)).Do(); + } + + protected void ReplaceColumn<T>(string tableName, string currentName, string newName) + { + Execute.Sql(SqlSyntax.FormatColumnRename(tableName, currentName, newName)).Do(); + AlterColumn<T>(tableName, newName); + } + + protected bool TableExists(string tableName) + { + IEnumerable<string>? tables = SqlSyntax.GetTablesInSchema(Context.Database); + return tables.Any(x => x.InvariantEquals(tableName)); + } + + protected bool IndexExists(string indexName) + { + IEnumerable<Tuple<string, string, string, bool>>? indexes = SqlSyntax.GetDefinedIndexes(Context.Database); + return indexes.Any(x => x.Item2.InvariantEquals(indexName)); + } + + protected void CreateIndex<T>(string toCreate) + { + TableDefinition tableDef = DefinitionFactory.GetTableDefinition(typeof(T), Context.SqlContext.SqlSyntax); + IndexDefinition index = tableDef.Indexes.First(x => x.Name == toCreate); + new ExecuteSqlStatementExpression(Context) { SqlStatement = Context.SqlContext.SqlSyntax.Format(index) } + .Execute(); + } + + protected void DeleteIndex<T>(string toDelete) + { + if (!IndexExists(toDelete)) + { + return; + } + + TableDefinition tableDef = DefinitionFactory.GetTableDefinition(typeof(T), Context.SqlContext.SqlSyntax); + Delete.Index(toDelete).OnTable(tableDef.Name).Do(); + } + + protected bool PrimaryKeyExists(string tableName, string primaryKeyName) + { + return SqlSyntax.DoesPrimaryKeyExist(Context.Database, tableName, primaryKeyName); + } + + protected bool ColumnExists(string tableName, string columnName) + { + ColumnInfo[]? columns = SqlSyntax.GetColumnsInSchema(Context.Database).Distinct().ToArray(); + return columns.Any(x => x.TableName.InvariantEquals(tableName) && x.ColumnName.InvariantEquals(columnName)); + } + + protected string? ColumnType(string tableName, string columnName) + { + ColumnInfo[]? columns = SqlSyntax.GetColumnsInSchema(Context.Database).Distinct().ToArray(); + ColumnInfo? column = columns.FirstOrDefault(x => x.TableName.InvariantEquals(tableName) && x.ColumnName.InvariantEquals(columnName)); + return column?.DataType; + } +} diff --git a/src/Umbraco.Infrastructure/Migrations/AsyncMigrationBase.cs b/src/Umbraco.Infrastructure/Migrations/AsyncMigrationBase.cs new file mode 100644 index 000000000000..461a79a0b471 --- /dev/null +++ b/src/Umbraco.Infrastructure/Migrations/AsyncMigrationBase.cs @@ -0,0 +1,140 @@ +using Microsoft.Extensions.Logging; +using NPoco; +using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Infrastructure.Migrations.Expressions.Alter; +using Umbraco.Cms.Infrastructure.Migrations.Expressions.Create; +using Umbraco.Cms.Infrastructure.Migrations.Expressions.Delete; +using Umbraco.Cms.Infrastructure.Migrations.Expressions.Execute; +using Umbraco.Cms.Infrastructure.Migrations.Expressions.Insert; +using Umbraco.Cms.Infrastructure.Migrations.Expressions.Rename; +using Umbraco.Cms.Infrastructure.Migrations.Expressions.Update; +using Umbraco.Cms.Infrastructure.Persistence; +using Umbraco.Cms.Infrastructure.Persistence.SqlSyntax; + +namespace Umbraco.Cms.Infrastructure.Migrations; + +/// <summary> +/// Provides a base class to all migrations. +/// </summary> +public abstract partial class AsyncMigrationBase : IDiscoverable +{ + /// <summary> + /// Initializes a new instance of the <see cref="AsyncMigrationBase" /> class. + /// </summary> + /// <param name="context">A migration context.</param> + protected AsyncMigrationBase(IMigrationContext context) + => Context = context; + + /// <summary> + /// Builds an Alter expression. + /// </summary> + public IAlterBuilder Alter => BeginBuild(new AlterBuilder(Context)); + + /// <summary> + /// Gets the migration context. + /// </summary> + protected IMigrationContext Context { get; } + + /// <summary> + /// Gets the logger. + /// </summary> + protected ILogger Logger => Context.Logger; + + /// <summary> + /// Gets the SQL syntax. + /// </summary> + protected ISqlSyntaxProvider SqlSyntax => Context.SqlContext.SqlSyntax; + + /// <summary> + /// Gets the database instance. + /// </summary> + protected IUmbracoDatabase Database => Context.Database; + + /// <summary> + /// Gets the database type. + /// </summary> + protected DatabaseType DatabaseType => Context.Database.DatabaseType; + + /// <summary> + /// Builds a Create expression. + /// </summary> + public ICreateBuilder Create => BeginBuild(new CreateBuilder(Context)); + + /// <summary> + /// Builds a Delete expression. + /// </summary> + public IDeleteBuilder Delete => BeginBuild(new DeleteBuilder(Context)); + + /// <summary> + /// Builds an Execute expression. + /// </summary> + public IExecuteBuilder Execute => BeginBuild(new ExecuteBuilder(Context)); + + /// <summary> + /// Builds an Insert expression. + /// </summary> + public IInsertBuilder Insert => BeginBuild(new InsertBuilder(Context)); + + /// <summary> + /// Builds a Rename expression. + /// </summary> + public IRenameBuilder Rename => BeginBuild(new RenameBuilder(Context)); + + /// <summary> + /// Builds an Update expression. + /// </summary> + public IUpdateBuilder Update => BeginBuild(new UpdateBuilder(Context)); + + /// <summary> + /// If this is set to true, the published cache will be rebuild upon successful completion of the migration. + /// </summary> + public bool RebuildCache { get; set; } + + /// <summary> + /// If this is set to true, all back-office client tokens will be revoked upon successful completion of the migration. + /// </summary> + public bool InvalidateBackofficeUserAccess { get; set; } + + /// <summary> + /// Runs the migration. + /// </summary> + public async Task RunAsync() + { + await MigrateAsync().ConfigureAwait(false); + + // ensure there is no building expression + // ie we did not forget to .Do() an expression + if (Context.BuildingExpression) + { + throw new IncompleteMigrationExpressionException("The migration has run, but leaves an expression that has not run."); + } + } + + /// <summary> + /// Creates a new Sql statement. + /// </summary> + protected Sql<ISqlContext> Sql() => Context.SqlContext.Sql(); + + /// <summary> + /// Creates a new Sql statement with arguments. + /// </summary> + protected Sql<ISqlContext> Sql(string sql, params object[] args) => Context.SqlContext.Sql(sql, args); + + /// <summary> + /// Executes the migration. + /// </summary> + protected abstract Task MigrateAsync(); + + // ensures we are not already building, + // ie we did not forget to .Do() an expression + private protected T BeginBuild<T>(T builder) + { + if (Context.BuildingExpression) + { + throw new IncompleteMigrationExpressionException("Cannot create a new expression: the previous expression has not run."); + } + + Context.BuildingExpression = true; + return builder; + } +} diff --git a/src/Umbraco.Infrastructure/Migrations/IMigrationBuilder.cs b/src/Umbraco.Infrastructure/Migrations/IMigrationBuilder.cs index 078bfcaf387b..8379404c0571 100644 --- a/src/Umbraco.Infrastructure/Migrations/IMigrationBuilder.cs +++ b/src/Umbraco.Infrastructure/Migrations/IMigrationBuilder.cs @@ -2,5 +2,5 @@ namespace Umbraco.Cms.Infrastructure.Migrations; public interface IMigrationBuilder { - MigrationBase Build(Type migrationType, IMigrationContext context); + AsyncMigrationBase Build(Type migrationType, IMigrationContext context); } diff --git a/src/Umbraco.Infrastructure/Migrations/IMigrationContext.cs b/src/Umbraco.Infrastructure/Migrations/IMigrationContext.cs index cf68617315ba..932dbd4628db 100644 --- a/src/Umbraco.Infrastructure/Migrations/IMigrationContext.cs +++ b/src/Umbraco.Infrastructure/Migrations/IMigrationContext.cs @@ -38,13 +38,6 @@ public interface IMigrationContext /// </summary> bool BuildingExpression { get; set; } - /// <summary> - /// Adds a post-migration. - /// </summary> - [Obsolete("This will be removed in the V13, and replaced with a RebuildCache flag on the MigrationBase")] - void AddPostMigration<TMigration>() - where TMigration : MigrationBase; - bool IsCompleted { get; } void Complete(); diff --git a/src/Umbraco.Infrastructure/Migrations/IMigrationPlanExecutor.cs b/src/Umbraco.Infrastructure/Migrations/IMigrationPlanExecutor.cs index ee3f787c123a..70350f5e93b3 100644 --- a/src/Umbraco.Infrastructure/Migrations/IMigrationPlanExecutor.cs +++ b/src/Umbraco.Infrastructure/Migrations/IMigrationPlanExecutor.cs @@ -5,9 +5,20 @@ namespace Umbraco.Cms.Core.Migrations; public interface IMigrationPlanExecutor { - [Obsolete("Use ExecutePlan instead.")] + [Obsolete("Use ExecutePlan instead. Scheduled for removal in Umbraco 17.")] string Execute(MigrationPlan plan, string fromState); + /// <summary> + /// Executes the migration plan. + /// </summary> + /// <param name="plan">The migration plan to execute.</param> + /// <param name="fromState">The state to start execution at.</param> + /// <returns><see cref="ExecutedMigrationPlan"/> containing information about the plan execution, such as completion state and the steps that ran.</returns> + /// <remarks> + /// <para>Each migration in the plan, may or may not run in a scope depending on the type of plan.</para> + /// <para>A plan can complete partially, the changes of each completed migration will be saved.</para> + /// </remarks> + [Obsolete("Use ExecutePlanAsync instead. Scheduled for removal in Umbraco 18.")] ExecutedMigrationPlan ExecutePlan(MigrationPlan plan, string fromState) { var state = Execute(plan, fromState); @@ -15,4 +26,19 @@ ExecutedMigrationPlan ExecutePlan(MigrationPlan plan, string fromState) // We have no real way of knowing whether it was successfull or not here, assume true. return new ExecutedMigrationPlan(plan, fromState, state, true, plan.Transitions.Select(x => x.Value).WhereNotNull().ToList()); } + + /// <summary> + /// Executes the migration plan asynchronously. + /// </summary> + /// <param name="plan">The migration plan to execute.</param> + /// <param name="fromState">The state to start execution at.</param> + /// <returns>A Task of <see cref="ExecutedMigrationPlan"/> containing information about the plan execution, such as completion state and the steps that ran.</returns> + /// <remarks> + /// <para>Each migration in the plan, may or may not run in a scope depending on the type of plan.</para> + /// <para>A plan can complete partially, the changes of each completed migration will be saved.</para> + /// </remarks> + Task<ExecutedMigrationPlan> ExecutePlanAsync(MigrationPlan plan, string fromState) +#pragma warning disable CS0618 // Type or member is obsolete + => Task.FromResult(ExecutePlan(plan, fromState)); +#pragma warning restore CS0618 // Type or member is obsolete } diff --git a/src/Umbraco.Infrastructure/Migrations/Install/DatabaseBuilder.cs b/src/Umbraco.Infrastructure/Migrations/Install/DatabaseBuilder.cs index fffa0ced9ffc..4915e68efaf7 100644 --- a/src/Umbraco.Infrastructure/Migrations/Install/DatabaseBuilder.cs +++ b/src/Umbraco.Infrastructure/Migrations/Install/DatabaseBuilder.cs @@ -353,8 +353,14 @@ private void Configure(bool installMissingDatabase) } } + [Obsolete("Use UpgradeSchemaAndDataAsync instead. Scheduled for removal in Umbraco 18.")] public Result? UpgradeSchemaAndData(UmbracoPlan plan) => UpgradeSchemaAndData((MigrationPlan)plan); + [Obsolete("Use UpgradeSchemaAndDataAsync instead. Scheduled for removal in Umbraco 18.")] + public Result? UpgradeSchemaAndData(MigrationPlan plan) => UpgradeSchemaAndDataAsync(plan).GetAwaiter().GetResult(); + + public async Task<Result?> UpgradeSchemaAndDataAsync(UmbracoPlan plan) => await UpgradeSchemaAndDataAsync((MigrationPlan)plan).ConfigureAwait(false); + /// <summary> /// Upgrades the database schema and data by running migrations. /// </summary> @@ -363,7 +369,7 @@ private void Configure(bool installMissingDatabase) /// configured and it is possible to connect to the database.</para> /// <para>Runs whichever migrations need to run.</para> /// </remarks> - public Result? UpgradeSchemaAndData(MigrationPlan plan) + public async Task<Result?> UpgradeSchemaAndDataAsync(MigrationPlan plan) { try { @@ -377,7 +383,7 @@ private void Configure(bool installMissingDatabase) // upgrade var upgrader = new Upgrader(plan); - ExecutedMigrationPlan result = upgrader.Execute(_migrationPlanExecutor, _scopeProvider, _keyValueService); + ExecutedMigrationPlan result = await upgrader.ExecuteAsync(_migrationPlanExecutor, _scopeProvider, _keyValueService).ConfigureAwait(false); _aggregator.Publish(new UmbracoPlanExecutedNotification { ExecutedPlan = result }); diff --git a/src/Umbraco.Infrastructure/Migrations/MergeBuilder.cs b/src/Umbraco.Infrastructure/Migrations/MergeBuilder.cs index 3e80d9ebc5da..9e382022d29e 100644 --- a/src/Umbraco.Infrastructure/Migrations/MergeBuilder.cs +++ b/src/Umbraco.Infrastructure/Migrations/MergeBuilder.cs @@ -25,7 +25,7 @@ public MergeBuilder To(string targetState) /// Adds a transition to a target state through a migration. /// </summary> public MergeBuilder To<TMigration>(string targetState) - where TMigration : MigrationBase + where TMigration : AsyncMigrationBase => To(targetState, typeof(TMigration)); /// <summary> diff --git a/src/Umbraco.Infrastructure/Migrations/MigrationBase.cs b/src/Umbraco.Infrastructure/Migrations/MigrationBase.cs index cbdccc1ca420..9e539a06a9a2 100644 --- a/src/Umbraco.Infrastructure/Migrations/MigrationBase.cs +++ b/src/Umbraco.Infrastructure/Migrations/MigrationBase.cs @@ -1,143 +1,27 @@ -using Microsoft.Extensions.Logging; -using NPoco; -using Umbraco.Cms.Core.Composing; -using Umbraco.Cms.Infrastructure.Migrations.Expressions.Alter; -using Umbraco.Cms.Infrastructure.Migrations.Expressions.Create; -using Umbraco.Cms.Infrastructure.Migrations.Expressions.Delete; -using Umbraco.Cms.Infrastructure.Migrations.Expressions.Execute; -using Umbraco.Cms.Infrastructure.Migrations.Expressions.Insert; -using Umbraco.Cms.Infrastructure.Migrations.Expressions.Rename; -using Umbraco.Cms.Infrastructure.Migrations.Expressions.Update; -using Umbraco.Cms.Infrastructure.Persistence; -using Umbraco.Cms.Infrastructure.Persistence.SqlSyntax; -using Umbraco.Cms.Infrastructure.Scoping; - namespace Umbraco.Cms.Infrastructure.Migrations; -/// <summary> -/// Provides a base class to all migrations. -/// </summary> -public abstract partial class MigrationBase : IDiscoverable +/// <inheritdoc /> +[Obsolete("Use AsyncMigrationBase instead. This class will be removed in a future version.")] +public abstract class MigrationBase : AsyncMigrationBase { /// <summary> - /// Initializes a new instance of the <see cref="MigrationBase" /> class. + /// Initializes a new instance of the <see cref="MigrationBase"/> class. /// </summary> /// <param name="context">A migration context.</param> protected MigrationBase(IMigrationContext context) - => Context = context; - - /// <summary> - /// Builds an Alter expression. - /// </summary> - public IAlterBuilder Alter => BeginBuild(new AlterBuilder(Context)); - - /// <summary> - /// Gets the migration context. - /// </summary> - protected IMigrationContext Context { get; } - - /// <summary> - /// Gets the logger. - /// </summary> - protected ILogger Logger => Context.Logger; - - /// <summary> - /// Gets the Sql syntax. - /// </summary> - protected ISqlSyntaxProvider SqlSyntax => Context.SqlContext.SqlSyntax; - - /// <summary> - /// Gets the database instance. - /// </summary> - protected IUmbracoDatabase Database => Context.Database; - - /// <summary> - /// Gets the database type. - /// </summary> - protected DatabaseType DatabaseType => Context.Database.DatabaseType; - - /// <summary> - /// Builds a Create expression. - /// </summary> - public ICreateBuilder Create => BeginBuild(new CreateBuilder(Context)); - - /// <summary> - /// Builds a Delete expression. - /// </summary> - public IDeleteBuilder Delete => BeginBuild(new DeleteBuilder(Context)); - - /// <summary> - /// Builds an Execute expression. - /// </summary> - public IExecuteBuilder Execute => BeginBuild(new ExecuteBuilder(Context)); + : base(context) + { } - /// <summary> - /// Builds an Insert expression. - /// </summary> - public IInsertBuilder Insert => BeginBuild(new InsertBuilder(Context)); - - /// <summary> - /// Builds a Rename expression. - /// </summary> - public IRenameBuilder Rename => BeginBuild(new RenameBuilder(Context)); - - /// <summary> - /// Builds an Update expression. - /// </summary> - public IUpdateBuilder Update => BeginBuild(new UpdateBuilder(Context)); - - /// <summary> - /// If this is set to true, the published cache will be rebuild upon successful completion of the migration. - /// </summary> - public bool RebuildCache { get; set; } - - /// <summary> - /// If this is set to true, all backoffice client tokens will be revoked upon successful completion of the migration. - /// </summary> - public bool InvalidateBackofficeUserAccess { get; set; } - - /// <summary> - /// Runs the migration. - /// </summary> - public void Run() + /// <inheritdoc /> + protected override Task MigrateAsync() { Migrate(); - // ensure there is no building expression - // ie we did not forget to .Do() an expression - if (Context.BuildingExpression) - { - throw new IncompleteMigrationExpressionException( - "The migration has run, but leaves an expression that has not run."); - } + return Task.CompletedTask; } /// <summary> - /// Creates a new Sql statement. - /// </summary> - protected Sql<ISqlContext> Sql() => Context.SqlContext.Sql(); - - /// <summary> - /// Creates a new Sql statement with arguments. - /// </summary> - protected Sql<ISqlContext> Sql(string sql, params object[] args) => Context.SqlContext.Sql(sql, args); - - /// <summary> - /// Executes the migration. + /// Executes the migration. /// </summary> protected abstract void Migrate(); - - // ensures we are not already building, - // ie we did not forget to .Do() an expression - private protected T BeginBuild<T>(T builder) - { - if (Context.BuildingExpression) - { - throw new IncompleteMigrationExpressionException( - "Cannot create a new expression: the previous expression has not run."); - } - - Context.BuildingExpression = true; - return builder; - } } diff --git a/src/Umbraco.Infrastructure/Migrations/MigrationBase_Extra.cs b/src/Umbraco.Infrastructure/Migrations/MigrationBase_Extra.cs deleted file mode 100644 index 23de94e8247c..000000000000 --- a/src/Umbraco.Infrastructure/Migrations/MigrationBase_Extra.cs +++ /dev/null @@ -1,151 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using Umbraco.Cms.Infrastructure.Persistence; -using Umbraco.Cms.Infrastructure.Persistence.DatabaseModelDefinitions; -using Umbraco.Cms.Infrastructure.Migrations.Expressions.Execute.Expressions; -using Umbraco.Cms.Infrastructure.Persistence.SqlSyntax; -using Umbraco.Extensions; - -namespace Umbraco.Cms.Infrastructure.Migrations -{ - /// <summary> - /// Provides a base class to all migrations. - /// </summary> - public abstract partial class MigrationBase - { - // provides extra methods for migrations - protected void AddColumn<T>(string columnName) - { - TableDefinition? table = DefinitionFactory.GetTableDefinition(typeof(T), SqlSyntax); - AddColumn(table, table.Name!, columnName); - } - - protected void AddColumnIfNotExists<T>(IEnumerable<ColumnInfo> columns, string columnName) - { - TableDefinition? table = DefinitionFactory.GetTableDefinition(typeof(T), SqlSyntax); - if (columns.Any(x => x.TableName.InvariantEquals(table.Name) && !x.ColumnName.InvariantEquals(columnName))) - { - AddColumn(table, table.Name!, columnName); - } - } - - protected void AddColumn<T>(string tableName, string columnName) - { - TableDefinition? table = DefinitionFactory.GetTableDefinition(typeof(T), SqlSyntax); - AddColumn(table, tableName, columnName); - } - - protected void AddColumnIfNotExists<T>(IEnumerable<ColumnInfo> columns, string tableName, string columnName) - { - TableDefinition? table = DefinitionFactory.GetTableDefinition(typeof(T), SqlSyntax); - if (columns.Any(x => x.TableName.InvariantEquals(tableName) && !x.ColumnName.InvariantEquals(columnName))) - { - AddColumn(table, tableName, columnName); - } - } - - protected void AddColumn<T>(string columnName, out IEnumerable<string> sqls) - { - TableDefinition? table = DefinitionFactory.GetTableDefinition(typeof(T), SqlSyntax); - AddColumn(table, table.Name!, columnName, out sqls); - } - - private void AddColumn(TableDefinition table, string tableName, string columnName) - { - if (ColumnExists(tableName, columnName)) - { - return; - } - - ColumnDefinition? column = table.Columns.First(x => x.Name == columnName); - var createSql = SqlSyntax.Format(column); - - Execute.Sql(string.Format(SqlSyntax.AddColumn, SqlSyntax.GetQuotedTableName(tableName), createSql)).Do(); - } - - protected void AddColumn<T>(string tableName, string columnName, out IEnumerable<string> sqls) - { - TableDefinition? table = DefinitionFactory.GetTableDefinition(typeof(T), SqlSyntax); - AddColumn(table, tableName, columnName, out sqls); - } - - protected void AlterColumn<T>(string tableName, string columnName) - { - TableDefinition? table = DefinitionFactory.GetTableDefinition(typeof(T), SqlSyntax); - ColumnDefinition? column = table.Columns.First(x => x.Name == columnName); - SqlSyntax.Format(column, SqlSyntax.GetQuotedTableName(tableName), out IEnumerable<string>? sqls); - foreach (var sql in sqls) - { - Execute.Sql(sql).Do(); - } - } - - private void AddColumn(TableDefinition table, string tableName, string columnName, out IEnumerable<string> sqls) - { - if (ColumnExists(tableName, columnName)) - { - sqls = Enumerable.Empty<string>(); - return; - } - - ColumnDefinition? column = table.Columns.First(x => x.Name == columnName); - var createSql = SqlSyntax.Format(column, SqlSyntax.GetQuotedTableName(tableName), out sqls); - Execute.Sql(string.Format(SqlSyntax.AddColumn, SqlSyntax.GetQuotedTableName(tableName), createSql)).Do(); - } - - protected void ReplaceColumn<T>(string tableName, string currentName, string newName) - { - Execute.Sql(SqlSyntax.FormatColumnRename(tableName, currentName, newName)).Do(); - AlterColumn<T>(tableName, newName); - } - - protected bool TableExists(string tableName) - { - IEnumerable<string>? tables = SqlSyntax.GetTablesInSchema(Context.Database); - return tables.Any(x => x.InvariantEquals(tableName)); - } - - protected bool IndexExists(string indexName) - { - IEnumerable<Tuple<string, string, string, bool>>? indexes = SqlSyntax.GetDefinedIndexes(Context.Database); - return indexes.Any(x => x.Item2.InvariantEquals(indexName)); - } - - protected void CreateIndex<T>(string toCreate) - { - TableDefinition tableDef = DefinitionFactory.GetTableDefinition(typeof(T), Context.SqlContext.SqlSyntax); - IndexDefinition index = tableDef.Indexes.First(x => x.Name == toCreate); - new ExecuteSqlStatementExpression(Context) { SqlStatement = Context.SqlContext.SqlSyntax.Format(index) } - .Execute(); - } - - protected void DeleteIndex<T>(string toDelete) - { - if (!IndexExists(toDelete)) - { - return; - } - - TableDefinition tableDef = DefinitionFactory.GetTableDefinition(typeof(T), Context.SqlContext.SqlSyntax); - Delete.Index(toDelete).OnTable(tableDef.Name).Do(); - } - - protected bool PrimaryKeyExists(string tableName, string primaryKeyName) - { - return SqlSyntax.DoesPrimaryKeyExist(Context.Database, tableName, primaryKeyName); - } - - protected bool ColumnExists(string tableName, string columnName) - { - ColumnInfo[]? columns = SqlSyntax.GetColumnsInSchema(Context.Database).Distinct().ToArray(); - return columns.Any(x => x.TableName.InvariantEquals(tableName) && x.ColumnName.InvariantEquals(columnName)); - } - - protected string? ColumnType(string tableName, string columnName) - { - ColumnInfo[]? columns = SqlSyntax.GetColumnsInSchema(Context.Database).Distinct().ToArray(); - ColumnInfo? column = columns.FirstOrDefault(x => x.TableName.InvariantEquals(tableName) && x.ColumnName.InvariantEquals(columnName)); - return column?.DataType; - } - } -} diff --git a/src/Umbraco.Infrastructure/Migrations/MigrationBuilder.cs b/src/Umbraco.Infrastructure/Migrations/MigrationBuilder.cs index 40db38e05303..6edeeddeed99 100644 --- a/src/Umbraco.Infrastructure/Migrations/MigrationBuilder.cs +++ b/src/Umbraco.Infrastructure/Migrations/MigrationBuilder.cs @@ -8,6 +8,6 @@ public class MigrationBuilder : IMigrationBuilder public MigrationBuilder(IServiceProvider container) => _container = container; - public MigrationBase Build(Type migrationType, IMigrationContext context) => - (MigrationBase)_container.CreateInstance(migrationType, context); + public AsyncMigrationBase Build(Type migrationType, IMigrationContext context) => + (AsyncMigrationBase)_container.CreateInstance(migrationType, context); } diff --git a/src/Umbraco.Infrastructure/Migrations/MigrationContext.cs b/src/Umbraco.Infrastructure/Migrations/MigrationContext.cs index 4af60ece4255..173ee942c4fb 100644 --- a/src/Umbraco.Infrastructure/Migrations/MigrationContext.cs +++ b/src/Umbraco.Infrastructure/Migrations/MigrationContext.cs @@ -24,10 +24,6 @@ public MigrationContext(MigrationPlan plan, IUmbracoDatabase? database, ILogger< Logger = logger ?? throw new ArgumentNullException(nameof(logger)); } - // this is only internally exposed - [Obsolete("This will be removed in the V13, and replaced with a RebuildCache flag on the MigrationBase")] - internal IReadOnlyList<Type> PostMigrations => _postMigrations; - /// <inheritdoc /> public ILogger<IMigrationContext> Logger { get; } @@ -58,12 +54,4 @@ public void Complete() IsCompleted = true; } - - /// <inheritdoc /> - [Obsolete("This will be removed in the V13, and replaced with a RebuildCache flag on the MigrationBase, and a UmbracoPlanExecutedNotification.")] - public void AddPostMigration<TMigration>() - where TMigration : MigrationBase => - - // just adding - will be de-duplicated when executing - _postMigrations.Add(typeof(TMigration)); } diff --git a/src/Umbraco.Infrastructure/Migrations/MigrationExpressionBase.cs b/src/Umbraco.Infrastructure/Migrations/MigrationExpressionBase.cs index a2029cbef81d..117eb8fe7b56 100644 --- a/src/Umbraco.Infrastructure/Migrations/MigrationExpressionBase.cs +++ b/src/Umbraco.Infrastructure/Migrations/MigrationExpressionBase.cs @@ -70,6 +70,11 @@ public virtual void Execute() } else { + if (stmtBuilder.Length > 0) + { + stmtBuilder.Append(Environment.NewLine); + } + stmtBuilder.Append(line); } } diff --git a/src/Umbraco.Infrastructure/Migrations/MigrationPlan.cs b/src/Umbraco.Infrastructure/Migrations/MigrationPlan.cs index 3be0a01a4fe0..df8e85a5628d 100644 --- a/src/Umbraco.Infrastructure/Migrations/MigrationPlan.cs +++ b/src/Umbraco.Infrastructure/Migrations/MigrationPlan.cs @@ -115,9 +115,9 @@ private MigrationPlan Add(string? sourceState, string targetState, Type? migrati throw new ArgumentNullException(nameof(migration)); } - if (!migration.Implements<MigrationBase>()) + if (!migration.Implements<AsyncMigrationBase>()) { - throw new ArgumentException($"Type {migration.Name} does not implement IMigration.", nameof(migration)); + throw new ArgumentException($"Type {migration.Name} does not implement AsyncMigrationBase.", nameof(migration)); } sourceState = sourceState.Trim(); @@ -155,11 +155,11 @@ public MigrationPlan To(Guid targetState) /// Adds a transition to a target state through a migration. /// </summary> public MigrationPlan To<TMigration>(string targetState) - where TMigration : MigrationBase + where TMigration : AsyncMigrationBase => To(targetState, typeof(TMigration)); public MigrationPlan To<TMigration>(Guid targetState) - where TMigration : MigrationBase + where TMigration : AsyncMigrationBase => To(targetState, typeof(TMigration)); /// <summary> @@ -191,8 +191,8 @@ public MigrationPlan From(string? sourceState) /// </param> /// <param name="targetState">The new target state.</param> public MigrationPlan ToWithReplace<TMigrationNew, TMigrationRecover>(string recoverState, string targetState) - where TMigrationNew : MigrationBase - where TMigrationRecover : MigrationBase + where TMigrationNew : AsyncMigrationBase + where TMigrationRecover : AsyncMigrationBase { To<TMigrationNew>(targetState); From(recoverState).To<TMigrationRecover>(targetState); @@ -206,7 +206,7 @@ public MigrationPlan ToWithReplace<TMigrationNew, TMigrationRecover>(string reco /// <param name="recoverState">The previous target state, which we can recover from directly.</param> /// <param name="targetState">The new target state.</param> public MigrationPlan ToWithReplace<TMigrationNew>(string recoverState, string targetState) - where TMigrationNew : MigrationBase + where TMigrationNew : AsyncMigrationBase { To<TMigrationNew>(targetState); From(recoverState).To(targetState); diff --git a/src/Umbraco.Infrastructure/Migrations/MigrationPlanExecutor.cs b/src/Umbraco.Infrastructure/Migrations/MigrationPlanExecutor.cs index bd9b4d7f0526..48c8899c81f7 100644 --- a/src/Umbraco.Infrastructure/Migrations/MigrationPlanExecutor.cs +++ b/src/Umbraco.Infrastructure/Migrations/MigrationPlanExecutor.cs @@ -104,25 +104,19 @@ public MigrationPlanExecutor( { } + [Obsolete("Use ExecutePlan instead. Scheduled for removal in Umbraco 17.")] public string Execute(MigrationPlan plan, string fromState) => ExecutePlan(plan, fromState).FinalState; - /// <summary> - /// Executes the plan. - /// </summary> - /// <param name="plan">The migration plan to be executes.</param> - /// <param name="fromState">The state to start execution at.</param> - /// <returns>ExecutedMigrationPlan containing information about the plan execution, such as completion state and the steps that ran.</returns> - /// <remarks> - /// <para>Each migration in the plan, may or may not run in a scope depending on the type of plan.</para> - /// <para>A plan can complete partially, the changes of each completed migration will be saved.</para> - /// </remarks> - public ExecutedMigrationPlan ExecutePlan(MigrationPlan plan, string fromState) + /// <inheritdoc/> + [Obsolete("Use ExecutePlanAsync instead. Scheduled for removal in Umbraco 18.")] + public ExecutedMigrationPlan ExecutePlan(MigrationPlan plan, string fromState) => ExecutePlanAsync(plan, fromState).GetAwaiter().GetResult(); + + /// <inheritdoc/> + public async Task<ExecutedMigrationPlan> ExecutePlanAsync(MigrationPlan plan, string fromState) { plan.Validate(); - ExecutedMigrationPlan result = RunMigrationPlan(plan, fromState); - - HandlePostMigrations(result); + ExecutedMigrationPlan result = await RunMigrationPlanAsync(plan, fromState).ConfigureAwait(false); // If any completed migration requires us to rebuild cache we'll do that. if (_rebuildCache) @@ -134,40 +128,13 @@ public ExecutedMigrationPlan ExecutePlan(MigrationPlan plan, string fromState) // If any completed migration requires us to sign out the user we'll do that. if (_invalidateBackofficeUserAccess) { - RevokeBackofficeTokens().GetAwaiter().GetResult(); // should async all the way up at some point + await RevokeBackofficeTokens().ConfigureAwait(false); } return result; } - [Obsolete] - private void HandlePostMigrations(ExecutedMigrationPlan result) - { - // prepare and de-duplicate post-migrations, only keeping the 1st occurence - var executedTypes = new HashSet<Type>(); - - foreach (IMigrationContext executedMigrationContext in result.ExecutedMigrationContexts) - { - if (executedMigrationContext is MigrationContext migrationContext) - { - foreach (Type migrationContextPostMigration in migrationContext.PostMigrations) - { - if (executedTypes.Contains(migrationContextPostMigration)) - { - continue; - } - - _logger.LogInformation("PostMigration: {migrationContextFullName}.", migrationContextPostMigration.FullName); - MigrationBase postMigration = _migrationBuilder.Build(migrationContextPostMigration, executedMigrationContext); - postMigration.Run(); - - executedTypes.Add(migrationContextPostMigration); - } - } - } - } - - private ExecutedMigrationPlan RunMigrationPlan(MigrationPlan plan, string fromState) + private async Task<ExecutedMigrationPlan> RunMigrationPlanAsync(MigrationPlan plan, string fromState) { _logger.LogInformation("Starting '{MigrationName}'...", plan.Name); var nextState = fromState; @@ -188,13 +155,13 @@ private ExecutedMigrationPlan RunMigrationPlan(MigrationPlan plan, string fromSt try { - if (transition.MigrationType.IsAssignableTo(typeof(UnscopedMigrationBase))) + if (transition.MigrationType.IsAssignableTo(typeof(UnscopedAsyncMigrationBase))) { - executedMigrationContexts.Add(RunUnscopedMigration(transition, plan)); + executedMigrationContexts.Add(await RunUnscopedMigrationAsync(transition, plan).ConfigureAwait(false)); } else { - executedMigrationContexts.Add(RunScopedMigration(transition, plan)); + executedMigrationContexts.Add(await RunScopedMigrationAsync(transition, plan).ConfigureAwait(false)); } } catch (Exception exception) @@ -214,7 +181,6 @@ private ExecutedMigrationPlan RunMigrationPlan(MigrationPlan plan, string fromSt }; } - IEnumerable<IMigrationContext> nonCompletedMigrationsContexts = executedMigrationContexts.Where(x => x.IsCompleted is false); if (nonCompletedMigrationsContexts.Any()) { @@ -270,12 +236,12 @@ private ExecutedMigrationPlan RunMigrationPlan(MigrationPlan plan, string fromSt }; } - private MigrationContext RunUnscopedMigration(MigrationPlan.Transition transition, MigrationPlan plan) + private async Task<MigrationContext> RunUnscopedMigrationAsync(MigrationPlan.Transition transition, MigrationPlan plan) { using IUmbracoDatabase database = _databaseFactory.CreateDatabase(); var context = new MigrationContext(plan, database, _loggerFactory.CreateLogger<MigrationContext>(), () => OnComplete(plan, transition.TargetState)); - RunMigration(transition.MigrationType, context); + await RunMigrationAsync(transition.MigrationType, context).ConfigureAwait(false); return context; } @@ -285,7 +251,7 @@ private void OnComplete(MigrationPlan plan, string targetState) _keyValueService.SetValue(Constants.Conventions.Migrations.KeyValuePrefix + plan.Name, targetState); } - private MigrationContext RunScopedMigration(MigrationPlan.Transition transition, MigrationPlan plan) + private async Task<MigrationContext> RunScopedMigrationAsync(MigrationPlan.Transition transition, MigrationPlan plan) { // We want to suppress scope (service, etc...) notifications during a migration plan // execution. This is because if a package that doesn't have their migration plan @@ -300,7 +266,7 @@ private MigrationContext RunScopedMigration(MigrationPlan.Transition transition, _loggerFactory.CreateLogger<MigrationContext>(), () => OnComplete(plan, transition.TargetState)); - RunMigration(transition.MigrationType, context); + await RunMigrationAsync(transition.MigrationType, context).ConfigureAwait(false); // Ensure we mark the context as complete before the scope completes context.Complete(); @@ -311,10 +277,10 @@ private MigrationContext RunScopedMigration(MigrationPlan.Transition transition, } } - private void RunMigration(Type migrationType, MigrationContext context) + private async Task RunMigrationAsync(Type migrationType, MigrationContext context) { - MigrationBase migration = _migrationBuilder.Build(migrationType, context); - migration.Run(); + AsyncMigrationBase migration = _migrationBuilder.Build(migrationType, context); + await migration.RunAsync().ConfigureAwait(false); // If the migration requires clearing the cache set the flag, this will automatically only happen if it succeeds // Otherwise it'll error out before and return. @@ -333,7 +299,7 @@ private void RebuildCache() { _appCaches.RuntimeCache.Clear(); _appCaches.IsolatedCaches.ClearAllCaches(); - _databaseCacheRebuilder.Rebuild(); + _databaseCacheRebuilder.Rebuild(false); _distributedCache.RefreshAllPublishedSnapshot(); } diff --git a/src/Umbraco.Infrastructure/Migrations/NoopMigration.cs b/src/Umbraco.Infrastructure/Migrations/NoopMigration.cs index 9ce64977c0d2..bc94591dbbd0 100644 --- a/src/Umbraco.Infrastructure/Migrations/NoopMigration.cs +++ b/src/Umbraco.Infrastructure/Migrations/NoopMigration.cs @@ -1,14 +1,12 @@ + namespace Umbraco.Cms.Infrastructure.Migrations; -public class NoopMigration : MigrationBase +public class NoopMigration : AsyncMigrationBase { public NoopMigration(IMigrationContext context) : base(context) - { - } + { } - protected override void Migrate() - { - // nop - } + protected override Task MigrateAsync() + => Task.CompletedTask; } diff --git a/src/Umbraco.Infrastructure/Migrations/PostMigrations/CacheRebuilder.cs b/src/Umbraco.Infrastructure/Migrations/PostMigrations/CacheRebuilder.cs index 618391b4adad..76ce8af339d4 100644 --- a/src/Umbraco.Infrastructure/Migrations/PostMigrations/CacheRebuilder.cs +++ b/src/Umbraco.Infrastructure/Migrations/PostMigrations/CacheRebuilder.cs @@ -27,7 +27,7 @@ public CacheRebuilder( /// <inheritdoc /> public void Rebuild() { - _databaseCacheRebuilder.Rebuild(); + _databaseCacheRebuilder.Rebuild(false); _distributedCache.RefreshAllPublishedSnapshot(); } } diff --git a/src/Umbraco.Infrastructure/Migrations/UnscopedAsyncMigrationBase.cs b/src/Umbraco.Infrastructure/Migrations/UnscopedAsyncMigrationBase.cs new file mode 100644 index 000000000000..0b5ced41a4c4 --- /dev/null +++ b/src/Umbraco.Infrastructure/Migrations/UnscopedAsyncMigrationBase.cs @@ -0,0 +1,34 @@ +using Umbraco.Cms.Infrastructure.Scoping; + +namespace Umbraco.Cms.Infrastructure.Migrations; + +/// <summary> +/// Base class for creating a migration that does not have a scope provided for it. +/// </summary> +public abstract class UnscopedAsyncMigrationBase : AsyncMigrationBase +{ + /// <summary> + /// Initializes a new instance of the <see cref="UnscopedAsyncMigrationBase" /> class. + /// </summary> + /// <param name="context">A migration context.</param> + protected UnscopedAsyncMigrationBase(IMigrationContext context) + : base(context) + { } + + /// <summary> + /// <para>Scope the database used by the migration builder.</para> + /// <para>This is used with <see cref="UnscopedAsyncMigrationBase"/> when you need to execute something before the scope is created + /// but later need to have your queries scoped in a transaction.</para> + /// </summary> + /// <param name="scope">The scope to get the database from.</param> + /// <exception cref="InvalidOperationException">If the migration is missing or has a malformed MigrationContext, this exception is thrown.</exception> + protected void ScopeDatabase(IScope scope) + { + if (Context is not MigrationContext context) + { + throw new InvalidOperationException("Cannot scope database because context is not a MigrationContext"); + } + + context.Database = scope.Database; + } +} diff --git a/src/Umbraco.Infrastructure/Migrations/UnscopedMigrationBase.cs b/src/Umbraco.Infrastructure/Migrations/UnscopedMigrationBase.cs index 910b9753de27..a182d2237021 100644 --- a/src/Umbraco.Infrastructure/Migrations/UnscopedMigrationBase.cs +++ b/src/Umbraco.Infrastructure/Migrations/UnscopedMigrationBase.cs @@ -1,31 +1,26 @@ -using Umbraco.Cms.Infrastructure.Scoping; - namespace Umbraco.Cms.Infrastructure.Migrations; -/// <summary> -/// Base class for creating a migration that does not have a scope provided for it. -/// </summary> -public abstract class UnscopedMigrationBase : MigrationBase +/// <inheritdoc /> +public abstract class UnscopedMigrationBase : UnscopedAsyncMigrationBase { + /// <summary> + /// Initializes a new instance of the <see cref="UnscopedMigrationBase" /> class. + /// </summary> + /// <param name="context">The context.</param> protected UnscopedMigrationBase(IMigrationContext context) : base(context) + { } + + /// <inheritdoc /> + protected override Task MigrateAsync() { + Migrate(); + + return Task.CompletedTask; } /// <summary> - /// <para>Scope the database used by the migration builder.</para> - /// <para>This is used with <see cref="UnscopedMigrationBase"/> when you need to execute something before the scope is created - /// but later need to have your queries scoped in a transaction.</para> + /// Executes the migration. /// </summary> - /// <param name="scope">The scope to get the database from.</param> - /// <exception cref="InvalidOperationException">If the migration is missing or has a malformed MigrationContext, this exception is thrown.</exception> - protected void ScopeDatabase(IScope scope) - { - if (Context is not MigrationContext context) - { - throw new InvalidOperationException("Cannot scope database because context is not a MigrationContext"); - } - - context.Database = scope.Database; - } + protected abstract void Migrate(); } diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/Upgrader.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/Upgrader.cs index b73967f400b0..921a6f95f781 100644 --- a/src/Umbraco.Infrastructure/Migrations/Upgrade/Upgrader.cs +++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/Upgrader.cs @@ -30,13 +30,20 @@ public class Upgrader /// </summary> public virtual string StateValueKey => Constants.Conventions.Migrations.KeyValuePrefix + Name; + [Obsolete("Use ExecuteAsync instead. Scheduled for removal in Umbraco 18.")] + public ExecutedMigrationPlan Execute( + IMigrationPlanExecutor migrationPlanExecutor, + ICoreScopeProvider scopeProvider, + IKeyValueService keyValueService) + => ExecuteAsync(migrationPlanExecutor, scopeProvider, keyValueService).GetAwaiter().GetResult(); + /// <summary> /// Executes. /// </summary> /// <param name="migrationPlanExecutor"></param> /// <param name="scopeProvider">A scope provider.</param> /// <param name="keyValueService">A key-value service.</param> - public ExecutedMigrationPlan Execute( + public async Task<ExecutedMigrationPlan> ExecuteAsync( IMigrationPlanExecutor migrationPlanExecutor, ICoreScopeProvider scopeProvider, IKeyValueService keyValueService) @@ -53,7 +60,7 @@ public ExecutedMigrationPlan Execute( string initialState = GetInitialState(scopeProvider, keyValueService); - ExecutedMigrationPlan result = migrationPlanExecutor.ExecutePlan(Plan, initialState); + ExecutedMigrationPlan result = await migrationPlanExecutor.ExecutePlanAsync(Plan, initialState).ConfigureAwait(false); // This should never happen, if the final state comes back as null or equal to the initial state // it means that no transitions was successful, which means it cannot be a successful migration @@ -86,11 +93,4 @@ private string GetInitialState(ICoreScopeProvider scopeProvider, IKeyValueServic return currentState; } - - private void SetState(string state, ICoreScopeProvider scopeProvider, IKeyValueService keyValueService) - { - using ICoreScope scope = scopeProvider.CreateCoreScope(); - keyValueService.SetValue(StateValueKey, state); - scope.Complete(); - } } diff --git a/src/Umbraco.Infrastructure/ModelsBuilder/Building/TextBuilder.cs b/src/Umbraco.Infrastructure/ModelsBuilder/Building/TextBuilder.cs index 81e08a8d591b..f46e0d66ffb4 100644 --- a/src/Umbraco.Infrastructure/ModelsBuilder/Building/TextBuilder.cs +++ b/src/Umbraco.Infrastructure/ModelsBuilder/Building/TextBuilder.cs @@ -51,7 +51,15 @@ public TextBuilder() /// Outputs an "auto-generated" header to a string builder. /// </summary> /// <param name="sb">The string builder.</param> - public static void WriteHeader(StringBuilder sb) => TextHeaderWriter.WriteHeader(sb); + [Obsolete("Please use the overload taking all parameters. Scheduled for removal in Umbraco 17.")] + public static void WriteHeader(StringBuilder sb) => WriteHeader(sb, true); + + /// <summary> + /// Outputs an "auto-generated" header to a string builder. + /// </summary> + /// <param name="sb">The string builder.</param> + /// <param name="includeVersion">Flag indicating whether the tool version number should be included in the output.</param> + public static void WriteHeader(StringBuilder sb, bool includeVersion) => TextHeaderWriter.WriteHeader(sb, includeVersion); /// <summary> /// Outputs a generated model to a string builder. @@ -60,7 +68,7 @@ public TextBuilder() /// <param name="typeModel">The model to generate.</param> public void Generate(StringBuilder sb, TypeModel typeModel) { - WriteHeader(sb); + WriteHeader(sb, Config.IncludeVersionNumberInGeneratedModels); foreach (var t in TypesUsing) { @@ -83,7 +91,7 @@ public void Generate(StringBuilder sb, TypeModel typeModel) /// <param name="typeModels">The models to generate.</param> public void Generate(StringBuilder sb, IEnumerable<TypeModel> typeModels) { - WriteHeader(sb); + WriteHeader(sb, Config.IncludeVersionNumberInGeneratedModels); foreach (var t in TypesUsing) { diff --git a/src/Umbraco.Infrastructure/ModelsBuilder/Building/TextHeaderWriter.cs b/src/Umbraco.Infrastructure/ModelsBuilder/Building/TextHeaderWriter.cs index 5a532cbdbac1..9ab59516d799 100644 --- a/src/Umbraco.Infrastructure/ModelsBuilder/Building/TextHeaderWriter.cs +++ b/src/Umbraco.Infrastructure/ModelsBuilder/Building/TextHeaderWriter.cs @@ -8,13 +8,30 @@ internal static class TextHeaderWriter /// Outputs an "auto-generated" header to a string builder. /// </summary> /// <param name="sb">The string builder.</param> - public static void WriteHeader(StringBuilder sb) + [Obsolete("Please use the overload taking all parameters. Scheduled for removal in Umbraco 17.")] + public static void WriteHeader(StringBuilder sb) => WriteHeader(sb, true); + + /// <summary> + /// Outputs an "auto-generated" header to a string builder. + /// </summary> + /// <param name="sb">The string builder.</param> + /// <param name="includeVersion">Flag indicating whether the tool version number should be included in the output.</param> + public static void WriteHeader(StringBuilder sb, bool includeVersion) { sb.Append("//------------------------------------------------------------------------------\n"); sb.Append("// <auto-generated>\n"); sb.Append("// This code was generated by a tool.\n"); sb.Append("//\n"); - sb.AppendFormat("// Umbraco.ModelsBuilder.Embedded v{0}\n", ApiVersion.Current.Version); + + if (includeVersion) + { + sb.AppendFormat("// Umbraco.ModelsBuilder.Embedded v{0}\n", ApiVersion.Current.Version); + } + else + { + sb.Append("// Umbraco.ModelsBuilder.Embedded\n"); + } + sb.Append("//\n"); sb.Append("// Changes to this file will be lost if the code is regenerated.\n"); sb.Append("// </auto-generated>\n"); diff --git a/src/Umbraco.Infrastructure/ModelsBuilder/Options/ConfigurePropertySettingsOptions.cs b/src/Umbraco.Infrastructure/ModelsBuilder/Options/ConfigurePropertySettingsOptions.cs new file mode 100644 index 000000000000..fe7cfb45cf85 --- /dev/null +++ b/src/Umbraco.Infrastructure/ModelsBuilder/Options/ConfigurePropertySettingsOptions.cs @@ -0,0 +1,37 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Infrastructure.ModelsBuilder.Options; + +public class ConfigurePropertySettingsOptions : + IConfigureOptions<ContentPropertySettings>, + IConfigureOptions<MemberPropertySettings>, + IConfigureOptions<MediaPropertySettings> +{ + + public void Configure(ContentPropertySettings options) + { + var reservedProperties = typeof(IPublishedContent).GetPublicProperties().Select(x => x.Name).ToHashSet(); + var reservedMethods = typeof(IPublishedContent).GetPublicMethods().Select(x => x.Name).ToHashSet(); + options.AddReservedFieldNames(reservedProperties); + options.AddReservedFieldNames(reservedMethods); + } + + public void Configure(MemberPropertySettings options) + { + var reservedProperties = typeof(IPublishedMember).GetPublicProperties().Select(x => x.Name).ToHashSet(); + var reservedMethods = typeof(IPublishedMember).GetPublicMethods().Select(x => x.Name).ToHashSet(); + options.AddReservedFieldNames(reservedProperties); + options.AddReservedFieldNames(reservedMethods); + } + + public void Configure(MediaPropertySettings options) + { + var reservedProperties = typeof(IPublishedContent).GetPublicProperties().Select(x => x.Name).ToHashSet(); + var reservedMethods = typeof(IPublishedContent).GetPublicMethods().Select(x => x.Name).ToHashSet(); + options.AddReservedFieldNames(reservedProperties); + options.AddReservedFieldNames(reservedMethods); + } +} diff --git a/src/Umbraco.Infrastructure/Packaging/AsyncPackageMigrationBase.cs b/src/Umbraco.Infrastructure/Packaging/AsyncPackageMigrationBase.cs new file mode 100644 index 000000000000..e9bb8df92fc2 --- /dev/null +++ b/src/Umbraco.Infrastructure/Packaging/AsyncPackageMigrationBase.cs @@ -0,0 +1,51 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Strings; +using Umbraco.Cms.Infrastructure.Migrations; + +namespace Umbraco.Cms.Infrastructure.Packaging; + +public abstract class AsyncPackageMigrationBase : AsyncMigrationBase +{ + private readonly IContentTypeBaseServiceProvider _contentTypeBaseServiceProvider; + private readonly MediaFileManager _mediaFileManager; + private readonly IMediaService _mediaService; + private readonly MediaUrlGeneratorCollection _mediaUrlGenerators; + private readonly IOptions<PackageMigrationSettings> _packageMigrationsSettings; + private readonly IPackagingService _packagingService; + private readonly IShortStringHelper _shortStringHelper; + + public AsyncPackageMigrationBase( + IPackagingService packagingService, + IMediaService mediaService, + MediaFileManager mediaFileManager, + MediaUrlGeneratorCollection mediaUrlGenerators, + IShortStringHelper shortStringHelper, + IContentTypeBaseServiceProvider contentTypeBaseServiceProvider, + IMigrationContext context, + IOptions<PackageMigrationSettings> packageMigrationsSettings) + : base(context) + { + _packagingService = packagingService; + _mediaService = mediaService; + _mediaFileManager = mediaFileManager; + _mediaUrlGenerators = mediaUrlGenerators; + _shortStringHelper = shortStringHelper; + _contentTypeBaseServiceProvider = contentTypeBaseServiceProvider; + _packageMigrationsSettings = packageMigrationsSettings; + } + + public IImportPackageBuilder ImportPackage => BeginBuild( + new ImportPackageBuilder( + _packagingService, + _mediaService, + _mediaFileManager, + _mediaUrlGenerators, + _shortStringHelper, + _contentTypeBaseServiceProvider, + Context, + _packageMigrationsSettings)); +} diff --git a/src/Umbraco.Infrastructure/Packaging/AutomaticPackageMigrationPlan.cs b/src/Umbraco.Infrastructure/Packaging/AutomaticPackageMigrationPlan.cs index d8becf0bfacd..59a921364f16 100644 --- a/src/Umbraco.Infrastructure/Packaging/AutomaticPackageMigrationPlan.cs +++ b/src/Umbraco.Infrastructure/Packaging/AutomaticPackageMigrationPlan.cs @@ -21,8 +21,7 @@ public abstract class AutomaticPackageMigrationPlan : PackageMigrationPlan /// <param name="packageName">The package name that the plan is for. If the package has a package.manifest these must match.</param> protected AutomaticPackageMigrationPlan(string packageName) : this(packageName, packageName) - { - } + { } /// <summary> /// Initializes a new instance of the <see cref="AutomaticPackageMigrationPlan" /> class. @@ -31,8 +30,7 @@ protected AutomaticPackageMigrationPlan(string packageName) /// <param name="planName">The plan name for the package. This should be the same name as the package name, if there is only one plan in the package.</param> protected AutomaticPackageMigrationPlan(string packageName, string planName) : this(null!, packageName, planName) - { - } + { } /// <summary> /// Initializes a new instance of the <see cref="AutomaticPackageMigrationPlan" /> class. @@ -42,8 +40,7 @@ protected AutomaticPackageMigrationPlan(string packageName, string planName) /// <param name="planName">The plan name for the package. This should be the same name as the package name, if there is only one plan in the package.</param> protected AutomaticPackageMigrationPlan(string packageId, string packageName, string planName) : base(packageId, packageName, planName) - { - } + { } /// <inheritdoc /> protected sealed override void DefinePlan() @@ -59,7 +56,7 @@ protected sealed override void DefinePlan() /// <summary> /// Provides a migration that imports an embedded package data manifest. /// </summary> - private class MigrateToPackageData : PackageMigrationBase + private sealed class MigrateToPackageData : AsyncPackageMigrationBase { /// <summary> /// Initializes a new instance of the <see cref="MigrateToPackageData" /> class. @@ -82,15 +79,16 @@ public MigrateToPackageData( IMigrationContext context, IOptions<PackageMigrationSettings> options) : base(packagingService, mediaService, mediaFileManager, mediaUrlGenerators, shortStringHelper, contentTypeBaseServiceProvider, context, options) - { - } + { } /// <inheritdoc /> - protected override void Migrate() + protected override Task MigrateAsync() { var plan = (AutomaticPackageMigrationPlan)Context.Plan; ImportPackage.FromEmbeddedResource(plan.GetType()).Do(); + + return Task.CompletedTask; } } } diff --git a/src/Umbraco.Infrastructure/Packaging/IImportPackageBuilder.cs b/src/Umbraco.Infrastructure/Packaging/IImportPackageBuilder.cs index f826dd9dfe05..8265fa5d14ae 100644 --- a/src/Umbraco.Infrastructure/Packaging/IImportPackageBuilder.cs +++ b/src/Umbraco.Infrastructure/Packaging/IImportPackageBuilder.cs @@ -7,7 +7,7 @@ namespace Umbraco.Cms.Infrastructure.Packaging; public interface IImportPackageBuilder : IFluentBuilder { IExecutableBuilder FromEmbeddedResource<TPackageMigration>() - where TPackageMigration : PackageMigrationBase; + where TPackageMigration : AsyncPackageMigrationBase; IExecutableBuilder FromEmbeddedResource(Type packageMigrationType); diff --git a/src/Umbraco.Infrastructure/Packaging/ImportPackageBuilder.cs b/src/Umbraco.Infrastructure/Packaging/ImportPackageBuilder.cs index 8b28628e4cd9..e557181a517e 100644 --- a/src/Umbraco.Infrastructure/Packaging/ImportPackageBuilder.cs +++ b/src/Umbraco.Infrastructure/Packaging/ImportPackageBuilder.cs @@ -38,7 +38,7 @@ public ImportPackageBuilder( public void Do() => Expression.Execute(); public IExecutableBuilder FromEmbeddedResource<TPackageMigration>() - where TPackageMigration : PackageMigrationBase + where TPackageMigration : AsyncPackageMigrationBase { Expression.EmbeddedResourceMigrationType = typeof(TPackageMigration); return this; diff --git a/src/Umbraco.Infrastructure/Packaging/PackageMigrationBase.cs b/src/Umbraco.Infrastructure/Packaging/PackageMigrationBase.cs index 6f0355f6741d..9bd820a1e978 100644 --- a/src/Umbraco.Infrastructure/Packaging/PackageMigrationBase.cs +++ b/src/Umbraco.Infrastructure/Packaging/PackageMigrationBase.cs @@ -8,17 +8,11 @@ namespace Umbraco.Cms.Infrastructure.Packaging; -public abstract class PackageMigrationBase : MigrationBase +/// <inheritdoc /> +[Obsolete("Use AsyncPackageMigrationBase instead. Scheduled for removal in Umbraco 18.")] +public abstract class PackageMigrationBase : AsyncPackageMigrationBase { - private readonly IContentTypeBaseServiceProvider _contentTypeBaseServiceProvider; - private readonly MediaFileManager _mediaFileManager; - private readonly IMediaService _mediaService; - private readonly MediaUrlGeneratorCollection _mediaUrlGenerators; - private readonly IOptions<PackageMigrationSettings> _packageMigrationsSettings; - private readonly IPackagingService _packagingService; - private readonly IShortStringHelper _shortStringHelper; - - public PackageMigrationBase( + protected PackageMigrationBase( IPackagingService packagingService, IMediaService mediaService, MediaFileManager mediaFileManager, @@ -27,27 +21,19 @@ public PackageMigrationBase( IContentTypeBaseServiceProvider contentTypeBaseServiceProvider, IMigrationContext context, IOptions<PackageMigrationSettings> packageMigrationsSettings) - : base(context) - { - _packagingService = packagingService; - _mediaService = mediaService; - _mediaFileManager = mediaFileManager; - _mediaUrlGenerators = mediaUrlGenerators; - _shortStringHelper = shortStringHelper; - _contentTypeBaseServiceProvider = contentTypeBaseServiceProvider; - _packageMigrationsSettings = packageMigrationsSettings; - } + : base(packagingService, mediaService, mediaFileManager, mediaUrlGenerators, shortStringHelper, contentTypeBaseServiceProvider, context, packageMigrationsSettings) + { } - public IImportPackageBuilder ImportPackage => BeginBuild( - new ImportPackageBuilder( - _packagingService, - _mediaService, - _mediaFileManager, - _mediaUrlGenerators, - _shortStringHelper, - _contentTypeBaseServiceProvider, - Context, - _packageMigrationsSettings)); + /// <inheritdoc /> + protected override Task MigrateAsync() + { + Migrate(); + return Task.CompletedTask; + } + /// <summary> + /// Executes the migration. + /// </summary> + protected abstract void Migrate(); } diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DataTypeUsageRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DataTypeUsageRepository.cs index 32ea15718b77..dd58cd0716c0 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DataTypeUsageRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DataTypeUsageRepository.cs @@ -38,7 +38,7 @@ public bool HasSavedValues(int dataTypeId) return database.ExecuteScalar<bool>(hasValueQuery); } - public async Task<bool> HasSavedValuesAsync(Guid dataTypeKey) + public Task<bool> HasSavedValuesAsync(Guid dataTypeKey) { IUmbracoDatabase? database = _scopeAccessor.AmbientScope?.Database; @@ -59,6 +59,6 @@ public async Task<bool> HasSavedValuesAsync(Guid dataTypeKey) Sql<ISqlContext> hasValueQuery = database.SqlContext.Sql() .SelectAnyIfExists(selectQuery); - return await Task.FromResult(database.ExecuteScalar<bool>(hasValueQuery)); + return Task.FromResult(database.ExecuteScalar<bool>(hasValueQuery)); } } diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/LocalFileSystemTemporaryFileRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/LocalFileSystemTemporaryFileRepository.cs index 409c2d8c915b..94cb6a9bf0e4 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/LocalFileSystemTemporaryFileRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/LocalFileSystemTemporaryFileRepository.cs @@ -81,12 +81,11 @@ public async Task SaveAsync(TemporaryFileModel model) var fullFileName = Path.Combine(fileDirectory.FullName, model.FileName); var metadataFileName = Path.Combine(fileDirectory.FullName, MetaDataFileName); - await Task.WhenAll( - CreateActualFile(model, fullFileName), - CreateMetadataFile(metadataFileName, new FileMetaData() - { - AvailableUntil = model.AvailableUntil - })); + await CreateActualFile(model, fullFileName); + await CreateMetadataFile(metadataFileName, new FileMetaData() + { + AvailableUntil = model.AvailableUntil + }); } public Task DeleteAsync(Guid key) @@ -120,7 +119,10 @@ public async Task<IEnumerable<Guid>> CleanUpOldTempFiles(DateTime now) } } - await Task.WhenAll(keysToDelete.Select(DeleteAsync).ToArray()); + foreach (Guid keyToDelete in keysToDelete) + { + await DeleteAsync(keyToDelete); + } return keysToDelete; } diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/PropertyTypeUsageRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/PropertyTypeUsageRepository.cs index bca7cecd7a72..dc7ba7e1bd33 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/PropertyTypeUsageRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/PropertyTypeUsageRepository.cs @@ -45,7 +45,7 @@ public bool HasSavedPropertyValues(string propertyTypeAlias) return database.ExecuteScalar<bool>(hasValuesQuery); } - public async Task<bool> HasSavedPropertyValuesAsync(Guid contentTypeKey, string propertyAlias) + public Task<bool> HasSavedPropertyValuesAsync(Guid contentTypeKey, string propertyAlias) { IUmbracoDatabase? database = _scopeAccessor.AmbientScope?.Database; @@ -67,10 +67,10 @@ public async Task<bool> HasSavedPropertyValuesAsync(Guid contentTypeKey, string Sql<ISqlContext> hasValuesQuery = database.SqlContext.Sql() .SelectAnyIfExists(selectQuery); - return database.ExecuteScalar<bool>(hasValuesQuery); + return Task.FromResult(database.ExecuteScalar<bool>(hasValuesQuery)); } - public async Task<bool> ContentTypeExistAsync(Guid contentTypeKey) + public Task<bool> ContentTypeExistAsync(Guid contentTypeKey) { IUmbracoDatabase? database = _scopeAccessor.AmbientScope?.Database; @@ -88,7 +88,7 @@ public async Task<bool> ContentTypeExistAsync(Guid contentTypeKey) Sql<ISqlContext> hasValuesQuery = database.SqlContext.Sql() .SelectAnyIfExists(selectQuery); - return database.ExecuteScalar<bool>(hasValuesQuery); + return Task.FromResult(database.ExecuteScalar<bool>(hasValuesQuery)); } diff --git a/src/Umbraco.Infrastructure/PropertyEditors/BlockEditorMinMaxValidatorBase.cs b/src/Umbraco.Infrastructure/PropertyEditors/BlockEditorMinMaxValidatorBase.cs index f8f1dde8a1eb..f970ae8f9a2d 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/BlockEditorMinMaxValidatorBase.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/BlockEditorMinMaxValidatorBase.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. using System.ComponentModel.DataAnnotations; @@ -16,12 +16,22 @@ internal abstract class BlockEditorMinMaxValidatorBase<TValue, TLayout> : IValue where TValue : BlockValue<TLayout>, new() where TLayout : class, IBlockLayoutItem, new() { + /// <summary> + /// Initializes a new instance of the <see cref="BlockEditorMinMaxValidatorBase{TValue, TLayout}"/> class. + /// </summary> protected BlockEditorMinMaxValidatorBase(ILocalizedTextService textService) => TextService = textService; + /// <summary> + /// Gets the <see cref="ILocalizedTextService"/> + /// </summary> protected ILocalizedTextService TextService { get; } + /// <inheritdoc/> public abstract IEnumerable<ValidationResult> Validate(object? value, string? valueType, object? dataTypeConfiguration, PropertyValidationContext validationContext); + /// <summary> + /// Validates the number of blocks are within the configured minimum and maximum values. + /// </summary> protected IEnumerable<ValidationResult> ValidateNumberOfBlocks(BlockEditorData<TValue, TLayout>? blockEditorData, int? min, int? max) { var numberOfBlocks = blockEditorData?.Layout?.Count() ?? 0; @@ -35,8 +45,8 @@ protected IEnumerable<ValidationResult> ValidateNumberOfBlocks(BlockEditorData<T TextService.Localize( "validation", "entriesShort", - new[] { min.ToString(), (min - numberOfBlocks).ToString(), }), - new[] { "minCount" }); + [min.ToString(), (min - numberOfBlocks).ToString(),]), + ["value"]); } } @@ -46,8 +56,8 @@ protected IEnumerable<ValidationResult> ValidateNumberOfBlocks(BlockEditorData<T TextService.Localize( "validation", "entriesExceed", - new[] { max.ToString(), (numberOfBlocks - max).ToString(), }), - new[] { "maxCount" }); + [max.ToString(), (numberOfBlocks - max).ToString(),]), + ["value"]); } } } diff --git a/src/Umbraco.Infrastructure/PropertyEditors/BlockEditorPropertyValueEditor.cs b/src/Umbraco.Infrastructure/PropertyEditors/BlockEditorPropertyValueEditor.cs index a43921156a3b..d157b223b32e 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/BlockEditorPropertyValueEditor.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/BlockEditorPropertyValueEditor.cs @@ -20,8 +20,6 @@ public abstract class BlockEditorPropertyValueEditor<TValue, TLayout> : BlockVal where TValue : BlockValue<TLayout>, new() where TLayout : class, IBlockLayoutItem, new() { - private readonly IJsonSerializer _jsonSerializer; - [Obsolete("Please use the non-obsolete constructor. Will be removed in V16.")] protected BlockEditorPropertyValueEditor( DataEditorAttribute attribute, @@ -52,7 +50,12 @@ protected BlockEditorPropertyValueEditor( IIOHelper ioHelper, DataEditorAttribute attribute) : base(propertyEditors, dataTypeConfigurationCache, shortStringHelper, jsonSerializer, dataValueReferenceFactories, blockEditorVarianceHandler, languageService, ioHelper, attribute) => - _jsonSerializer = jsonSerializer; + JsonSerializer = jsonSerializer; + + /// <summary> + /// Gets the <see cref="IJsonSerializer"/>. + /// </summary> + protected IJsonSerializer JsonSerializer { get; } /// <inheritdoc /> public override IEnumerable<UmbracoEntityReference> GetReferences(object? value) @@ -143,6 +146,6 @@ public override object ToEditor(IProperty property, string? culture = null, stri MapBlockValueFromEditor(blockEditorData.BlockValue); // return json - return _jsonSerializer.Serialize(blockEditorData.BlockValue); + return JsonSerializer.Serialize(blockEditorData.BlockValue); } } diff --git a/src/Umbraco.Infrastructure/PropertyEditors/BlockEditorValidatorBase.cs b/src/Umbraco.Infrastructure/PropertyEditors/BlockEditorValidatorBase.cs index d0338f185229..419b84845aef 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/BlockEditorValidatorBase.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/BlockEditorValidatorBase.cs @@ -51,6 +51,12 @@ protected IEnumerable<ElementTypeValidationModel> GetBlockEditorDataValidation(B return elementTypeValidation; } + protected virtual string ContentDataGroupJsonPath => + nameof(BlockValue<TLayout>.ContentData).ToFirstLowerInvariant(); + + protected virtual string SettingsDataGroupJsonPath => + nameof(BlockValue<TLayout>.SettingsData).ToFirstLowerInvariant(); + private IEnumerable<ElementTypeValidationModel> GetBlockEditorDataValidation(BlockEditorData<TValue, TLayout> blockEditorData, string? culture, string? segment) { // There is no guarantee that the client will post data for every property defined in the Element Type but we still @@ -74,8 +80,8 @@ private IEnumerable<ElementTypeValidationModel> GetBlockEditorDataValidation(Blo var itemDataGroups = new[] { - new { Path = nameof(BlockValue<TLayout>.ContentData).ToFirstLowerInvariant(), Items = blockEditorData.BlockValue.ContentData.Where(cd => exposedContentKeys.Contains(cd.Key)).ToArray() }, - new { Path = nameof(BlockValue<TLayout>.SettingsData).ToFirstLowerInvariant(), Items = blockEditorData.BlockValue.SettingsData.Where(sd => exposedSettingsKeys.Contains(sd.Key)).ToArray() } + new { Path = ContentDataGroupJsonPath, Items = blockEditorData.BlockValue.ContentData.Where(cd => exposedContentKeys.Contains(cd.Key)).ToArray() }, + new { Path = SettingsDataGroupJsonPath, Items = blockEditorData.BlockValue.SettingsData.Where(sd => exposedSettingsKeys.Contains(sd.Key)).ToArray() } }; var valuesJsonPathPart = nameof(BlockItemData.Values).ToFirstLowerInvariant(); diff --git a/src/Umbraco.Infrastructure/PropertyEditors/BlockListPropertyEditor.cs b/src/Umbraco.Infrastructure/PropertyEditors/BlockListPropertyEditor.cs index eee452892f51..816fad7098df 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/BlockListPropertyEditor.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/BlockListPropertyEditor.cs @@ -9,7 +9,7 @@ namespace Umbraco.Cms.Core.PropertyEditors; /// <summary> -/// Represents a block list property editor. +/// Represents a block list property editor. /// </summary> [DataEditor( Constants.PropertyEditors.Aliases.BlockList, @@ -19,6 +19,9 @@ public class BlockListPropertyEditor : BlockListPropertyEditorBase { private readonly IIOHelper _ioHelper; + /// <summary> + /// Initializes a new instance of the <see cref="BlockListPropertyEditor"/> class. + /// </summary> public BlockListPropertyEditor( IDataValueEditorFactory dataValueEditorFactory, IIOHelper ioHelper, @@ -27,6 +30,9 @@ public BlockListPropertyEditor( : base(dataValueEditorFactory, blockValuePropertyIndexValueFactory, jsonSerializer) => _ioHelper = ioHelper; + /// <summary> + /// Initializes a new instance of the <see cref="BlockListPropertyEditor"/> class. + /// </summary> [Obsolete("Use constructor that doesn't take PropertyEditorCollection, scheduled for removal in V15")] public BlockListPropertyEditor( IDataValueEditorFactory dataValueEditorFactory, @@ -38,6 +44,7 @@ public BlockListPropertyEditor( { } + /// <inheritdoc/> public override bool SupportsConfigurableElements => true; /// <inheritdoc /> @@ -50,6 +57,7 @@ public BlockListPropertyEditor( return valueEditor.MergePartialPropertyValueForCulture(sourceValue, targetValue, culture); } + /// <inheritdoc/> public override object? MergeVariantInvariantPropertyValue( object? sourceValue, object? targetValue, @@ -60,10 +68,7 @@ public BlockListPropertyEditor( return valueEditor.MergeVariantInvariantPropertyValue(sourceValue, targetValue, canUpdateInvariantData, allowedCultures); } - #region Pre Value Editor - + /// <inheritdoc/> protected override IConfigurationEditor CreateConfigurationEditor() => new BlockListConfigurationEditor(_ioHelper); - - #endregion } diff --git a/src/Umbraco.Infrastructure/PropertyEditors/BlockListPropertyEditorBase.cs b/src/Umbraco.Infrastructure/PropertyEditors/BlockListPropertyEditorBase.cs index 9478c8e8056f..feb44a784c24 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/BlockListPropertyEditorBase.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/BlockListPropertyEditorBase.cs @@ -11,6 +11,7 @@ using Umbraco.Cms.Core.Serialization; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Strings; +using Umbraco.Cms.Infrastructure.PropertyEditors.Validators; using StaticServiceProvider = Umbraco.Cms.Core.DependencyInjection.StaticServiceProvider; namespace Umbraco.Cms.Core.PropertyEditors; @@ -23,12 +24,18 @@ public abstract class BlockListPropertyEditorBase : DataEditor private readonly IBlockValuePropertyIndexValueFactory _blockValuePropertyIndexValueFactory; private readonly IJsonSerializer _jsonSerializer; + /// <summary> + /// Initializes a new instance of the <see cref="BlockListPropertyEditorBase"/> class. + /// </summary> [Obsolete("Use non-obsoleted ctor. This will be removed in Umbraco 15.")] protected BlockListPropertyEditorBase(IDataValueEditorFactory dataValueEditorFactory, IBlockValuePropertyIndexValueFactory blockValuePropertyIndexValueFactory) : this(dataValueEditorFactory,blockValuePropertyIndexValueFactory, StaticServiceProvider.Instance.GetRequiredService<IJsonSerializer>()) { } + /// <summary> + /// Initializes a new instance of the <see cref="BlockListPropertyEditorBase"/> class. + /// </summary> protected BlockListPropertyEditorBase(IDataValueEditorFactory dataValueEditorFactory, IBlockValuePropertyIndexValueFactory blockValuePropertyIndexValueFactory, IJsonSerializer jsonSerializer) : base(dataValueEditorFactory) { @@ -37,21 +44,27 @@ protected BlockListPropertyEditorBase(IDataValueEditorFactory dataValueEditorFac SupportsReadOnly = true; } + /// <inheritdoc/> public override IPropertyIndexValueFactory PropertyIndexValueFactory => _blockValuePropertyIndexValueFactory; - #region Value Editor - /// <summary> - /// Instantiates a new <see cref="BlockEditorDataConverter"/> for use with the block list editor property value editor. + /// Instantiates a new <see cref="BlockEditorDataConverter{BlockListValue, BlockListLayoutItem}"/> for use with the block list editor property value editor. /// </summary> /// <returns>A new instance of <see cref="BlockListEditorDataConverter"/>.</returns> protected virtual BlockEditorDataConverter<BlockListValue, BlockListLayoutItem> CreateBlockEditorDataConverter() => new BlockListEditorDataConverter(_jsonSerializer); + /// <inheritdoc/> protected override IDataValueEditor CreateValueEditor() => DataValueEditorFactory.Create<BlockListEditorPropertyValueEditor>(Attribute!, CreateBlockEditorDataConverter()); + /// <summary> + /// Defines the value editor for the block list property editors. + /// </summary> internal class BlockListEditorPropertyValueEditor : BlockEditorPropertyValueEditor<BlockListValue, BlockListLayoutItem> { + /// <summary> + /// Initializes a new instance of the <see cref="BlockListEditorPropertyValueEditor"/> class. + /// </summary> public BlockListEditorPropertyValueEditor( DataEditorAttribute attribute, BlockEditorDataConverter<BlockListValue, BlockListLayoutItem> blockEditorDataConverter, @@ -74,8 +87,15 @@ public BlockListEditorPropertyValueEditor( Validators.Add(new MinMaxValidator(BlockEditorValues, textService)); } + /// <inheritdoc /> + public override IValueRequiredValidator RequiredValidator => new BlockListValueRequiredValidator(JsonSerializer); + + /// <inheritdoc/> protected override BlockListValue CreateWithLayout(IEnumerable<BlockListLayoutItem> layout) => new(layout); + /// <summary> + /// Validates the min/max configuration for block list property editors. + /// </summary> private class MinMaxValidator : BlockEditorMinMaxValidatorBase<BlockListValue, BlockListLayoutItem> { private readonly BlockEditorValues<BlockListValue, BlockListLayoutItem> _blockEditorValues; @@ -100,12 +120,11 @@ public override IEnumerable<ValidationResult> Validate(object? value, string? va } } + /// <inheritdoc/> public override IEnumerable<Guid> ConfiguredElementTypeKeys() { var configuration = ConfigurationObject as BlockListConfiguration; return configuration?.Blocks.SelectMany(ConfiguredElementTypeKeys) ?? Enumerable.Empty<Guid>(); } } - - #endregion } diff --git a/src/Umbraco.Infrastructure/PropertyEditors/BlockValuePropertyValueEditorBase.cs b/src/Umbraco.Infrastructure/PropertyEditors/BlockValuePropertyValueEditorBase.cs index 22e9384bf201..00bc627d60c8 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/BlockValuePropertyValueEditorBase.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/BlockValuePropertyValueEditorBase.cs @@ -10,7 +10,6 @@ using Umbraco.Cms.Core.Serialization; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Strings; -using Umbraco.Extensions; namespace Umbraco.Cms.Core.PropertyEditors; @@ -358,8 +357,8 @@ private void CleanupVariantValues( foreach (BlockPropertyValue targetBlockPropertyValue in targetBlockItem.Values) { - BlockPropertyValue? sourceBlockPropertyValue = - sourceBlockItem?.Values.FirstOrDefault(v => v.Culture == targetBlockPropertyValue.Culture); + BlockPropertyValue? sourceBlockPropertyValue = sourceBlockItem?.Values.FirstOrDefault(v + => v.Alias == targetBlockPropertyValue.Alias && v.Culture == targetBlockPropertyValue.Culture); // todo double check if this path can have an invariant value, but it shouldn't right??? // => it can be a null culture, but we shouldn't do anything? as the invariant section should have done it already diff --git a/src/Umbraco.Infrastructure/PropertyEditors/ColorPickerPropertyEditor.cs b/src/Umbraco.Infrastructure/PropertyEditors/ColorPickerPropertyEditor.cs index 9b5fce4a2f9e..544004c1aa66 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/ColorPickerPropertyEditor.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/ColorPickerPropertyEditor.cs @@ -1,11 +1,21 @@ // Copyright (c) Umbraco. // See LICENSE for more details. +using System.ComponentModel.DataAnnotations; +using System.Text.Json.Nodes; using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.Validation; using Umbraco.Cms.Core.Serialization; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Strings; +using Umbraco.Extensions; namespace Umbraco.Cms.Core.PropertyEditors; +/// <summary> +/// Represents a color picker property editor. +/// </summary> [DataEditor( Constants.PropertyEditors.Aliases.ColorPicker, ValueEditorIsReusable = true)] @@ -14,6 +24,9 @@ public class ColorPickerPropertyEditor : DataEditor private readonly IIOHelper _ioHelper; private readonly IConfigurationEditorJsonSerializer _configurationEditorJsonSerializer; + /// <summary> + /// Initializes a new instance of the <see cref="ColorPickerPropertyEditor"/> class. + /// </summary> public ColorPickerPropertyEditor(IDataValueEditorFactory dataValueEditorFactory, IIOHelper ioHelper, IConfigurationEditorJsonSerializer configurationEditorJsonSerializer) : base(dataValueEditorFactory) { @@ -22,10 +35,78 @@ public ColorPickerPropertyEditor(IDataValueEditorFactory dataValueEditorFactory, SupportsReadOnly = true; } + /// <inheritdoc/> public override IPropertyIndexValueFactory PropertyIndexValueFactory { get; } = new NoopPropertyIndexValueFactory(); + /// <inheritdoc /> + protected override IDataValueEditor CreateValueEditor() + => DataValueEditorFactory.Create<ColorPickerPropertyValueEditor>(Attribute!); /// <inheritdoc /> protected override IConfigurationEditor CreateConfigurationEditor() => new ColorPickerConfigurationEditor(_ioHelper, _configurationEditorJsonSerializer); + + /// <summary> + /// Defines the value editor for the color picker property editor. + /// </summary> + internal class ColorPickerPropertyValueEditor : DataValueEditor + { + /// <summary> + /// Initializes a new instance of the <see cref="ColorPickerPropertyValueEditor"/> class. + /// </summary> + public ColorPickerPropertyValueEditor( + IShortStringHelper shortStringHelper, + IJsonSerializer jsonSerializer, + IIOHelper ioHelper, + DataEditorAttribute attribute, + ILocalizedTextService localizedTextService) + : base(shortStringHelper, jsonSerializer, ioHelper, attribute) + { + Validators.AddRange(new ConfiguredColorValidator(localizedTextService)); + } + + /// <summary> + /// Validates the color selection for the color picker property editor. + /// </summary> + internal class ConfiguredColorValidator : IValueValidator + { + private readonly ILocalizedTextService _localizedTextService; + + /// <summary> + /// Initializes a new instance of the <see cref="ConfiguredColorValidator"/> class. + /// </summary> + public ConfiguredColorValidator(ILocalizedTextService localizedTextService) => _localizedTextService = localizedTextService; + + /// <inheritdoc/> + public IEnumerable<ValidationResult> Validate(object? value, string? valueType, object? dataTypeConfiguration, PropertyValidationContext validationContext) + { + if (value is null || value is not JsonObject valueAsJsonObject) + { + yield break; + } + + if (dataTypeConfiguration is not ColorPickerConfiguration colorPickerConfiguration) + { + yield break; + } + + string? selectedColor = valueAsJsonObject["value"]?.GetValue<string>(); + if (selectedColor.IsNullOrWhiteSpace()) + { + yield break; + } + + IEnumerable<string> validColors = colorPickerConfiguration.Items.Select(x => EnsureConsistentColorRepresentation(x.Value)); + if (validColors.Contains(EnsureConsistentColorRepresentation(selectedColor)) is false) + { + yield return new ValidationResult( + _localizedTextService.Localize("validation", "invalidColor", [selectedColor]), + ["value"]); + } + } + + private static string EnsureConsistentColorRepresentation(string color) + => (color.StartsWith('#') ? color : $"#{color}").ToLowerInvariant(); + } + } } diff --git a/src/Umbraco.Infrastructure/PropertyEditors/MediaPicker3PropertyEditor.cs b/src/Umbraco.Infrastructure/PropertyEditors/MediaPicker3PropertyEditor.cs index 14b6fd75253c..f23ce49fe7d1 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/MediaPicker3PropertyEditor.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/MediaPicker3PropertyEditor.cs @@ -18,7 +18,7 @@ namespace Umbraco.Cms.Core.PropertyEditors; /// <summary> -/// Represents a media picker property editor. +/// Represents a media picker property editor. /// </summary> [DataEditor( Constants.PropertyEditors.Aliases.MediaPicker3, @@ -38,6 +38,7 @@ public MediaPicker3PropertyEditor(IDataValueEditorFactory dataValueEditorFactory SupportsReadOnly = true; } + /// <inheritdoc /> public override IPropertyIndexValueFactory PropertyIndexValueFactory { get; } = new NoopPropertyIndexValueFactory(); /// <inheritdoc /> @@ -48,8 +49,9 @@ protected override IConfigurationEditor CreateConfigurationEditor() => protected override IDataValueEditor CreateValueEditor() => DataValueEditorFactory.Create<MediaPicker3PropertyValueEditor>(Attribute!); - - + /// <summary> + /// Defines the value editor for the media picker property editor. + /// </summary> internal class MediaPicker3PropertyValueEditor : DataValueEditor, IDataValueReference { private readonly IDataTypeConfigurationCache _dataTypeReadCache; @@ -60,6 +62,13 @@ internal class MediaPicker3PropertyValueEditor : DataValueEditor, IDataValueRefe private readonly IScopeProvider _scopeProvider; private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor; + /// <summary> + /// Initializes a new instance of the <see cref="MediaPicker3PropertyValueEditor"/> class. + /// </summary> + /// <remarks> + /// Note on FromEditor() and ToEditor() methods. + /// We do not want to transform the way the data is stored in the DB and would like to keep a raw JSON string. + /// </remarks> public MediaPicker3PropertyValueEditor( IShortStringHelper shortStringHelper, IJsonSerializer jsonSerializer, @@ -86,16 +95,13 @@ public MediaPicker3PropertyValueEditor( var validators = new TypedJsonValidatorRunner<List<MediaWithCropsDto>, MediaPicker3Configuration>( jsonSerializer, new MinMaxValidator(localizedTextService), - new AllowedTypeValidator(localizedTextService, mediaTypeService), + new AllowedTypeValidator(localizedTextService, mediaTypeService, _mediaService), new StartNodeValidator(localizedTextService, mediaNavigationQueryService)); Validators.Add(validators); } - /// <remarks> - /// Note: no FromEditor() and ToEditor() methods - /// We do not want to transform the way the data is stored in the DB and would like to keep a raw JSON string - /// </remarks> + /// <inheritdoc/> public IEnumerable<UmbracoEntityReference> GetReferences(object? value) { foreach (MediaWithCropsDto dto in Deserialize(_jsonSerializer, value)) @@ -104,6 +110,7 @@ public IEnumerable<UmbracoEntityReference> GetReferences(object? value) } } + /// <inheritdoc/> public override object ToEditor(IProperty property, string? culture = null, string? segment = null) { var value = property.GetValue(culture, segment); @@ -111,7 +118,7 @@ public override object ToEditor(IProperty property, string? culture = null, stri var dtos = Deserialize(_jsonSerializer, value).ToList(); dtos = UpdateMediaTypeAliases(dtos); - var configuration = _dataTypeReadCache.GetConfigurationAs<MediaPicker3Configuration>(property.PropertyType.DataTypeKey); + MediaPicker3Configuration? configuration = _dataTypeReadCache.GetConfigurationAs<MediaPicker3Configuration>(property.PropertyType.DataTypeKey); if (configuration is not null) { foreach (MediaWithCropsDto dto in dtos) @@ -123,6 +130,7 @@ public override object ToEditor(IProperty property, string? culture = null, stri return dtos; } + /// <inheritdoc/> public override object? FromEditor(ContentPropertyData editorValue, object? currentValue) { if (editorValue.Value is null || @@ -145,6 +153,9 @@ public override object ToEditor(IProperty property, string? culture = null, stri return _jsonSerializer.Serialize(mediaWithCropsDtos); } + /// <summary> + /// Deserializes the provided JSON value into a list of <see cref="MediaWithCropsDto"/>. + /// </summary> internal static IEnumerable<MediaWithCropsDto> Deserialize(IJsonSerializer jsonSerializer, object? value) { var rawJson = value is string str ? str : value?.ToString(); @@ -248,14 +259,29 @@ private Guid CurrentUserKey() => _backOfficeSecurityAccessor.BackOfficeSecurity? /// </summary> internal class MediaWithCropsDto { + /// <summary> + /// Gets or sets the key. + /// </summary> public Guid Key { get; set; } + /// <summary> + /// Gets or sets the media key. + /// </summary> public Guid MediaKey { get; set; } + /// <summary> + /// Gets or sets the media type alias. + /// </summary> public string MediaTypeAlias { get; set; } = string.Empty; + /// <summary> + /// Gets or sets the crops. + /// </summary> public IEnumerable<ImageCropperValue.ImageCropperCrop>? Crops { get; set; } + /// <summary> + /// Gets or sets the focal point. + /// </summary> public ImageCropperValue.ImageCropperFocalPoint? FocalPoint { get; set; } /// <summary> @@ -309,12 +335,19 @@ public void ApplyConfiguration(MediaPicker3Configuration? configuration) } } + /// <summary> + /// Validates the min/max configuration for the media picker property editor. + /// </summary> internal class MinMaxValidator : ITypedJsonValidator<List<MediaWithCropsDto>, MediaPicker3Configuration> { private readonly ILocalizedTextService _localizedTextService; + /// <summary> + /// Initializes a new instance of the <see cref="MinMaxValidator"/> class. + /// </summary> public MinMaxValidator(ILocalizedTextService localizedTextService) => _localizedTextService = localizedTextService; + /// <inheritdoc/> public IEnumerable<ValidationResult> Validate( List<MediaWithCropsDto>? mediaWithCropsDtos, MediaPicker3Configuration? mediaPickerConfiguration, @@ -363,17 +396,26 @@ public IEnumerable<ValidationResult> Validate( } } + /// <summary> + /// Validates the allowed type configuration for the media picker property editor. + /// </summary> internal class AllowedTypeValidator : ITypedJsonValidator<List<MediaWithCropsDto>, MediaPicker3Configuration> { private readonly ILocalizedTextService _localizedTextService; private readonly IMediaTypeService _mediaTypeService; + private readonly IMediaService _mediaService; - public AllowedTypeValidator(ILocalizedTextService localizedTextService, IMediaTypeService mediaTypeService) + /// <summary> + /// Initializes a new instance of the <see cref="AllowedTypeValidator"/> class. + /// </summary> + public AllowedTypeValidator(ILocalizedTextService localizedTextService, IMediaTypeService mediaTypeService, IMediaService mediaService) { _localizedTextService = localizedTextService; _mediaTypeService = mediaTypeService; + _mediaService = mediaService; } + /// <inheritdoc/> public IEnumerable<ValidationResult> Validate( List<MediaWithCropsDto>? value, MediaPicker3Configuration? configuration, @@ -393,7 +435,20 @@ public IEnumerable<ValidationResult> Validate( return []; } - IEnumerable<string> distinctTypeAliases = value.DistinctBy(x => x.MediaTypeAlias).Select(x => x.MediaTypeAlias); + // We may or may not have explicit MediaTypeAlias values provided, depending on whether the operation is an update or a + // create. So let's make sure we have them all. + IEnumerable<string> providedTypeAliases = value + .Where(x => x.MediaTypeAlias.IsNullOrWhiteSpace() is false) + .Select(x => x.MediaTypeAlias); + + IEnumerable<Guid> retrievedMediaKeys = value + .Where(x => x.MediaTypeAlias.IsNullOrWhiteSpace()) + .Select(x => x.MediaKey); + IEnumerable<IMedia> retrievedMedia = _mediaService.GetByIds(retrievedMediaKeys); + IEnumerable<string> retrievedTypeAliases = retrievedMedia + .Select(x => x.ContentType.Alias); + + IEnumerable<string> distinctTypeAliases = providedTypeAliases.Union(retrievedTypeAliases).Distinct(); foreach (var typeAlias in distinctTypeAliases) { @@ -414,11 +469,17 @@ public IEnumerable<ValidationResult> Validate( } } + /// <summary> + /// Validates the start node configuration for the media picker property editor. + /// </summary> internal class StartNodeValidator : ITypedJsonValidator<List<MediaWithCropsDto>, MediaPicker3Configuration> { private readonly ILocalizedTextService _localizedTextService; private readonly IMediaNavigationQueryService _mediaNavigationQueryService; + /// <summary> + /// Initializes a new instance of the <see cref="StartNodeValidator"/> class. + /// </summary> public StartNodeValidator( ILocalizedTextService localizedTextService, IMediaNavigationQueryService mediaNavigationQueryService) @@ -427,6 +488,7 @@ public StartNodeValidator( _mediaNavigationQueryService = mediaNavigationQueryService; } + /// <inheritdoc/> public IEnumerable<ValidationResult> Validate( List<MediaWithCropsDto>? value, MediaPicker3Configuration? configuration, @@ -438,53 +500,14 @@ public IEnumerable<ValidationResult> Validate( return []; } - - List<Guid> uniqueParentKeys = []; - foreach (Guid distinctMediaKey in value.DistinctBy(x => x.MediaKey).Select(x => x.MediaKey)) + if (ValidationHelper.HasValidStartNode(value.Select(x => x.MediaKey), configuration.StartNodeId.Value, _mediaNavigationQueryService) is false) { - if (_mediaNavigationQueryService.TryGetParentKey(distinctMediaKey, out Guid? parentKey) is false) - { - continue; - } - - // If there is a start node, the media must have a parent. - if (parentKey is null) - { - return - [ - new ValidationResult( - _localizedTextService.Localize("validation", "invalidStartNode"), - ["value"]) - ]; - } - - uniqueParentKeys.Add(parentKey.Value); - } - - IEnumerable<Guid> parentKeysNotInStartNode = uniqueParentKeys.Where(x => x != configuration.StartNodeId.Value); - foreach (Guid parentKey in parentKeysNotInStartNode) - { - if (_mediaNavigationQueryService.TryGetAncestorsKeys(parentKey, out IEnumerable<Guid> foundAncestorKeys) is false) - { - // We couldn't find the parent node, so we fail. - return - [ - new ValidationResult( - _localizedTextService.Localize("validation", "invalidStartNode"), - ["value"]) - ]; - } - - Guid[] ancestorKeys = foundAncestorKeys.ToArray(); - if (ancestorKeys.Length == 0 || ancestorKeys.Contains(configuration.StartNodeId.Value) is false) - { - return - [ - new ValidationResult( - _localizedTextService.Localize("validation", "invalidStartNode"), - ["value"]) - ]; - } + return + [ + new ValidationResult( + _localizedTextService.Localize("validation", "invalidStartNode"), + ["value"]) + ]; } return []; diff --git a/src/Umbraco.Infrastructure/PropertyEditors/MultiNodeTreePickerPropertyEditor.cs b/src/Umbraco.Infrastructure/PropertyEditors/MultiNodeTreePickerPropertyEditor.cs index b50aa3c7763a..c69f55f18712 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/MultiNodeTreePickerPropertyEditor.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/MultiNodeTreePickerPropertyEditor.cs @@ -1,16 +1,27 @@ // Copyright (c) Umbraco. // See LICENSE for more details. +using System.ComponentModel.DataAnnotations; using System.Text.Json.Nodes; +using Microsoft.Extensions.DependencyInjection; +using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Editors; +using Umbraco.Cms.Core.Models.Entities; +using Umbraco.Cms.Core.Models.Validation; +using Umbraco.Cms.Core.PropertyEditors.Validation; +using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Core.Serialization; +using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Strings; using Umbraco.Extensions; namespace Umbraco.Cms.Core.PropertyEditors; +/// <summary> +/// Represents a multi-node tree picker property editor. +/// </summary> [DataEditor( Constants.PropertyEditors.Aliases.MultiNodeTreePicker, ValueType = ValueTypes.Text, @@ -19,6 +30,9 @@ public class MultiNodeTreePickerPropertyEditor : DataEditor { private readonly IIOHelper _ioHelper; + /// <summary> + /// Initializes a new instance of the <see cref="MultiNodeTreePickerPropertyEditor"/> class. + /// </summary> public MultiNodeTreePickerPropertyEditor(IDataValueEditorFactory dataValueEditorFactory, IIOHelper ioHelper) : base(dataValueEditorFactory) { @@ -26,32 +40,75 @@ public MultiNodeTreePickerPropertyEditor(IDataValueEditorFactory dataValueEditor SupportsReadOnly = true; } + /// <inheritdoc/> protected override IConfigurationEditor CreateConfigurationEditor() => new MultiNodePickerConfigurationEditor(_ioHelper); + /// <inheritdoc/> protected override IDataValueEditor CreateValueEditor() => DataValueEditorFactory.Create<MultiNodeTreePickerPropertyValueEditor>(Attribute!); + /// <summary> + /// Defines the value editor for the media picker property editor. + /// </summary> /// <remarks> - /// At first glance, the fromEditor and toEditor methods might seem strange. - /// This is because we wanted to stop the leaking of UDI's to the frontend while not having to do database migrations - /// so we opted to, for now, translate the udi string in the database into a structured format unique to the client + /// At first glance, the FromEditor and ToEditor methods might seem strange. + /// This is because we wanted to stop the leaking of UDIs to the frontend while not having to do database migrations + /// so we opted to, for now, translate the UDI string in the database into a structured format unique to the client. /// This way, for now, no migration is needed and no changes outside of the editor logic needs to be touched to stop the leaking. /// </remarks> public class MultiNodeTreePickerPropertyValueEditor : DataValueEditor, IDataValueReference { private readonly IJsonSerializer _jsonSerializer; + /// <summary> + /// Initializes a new instance of the <see cref="MultiNodeTreePickerPropertyValueEditor"/> class. + /// </summary> public MultiNodeTreePickerPropertyValueEditor( IShortStringHelper shortStringHelper, IJsonSerializer jsonSerializer, IIOHelper ioHelper, - DataEditorAttribute attribute) + DataEditorAttribute attribute, + ILocalizedTextService localizedTextService, + IEntityService entityService, + ICoreScopeProvider coreScopeProvider, + IContentService contentService, + IMediaService mediaService, + IMemberService memberService) : base(shortStringHelper, jsonSerializer, ioHelper, attribute) { _jsonSerializer = jsonSerializer; + Validators.Add(new TypedJsonValidatorRunner<EditorEntityReference[], MultiNodePickerConfiguration>( + jsonSerializer, + new MinMaxValidator(localizedTextService), + new ObjectTypeValidator(localizedTextService, coreScopeProvider, entityService), + new ContentTypeValidator(localizedTextService, coreScopeProvider, contentService, mediaService, memberService))); } + /// <summary> + /// Initializes a new instance of the <see cref="MultiNodeTreePickerPropertyValueEditor"/> class. + /// </summary> + [Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 17.")] + public MultiNodeTreePickerPropertyValueEditor( + IShortStringHelper shortStringHelper, + IJsonSerializer jsonSerializer, + IIOHelper ioHelper, + DataEditorAttribute attribute) + : this( + shortStringHelper, + jsonSerializer, + ioHelper, + attribute, + StaticServiceProvider.Instance.GetRequiredService<ILocalizedTextService>(), + StaticServiceProvider.Instance.GetRequiredService<IEntityService>(), + StaticServiceProvider.Instance.GetRequiredService<ICoreScopeProvider>(), + StaticServiceProvider.Instance.GetRequiredService<IContentService>(), + StaticServiceProvider.Instance.GetRequiredService<IMediaService>(), + StaticServiceProvider.Instance.GetRequiredService<IMemberService>()) + { + } + + /// <inheritdoc/> public IEnumerable<UmbracoEntityReference> GetReferences(object? value) { var asString = value == null ? string.Empty : value is string str ? str : value.ToString(); @@ -66,12 +123,13 @@ public IEnumerable<UmbracoEntityReference> GetReferences(object? value) } } - + /// <inheritdoc/> public override object? FromEditor(ContentPropertyData editorValue, object? currentValue) => editorValue.Value is JsonArray jsonArray ? EntityReferencesToUdis(_jsonSerializer.Deserialize<IEnumerable<EditorEntityReference>>(jsonArray.ToJsonString()) ?? Enumerable.Empty<EditorEntityReference>()) : null; + /// <inheritdoc/> public override object? ToEditor(IProperty property, string? culture = null, string? segment = null) { var value = property.GetValue(culture, segment); @@ -80,7 +138,7 @@ public IEnumerable<UmbracoEntityReference> GetReferences(object? value) : null; } - private IEnumerable<EditorEntityReference> UdisToEntityReferences(IEnumerable<string> stringUdis) + private static IEnumerable<EditorEntityReference> UdisToEntityReferences(IEnumerable<string> stringUdis) { foreach (var stringUdi in stringUdis) { @@ -93,14 +151,277 @@ private IEnumerable<EditorEntityReference> UdisToEntityReferences(IEnumerable<st } } - private string EntityReferencesToUdis(IEnumerable<EditorEntityReference> nodeReferences) + private static string EntityReferencesToUdis(IEnumerable<EditorEntityReference> nodeReferences) => string.Join(",", nodeReferences.Select(entityReference => Udi.Create(entityReference.Type, entityReference.Unique).ToString())); + /// <summary> + /// Describes and editor entity reference. + /// </summary> public class EditorEntityReference { + /// <summary> + /// Gets or sets the entity object type. + /// </summary> public required string Type { get; set; } + /// <summary> + /// Gets or sets the entity unique identifier. + /// </summary> public required Guid Unique { get; set; } } + + /// <summary> + /// Gets the name of the configured object type for documents. + /// </summary> + internal const string DocumentObjectType = "content"; + + /// <summary> + /// Gets the name of the configured object type for media. + /// </summary> + internal const string MediaObjectType = "media"; + + /// <summary> + /// Gets the name of the configured object type for members. + /// </summary> + internal const string MemberObjectType = "member"; + + /// <inheritdoc/> + /// <summary> + /// Validates the min/max configuration for the multi-node tree picker property editor. + /// </summary> + internal class MinMaxValidator : ITypedJsonValidator<EditorEntityReference[], MultiNodePickerConfiguration> + { + private readonly ILocalizedTextService _localizedTextService; + + /// <summary> + /// Initializes a new instance of the <see cref="MinMaxValidator"/> class. + /// </summary> + public MinMaxValidator(ILocalizedTextService localizedTextService) => _localizedTextService = localizedTextService; + + /// <inheritdoc/> + public IEnumerable<ValidationResult> Validate( + EditorEntityReference[]? entityReferences, + MultiNodePickerConfiguration? configuration, + string? valueType, + PropertyValidationContext validationContext) + { + var validationResults = new List<ValidationResult>(); + + if (configuration is null) + { + return validationResults; + } + + if (configuration.MinNumber > 0 && (entityReferences is null || entityReferences.Length < configuration.MinNumber)) + { + validationResults.Add(new ValidationResult( + _localizedTextService.Localize( + "validation", + "entriesShort", + [configuration.MinNumber.ToString(), (configuration.MinNumber - (entityReferences?.Length ?? 0)).ToString() + ]), + ["value"])); + } + + if (entityReferences is null) + { + return validationResults; + } + + if (configuration.MaxNumber > 0 && entityReferences.Length > configuration.MaxNumber) + { + validationResults.Add(new ValidationResult( + _localizedTextService.Localize( + "validation", + "entriesExceed", + [configuration.MaxNumber.ToString(), (entityReferences.Length - configuration.MaxNumber).ToString() + ]), + ["value"])); + } + + return validationResults; + } + } + + /// <inheritdoc/> + /// <summary> + /// Validates the selected object type for the multi-node tree picker property editor. + /// </summary> + internal class ObjectTypeValidator : ITypedJsonValidator<EditorEntityReference[], MultiNodePickerConfiguration> + { + private readonly ILocalizedTextService _localizedTextService; + private readonly ICoreScopeProvider _coreScopeProvider; + private readonly IEntityService _entityService; + + /// <summary> + /// Initializes a new instance of the <see cref="ObjectTypeValidator"/> class. + /// </summary> + public ObjectTypeValidator( + ILocalizedTextService localizedTextService, + ICoreScopeProvider coreScopeProvider, + IEntityService entityService) + { + _localizedTextService = localizedTextService; + _coreScopeProvider = coreScopeProvider; + _entityService = entityService; + } + + /// <inheritdoc/> + public IEnumerable<ValidationResult> Validate( + EditorEntityReference[]? entityReferences, + MultiNodePickerConfiguration? configuration, + string? valueType, + PropertyValidationContext validationContext) + { + var validationResults = new List<ValidationResult>(); + + if (entityReferences is null || configuration?.TreeSource?.ObjectType is null) + { + return validationResults; + } + + Guid[] uniqueKeys = entityReferences.DistinctBy(x => x.Unique).Select(x => x.Unique).ToArray(); + + if (uniqueKeys.Length == 0) + { + return validationResults; + } + + Guid? allowedObjectType = GetObjectType(configuration.TreeSource.ObjectType); + if (allowedObjectType is null) + { + return + [ + + // Some invalid object type was sent. + new ValidationResult( + _localizedTextService.Localize( + "validation", + "invalidObjectType"), + ["value"]) + ]; + } + + using ICoreScope scope = _coreScopeProvider.CreateCoreScope(); + foreach (Guid key in uniqueKeys) + { + IEntitySlim? entity = _entityService.Get(key); + if (entity is not null && entity.NodeObjectType != allowedObjectType) + { + validationResults.Add(new ValidationResult( + _localizedTextService.Localize( + "validation", + "invalidObjectType"), + ["value"])); + } + } + + scope.Complete(); + + return validationResults; + } + + private static Guid? GetObjectType(string objectType) => + objectType switch + { + DocumentObjectType => Constants.ObjectTypes.Document, + MediaObjectType => Constants.ObjectTypes.Media, + MemberObjectType => Constants.ObjectTypes.Member, + _ => null, + }; + } + + /// <inheritdoc/> + /// <summary> + /// Validates the selected content type for the multi-node tree picker property editor. + /// </summary> + internal class ContentTypeValidator : ITypedJsonValidator<EditorEntityReference[], MultiNodePickerConfiguration> + { + private readonly ILocalizedTextService _localizedTextService; + private readonly ICoreScopeProvider _coreScopeProvider; + private readonly IContentService _contentService; + private readonly IMediaService _mediaService; + private readonly IMemberService _memberService; + + /// <summary> + /// Initializes a new instance of the <see cref="ContentTypeValidator"/> class. + /// </summary> + public ContentTypeValidator( + ILocalizedTextService localizedTextService, + ICoreScopeProvider coreScopeProvider, + IContentService contentService, + IMediaService mediaService, + IMemberService memberService) + { + _localizedTextService = localizedTextService; + _coreScopeProvider = coreScopeProvider; + _contentService = contentService; + _mediaService = mediaService; + _memberService = memberService; + } + + /// <inheritdoc/> + public IEnumerable<ValidationResult> Validate( + EditorEntityReference[]? entityReferences, + MultiNodePickerConfiguration? configuration, + string? valueType, + PropertyValidationContext validationContext) + { + var validationResults = new List<ValidationResult>(); + + Guid[] allowedTypes = configuration?.Filter?.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries).Select(Guid.Parse).ToArray() ?? []; + + // We can't validate if there is no object type, and we don't need to if there's no filter. + if (entityReferences is null || allowedTypes.Length == 0 || configuration?.TreeSource?.ObjectType is null) + { + return validationResults; + } + + using ICoreScope scope = _coreScopeProvider.CreateCoreScope(); + + Guid?[] uniqueContentTypeKeys = entityReferences + .Select(x => x.Unique) + .Distinct() + .Select(x => GetContent(configuration.TreeSource.ObjectType, x)) + .Select(x => x?.ContentType.Key) + .Distinct() + .ToArray(); + + scope.Complete(); + + foreach (Guid? key in uniqueContentTypeKeys) + { + if (key is null) + { + validationResults.Add(new ValidationResult( + _localizedTextService.Localize( + "validation", + "missingContent"), + ["value"])); + continue; + } + + if (allowedTypes.Contains(key.Value) is false) + { + validationResults.Add(new ValidationResult( + _localizedTextService.Localize( + "validation", + "invalidObjectType"), + ["value"])); + } + } + + return validationResults; + } + + private IContentBase? GetContent(string objectType, Guid key) => + objectType switch + { + DocumentObjectType => _contentService.GetById(key), + MediaObjectType => _mediaService.GetById(key), + MemberObjectType => _memberService.GetById(key), + _ => null, + }; + } } } diff --git a/src/Umbraco.Infrastructure/PropertyEditors/MultiUrlPickerValueEditor.cs b/src/Umbraco.Infrastructure/PropertyEditors/MultiUrlPickerValueEditor.cs index c9118993e740..185f66daac24 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/MultiUrlPickerValueEditor.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/MultiUrlPickerValueEditor.cs @@ -1,6 +1,7 @@ // Copyright (c) Umbraco. // See LICENSE for more details. +using System.ComponentModel.DataAnnotations; using System.Runtime.Serialization; using Microsoft.Extensions.Logging; using Umbraco.Cms.Core.IO; @@ -8,10 +9,13 @@ using Umbraco.Cms.Core.Models.ContentEditing; using Umbraco.Cms.Core.Models.Editors; using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.Models.Validation; +using Umbraco.Cms.Core.PropertyEditors.Validation; using Umbraco.Cms.Core.Routing; using Umbraco.Cms.Core.Serialization; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Strings; +using Umbraco.Extensions; namespace Umbraco.Cms.Core.PropertyEditors; @@ -41,6 +45,9 @@ public MultiUrlPickerValueEditor( _jsonSerializer = jsonSerializer; _contentService = contentService; _mediaService = mediaService; + Validators.Add(new TypedJsonValidatorRunner<LinkDisplay[], MultiUrlPickerConfiguration>( + _jsonSerializer, + new MinMaxValidator(localizedTextService))); } public IEnumerable<UmbracoEntityReference> GetReferences(object? value) @@ -209,4 +216,51 @@ public class LinkDto [DataMember(Name = "queryString")] public string? QueryString { get; set; } } + + internal class MinMaxValidator : ITypedJsonValidator<LinkDisplay[], MultiUrlPickerConfiguration> + { + private readonly ILocalizedTextService _localizedTextService; + + public MinMaxValidator(ILocalizedTextService localizedTextService) => _localizedTextService = localizedTextService; + + public IEnumerable<ValidationResult> Validate( + LinkDisplay[]? linksDtos, + MultiUrlPickerConfiguration? multiUrlPickerConfiguration, + string? valueType, + PropertyValidationContext validationContext) + { + if (multiUrlPickerConfiguration is null || (linksDtos is null && multiUrlPickerConfiguration.MinNumber == 0)) + { + return []; + } + + if (linksDtos is null || linksDtos.Length < multiUrlPickerConfiguration.MinNumber) + { + return [new ValidationResult( + _localizedTextService.Localize( + "validation", + "entriesShort", + [multiUrlPickerConfiguration.MinNumber.ToString(), (multiUrlPickerConfiguration.MinNumber - (linksDtos?.Length ?? 0)).ToString()]), + ["value"])]; + } + + if (linksDtos.Length > multiUrlPickerConfiguration.MaxNumber && multiUrlPickerConfiguration.MaxNumber > 0) + { + return + [ + new ValidationResult( + _localizedTextService.Localize( + "validation", + "entriesExceed", + [ + multiUrlPickerConfiguration.MaxNumber.ToString(), + (linksDtos.Length - multiUrlPickerConfiguration.MaxNumber).ToString() + ]), + ["value"]) + ]; + } + + return []; + } + } } diff --git a/src/Umbraco.Infrastructure/PropertyEditors/MultipleTextStringPropertyEditor.cs b/src/Umbraco.Infrastructure/PropertyEditors/MultipleTextStringPropertyEditor.cs index 7b86cb1bb4b6..9fe8dbe4ec65 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/MultipleTextStringPropertyEditor.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/MultipleTextStringPropertyEditor.cs @@ -6,14 +6,17 @@ using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Editors; +using Umbraco.Cms.Core.Models.Validation; using Umbraco.Cms.Core.PropertyEditors.Validators; using Umbraco.Cms.Core.Serialization; +using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Strings; +using Umbraco.Extensions; namespace Umbraco.Cms.Core.PropertyEditors; /// <summary> -/// Represents a multiple text string property editor. +/// Represents a multiple text string property editor. /// </summary> [DataEditor( Constants.PropertyEditors.Aliases.MultipleTextstring, @@ -24,7 +27,7 @@ public class MultipleTextStringPropertyEditor : DataEditor private readonly IIOHelper _ioHelper; /// <summary> - /// Initializes a new instance of the <see cref="MultipleTextStringPropertyEditor" /> class. + /// Initializes a new instance of the <see cref="MultipleTextStringPropertyEditor" /> class. /// </summary> public MultipleTextStringPropertyEditor(IIOHelper ioHelper, IDataValueEditorFactory dataValueEditorFactory) : base(dataValueEditorFactory) @@ -42,37 +45,42 @@ protected override IConfigurationEditor CreateConfigurationEditor() => new MultipleTextStringConfigurationEditor(_ioHelper); /// <summary> - /// Custom value editor so we can format the value for the editor and the database + /// Defines the value editor for the multiple text string property editor. /// </summary> internal class MultipleTextStringPropertyValueEditor : DataValueEditor { - private static readonly string NewLine = "\n"; - private static readonly string[] NewLineDelimiters = { "\r\n", "\r", "\n" }; + private static readonly string _newLine = "\n"; + private static readonly string[] _newLineDelimiters = { "\r\n", "\r", "\n" }; + /// <summary> + /// Initializes a new instance of the <see cref="MultipleTextStringPropertyValueEditor"/> class. + /// </summary> public MultipleTextStringPropertyValueEditor( IShortStringHelper shortStringHelper, IJsonSerializer jsonSerializer, IIOHelper ioHelper, - DataEditorAttribute attribute) + DataEditorAttribute attribute, + ILocalizedTextService localizedTextService) : base(shortStringHelper, jsonSerializer, ioHelper, attribute) { + Validators.AddRange(new MinMaxValidator(localizedTextService)); } /// <summary> - /// A custom FormatValidator is used as for multiple text strings, each string should individually be checked - /// against the configured regular expression, rather than the JSON representing all the strings as a whole. + /// A custom <see href="IValueFormatValidator" /> is used as for multiple text strings, each string should individually + /// be checked against the configured regular expression, rather than the JSON representing all the strings as a whole. /// </summary> public override IValueFormatValidator FormatValidator => new MultipleTextStringFormatValidator(); /// <summary> - /// The value passed in from the editor will be an array of simple objects so we'll need to parse them to get the - /// string + /// The value passed in from the editor will be an array of simple objects so we'll need to parse them to get the + /// string. /// </summary> /// <param name="editorValue"></param> /// <param name="currentValue"></param> /// <returns></returns> /// <remarks> - /// We will also check the pre-values here, if there are more items than what is allowed we'll just trim the end + /// We will also check the pre-values here, if there are more items than what is allowed we'll just trim the end. /// </remarks> public override object? FromEditor(ContentPropertyData editorValue, object? currentValue) { @@ -93,30 +101,35 @@ public MultipleTextStringPropertyValueEditor( // only allow the max if over 0 if (max > 0) { - return string.Join(NewLine, value.Take(max)); + return string.Join(_newLine, value.Take(max)); } - return string.Join(NewLine, value); + return string.Join(_newLine, value); } + /// <inheritdoc/> public override object ToEditor(IProperty property, string? culture = null, string? segment = null) { var value = property.GetValue(culture, segment); // The legacy property editor saved this data as new line delimited! strange but we have to maintain that. return value is string stringValue - ? stringValue.Split(NewLineDelimiters, StringSplitOptions.None) + ? stringValue.Split(_newLineDelimiters, StringSplitOptions.None) : Array.Empty<string>(); } } + /// <summary> + /// A custom <see href="IValueFormatValidator" /> to check each string against the configured format. + /// </summary> internal class MultipleTextStringFormatValidator : IValueFormatValidator { + /// <inheritdoc/> public IEnumerable<ValidationResult> ValidateFormat(object? value, string valueType, string format) { if (value is not IEnumerable<string> textStrings) { - return Enumerable.Empty<ValidationResult>(); + return []; } var textStringValidator = new RegexValidator(); @@ -129,7 +142,60 @@ public IEnumerable<ValidationResult> ValidateFormat(object? value, string valueT } } - return Enumerable.Empty<ValidationResult>(); + return []; + } + } + + /// <summary> + /// Validates the min/max configuration for the multiple text strings property editor. + /// </summary> + internal class MinMaxValidator : IValueValidator + { + private readonly ILocalizedTextService _localizedTextService; + + /// <summary> + /// Initializes a new instance of the <see cref="MinMaxValidator"/> class. + /// </summary> + public MinMaxValidator(ILocalizedTextService localizedTextService) => _localizedTextService = localizedTextService; + + /// <inheritdoc/> + public IEnumerable<ValidationResult> Validate(object? value, string? valueType, object? dataTypeConfiguration, PropertyValidationContext validationContext) + { + if (dataTypeConfiguration is not MultipleTextStringConfiguration multipleTextStringConfiguration) + { + yield break; + } + + // If we have a null value, treat as an empty collection for minimum number validation. + if (value is not IEnumerable<string> stringValues) + { + stringValues = []; + } + + var stringCount = stringValues.Count(); + + if (stringCount < multipleTextStringConfiguration.Min) + { + if (stringCount == 1) + { + yield return new ValidationResult( + _localizedTextService.Localize("validation", "outOfRangeSingleItemMinimum", [multipleTextStringConfiguration.Min.ToString()]), + ["value"]); + } + else + { + yield return new ValidationResult( + _localizedTextService.Localize("validation", "outOfRangeMultipleItemsMinimum", [stringCount.ToString(), multipleTextStringConfiguration.Min.ToString()]), + ["value"]); + } + } + + if (multipleTextStringConfiguration.Max > 0 && stringCount > multipleTextStringConfiguration.Max) + { + yield return new ValidationResult( + _localizedTextService.Localize("validation", "outOfRangeMultipleItemsMaximum", [stringCount.ToString(), multipleTextStringConfiguration.Max.ToString()]), + ["value"]); + } } } } diff --git a/src/Umbraco.Infrastructure/PropertyEditors/MultipleValueEditor.cs b/src/Umbraco.Infrastructure/PropertyEditors/MultipleValueEditor.cs index cd12e1921a67..44f020b633d0 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/MultipleValueEditor.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/MultipleValueEditor.cs @@ -11,33 +11,34 @@ namespace Umbraco.Cms.Core.PropertyEditors; /// <summary> -/// A value editor to handle posted json array data and to return array data for the multiple selected csv items +/// A value editor to handle posted JSON array data and to return array data for the multiple selected CSV items. /// </summary> /// <remarks> -/// This is re-used by editors such as the multiple drop down list or check box list +/// This is re-used by editors such as the multiple drop down list or check box list. /// </remarks> public class MultipleValueEditor : DataValueEditor { private readonly IJsonSerializer _jsonSerializer; + /// <summary> + /// Initializes a new instance of the <see cref="MultipleValueEditor"/> class. + /// </summary> public MultipleValueEditor( ILocalizedTextService localizedTextService, IShortStringHelper shortStringHelper, IJsonSerializer jsonSerializer, IIOHelper ioHelper, DataEditorAttribute attribute) - : base(localizedTextService, shortStringHelper, jsonSerializer, ioHelper, attribute) + : base(shortStringHelper, jsonSerializer, ioHelper, attribute) { _jsonSerializer = jsonSerializer; - Validators.Add(new MultipleValueValidator()); + Validators.Add(new MultipleValueValidator(localizedTextService)); } /// <summary> - /// When multiple values are selected a json array will be posted back so we need to format for storage in - /// the database which is a comma separated string value + /// When multiple values are selected a JSON array will be posted back so we need to format for storage in + /// the database which is a comma separated string value. /// </summary> - /// <param name="editorValue"></param> - /// <param name="currentValue"></param> /// <returns></returns> public override object? FromEditor(ContentPropertyData editorValue, object? currentValue) { @@ -46,7 +47,6 @@ public MultipleValueEditor( return null; } - var result = _jsonSerializer.Serialize(stringValues); - return result; + return _jsonSerializer.Serialize(stringValues); } } diff --git a/src/Umbraco.Infrastructure/PropertyEditors/RichTextEditorBlockValidator.cs b/src/Umbraco.Infrastructure/PropertyEditors/RichTextEditorBlockValidator.cs index 74428dbd30c3..a11a11e70357 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/RichTextEditorBlockValidator.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/RichTextEditorBlockValidator.cs @@ -4,6 +4,7 @@ using Umbraco.Cms.Core.Models.Validation; using Umbraco.Cms.Core.Serialization; using Umbraco.Cms.Core.Services; +using Umbraco.Extensions; namespace Umbraco.Cms.Core.PropertyEditors; @@ -26,6 +27,12 @@ public RichTextEditorBlockValidator( _logger = logger; } + protected override string ContentDataGroupJsonPath => + $"{nameof(RichTextEditorValue.Blocks).ToFirstLowerInvariant()}.{base.ContentDataGroupJsonPath}"; + + protected override string SettingsDataGroupJsonPath => + $"{nameof(RichTextEditorValue.Blocks).ToFirstLowerInvariant()}.{base.SettingsDataGroupJsonPath}"; + protected override IEnumerable<ElementTypeValidationModel> GetElementTypeValidation(object? value, PropertyValidationContext validationContext) { RichTextPropertyEditorHelper.TryParseRichTextEditorValue(value, _jsonSerializer, _logger, out RichTextEditorValue? richTextEditorValue); diff --git a/src/Umbraco.Infrastructure/PropertyEditors/SliderPropertyEditor.cs b/src/Umbraco.Infrastructure/PropertyEditors/SliderPropertyEditor.cs index 5277a2f55209..02e96d075ac1 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/SliderPropertyEditor.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/SliderPropertyEditor.cs @@ -10,7 +10,6 @@ using Umbraco.Cms.Core.Models.Editors; using Umbraco.Cms.Core.Models.Validation; using Umbraco.Cms.Core.PropertyEditors.Validation; -using Umbraco.Cms.Core.PropertyEditors.Validators; using Umbraco.Cms.Core.Serialization; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Strings; @@ -261,7 +260,7 @@ public IEnumerable<ValidationResult> Validate(object? value, string? valueType, ["value"]); } - if (sliderRange.To > sliderConfiguration.MaximumValue) + if (sliderConfiguration.MaximumValue != 0 && sliderRange.To > sliderConfiguration.MaximumValue) { yield return new ValidationResult( LocalizedTextService.Localize("validation", "outOfRangeMaximum", [sliderRange.To.ToString(), sliderConfiguration.MaximumValue.ToString()]), diff --git a/src/Umbraco.Infrastructure/PropertyEditors/TrueFalsePropertyEditor.cs b/src/Umbraco.Infrastructure/PropertyEditors/TrueFalsePropertyEditor.cs index 66d1af9c7756..df63ee52d26a 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/TrueFalsePropertyEditor.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/TrueFalsePropertyEditor.cs @@ -6,11 +6,12 @@ using Umbraco.Cms.Core.Models.Editors; using Umbraco.Cms.Core.Serialization; using Umbraco.Cms.Core.Strings; +using Umbraco.Cms.Infrastructure.PropertyEditors.Validators; namespace Umbraco.Cms.Core.PropertyEditors; /// <summary> -/// Represents a checkbox property and parameter editor. +/// Represents a true/false (toggle) property editor. /// </summary> [DataEditor( Constants.PropertyEditors.Aliases.Boolean, @@ -29,8 +30,14 @@ public TrueFalsePropertyEditor(IDataValueEditorFactory dataValueEditorFactory) protected override IDataValueEditor CreateValueEditor() => DataValueEditorFactory.Create<TrueFalsePropertyValueEditor>(Attribute!); + /// <summary> + /// Defines the value editor for the true/false (toggle) property editor. + /// </summary> internal class TrueFalsePropertyValueEditor : DataValueEditor { + /// <summary> + /// Initializes a new instance of the <see cref="TrueFalsePropertyValueEditor"/> class. + /// </summary> public TrueFalsePropertyValueEditor( IShortStringHelper shortStringHelper, IJsonSerializer jsonSerializer, @@ -40,10 +47,17 @@ public TrueFalsePropertyValueEditor( { } + /// <inheritdoc /> + public override IValueRequiredValidator RequiredValidator => new TrueFalseValueRequiredValidator(); + + /// <inheritdoc/> public override object? ToEditor(IProperty property, string? culture = null, string? segment = null) => ParsePropertyValue(property.GetValue(culture, segment)); - // NOTE: property editor value type is Integer, which means we need to store the boolean representation as 0 or 1 + /// <inheritdoc/> + /// <remarks> + /// NOTE: property editor value type is Integer, which means we need to store the boolean representation as 0 or 1. + /// </remarks> public override object? FromEditor(ContentPropertyData editorValue, object? currentValue) => ParsePropertyValue(editorValue.Value) ? 1 : 0; diff --git a/src/Umbraco.Infrastructure/PropertyEditors/Validators/BlockListValueRequiredValidator.cs b/src/Umbraco.Infrastructure/PropertyEditors/Validators/BlockListValueRequiredValidator.cs new file mode 100644 index 000000000000..40d67bb0d206 --- /dev/null +++ b/src/Umbraco.Infrastructure/PropertyEditors/Validators/BlockListValueRequiredValidator.cs @@ -0,0 +1,40 @@ +using System.ComponentModel.DataAnnotations; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Models.Blocks; +using Umbraco.Cms.Core.PropertyEditors.Validators; +using Umbraco.Cms.Core.Serialization; + +namespace Umbraco.Cms.Infrastructure.PropertyEditors.Validators; + +/// <summary> +/// Custom validator for block value required validation. +/// </summary> +internal class BlockListValueRequiredValidator : RequiredValidator +{ + private readonly IJsonSerializer _jsonSerializer; + + /// <summary> + /// Initializes a new instance of the <see cref="BlockListValueRequiredValidator"/> class. + /// </summary> + public BlockListValueRequiredValidator(IJsonSerializer jsonSerializer) => _jsonSerializer = jsonSerializer; + + /// <inheritdoc/> + public override IEnumerable<ValidationResult> ValidateRequired(object? value, string? valueType) + { + IEnumerable<ValidationResult> validationResults = base.ValidateRequired(value, valueType); + + if (value is null) + { + return validationResults; + } + + if (_jsonSerializer.TryDeserialize(value, out BlockListValue? blockListValue) && + blockListValue.ContentData.Count == 0 && + blockListValue.Layout.Count == 0) + { + validationResults = validationResults.Append(new ValidationResult(Constants.Validation.ErrorMessages.Properties.Empty, ["value"])); + } + + return validationResults; + } +} diff --git a/src/Umbraco.Infrastructure/PropertyEditors/Validators/TrueFalseValueRequiredValidator.cs b/src/Umbraco.Infrastructure/PropertyEditors/Validators/TrueFalseValueRequiredValidator.cs new file mode 100644 index 000000000000..3e1cdfd1175a --- /dev/null +++ b/src/Umbraco.Infrastructure/PropertyEditors/Validators/TrueFalseValueRequiredValidator.cs @@ -0,0 +1,29 @@ +using System.ComponentModel.DataAnnotations; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.PropertyEditors.Validators; + +namespace Umbraco.Cms.Infrastructure.PropertyEditors.Validators; + +/// <summary> +/// Custom validator for true/false (toggle) required validation. +/// </summary> +internal class TrueFalseValueRequiredValidator : RequiredValidator +{ + /// <inheritdoc/> + public override IEnumerable<ValidationResult> ValidateRequired(object? value, string? valueType) + { + IEnumerable<ValidationResult> validationResults = base.ValidateRequired(value, valueType); + + if (value is null) + { + return validationResults; + } + + if (value is bool valueAsBool && valueAsBool is false) + { + validationResults = validationResults.Append(new ValidationResult(Constants.Validation.ErrorMessages.Properties.Empty, ["value"])); + } + + return validationResults; + } +} diff --git a/src/Umbraco.Infrastructure/PublishedCache/ReservedFieldNamesService.cs b/src/Umbraco.Infrastructure/PublishedCache/ReservedFieldNamesService.cs index 8cbdf4f130bd..c4d981ef10e6 100644 --- a/src/Umbraco.Infrastructure/PublishedCache/ReservedFieldNamesService.cs +++ b/src/Umbraco.Infrastructure/PublishedCache/ReservedFieldNamesService.cs @@ -1,8 +1,6 @@ using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Cms.Core.Services; -using Umbraco.Extensions; namespace Umbraco.Cms.Infrastructure.PublishedCache; @@ -22,33 +20,9 @@ public ReservedFieldNamesService( _mediaPropertySettings = mediaPropertySettings.Value; } - public ISet<string> GetDocumentReservedFieldNames() - { - var reservedProperties = typeof(IPublishedContent).GetPublicProperties().Select(x => x.Name).ToHashSet(); - var reservedMethods = typeof(IPublishedContent).GetPublicMethods().Select(x => x.Name).ToHashSet(); - reservedProperties.UnionWith(reservedMethods); - reservedProperties.UnionWith(_contentPropertySettings.ReservedFieldNames); + public ISet<string> GetDocumentReservedFieldNames() => _contentPropertySettings.ReservedFieldNames; - return reservedProperties; - } + public ISet<string> GetMediaReservedFieldNames() => _mediaPropertySettings.ReservedFieldNames; - public ISet<string> GetMediaReservedFieldNames() - { - var reservedProperties = typeof(IPublishedContent).GetPublicProperties().Select(x => x.Name).ToHashSet(); - var reservedMethods = typeof(IPublishedContent).GetPublicMethods().Select(x => x.Name).ToHashSet(); - reservedProperties.UnionWith(reservedMethods); - reservedProperties.UnionWith(_mediaPropertySettings.ReservedFieldNames); - - return reservedProperties; - } - - public ISet<string> GetMemberReservedFieldNames() - { - var reservedProperties = typeof(IPublishedMember).GetPublicProperties().Select(x => x.Name).ToHashSet(); - var reservedMethods = typeof(IPublishedMember).GetPublicMethods().Select(x => x.Name).ToHashSet(); - reservedProperties.UnionWith(reservedMethods); - reservedProperties.UnionWith(_memberPropertySettings.ReservedFieldNames); - - return reservedProperties; - } + public ISet<string> GetMemberReservedFieldNames() => _memberPropertySettings.ReservedFieldNames; } diff --git a/src/Umbraco.Infrastructure/Security/MemberUserStore.cs b/src/Umbraco.Infrastructure/Security/MemberUserStore.cs index 21c420722447..6dddd08756d9 100644 --- a/src/Umbraco.Infrastructure/Security/MemberUserStore.cs +++ b/src/Umbraco.Infrastructure/Security/MemberUserStore.cs @@ -483,8 +483,7 @@ public override Task<bool> IsInRoleAsync(MemberIdentityUser user, string roleNam } /// <inheritdoc /> - protected override async Task<IdentityUserLogin<string>?> FindUserLoginAsync(string userId, string loginProvider, - string providerKey, CancellationToken cancellationToken) + protected override async Task<IdentityUserLogin<string>?> FindUserLoginAsync(string userId, string loginProvider, string providerKey, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); @@ -502,15 +501,14 @@ public override Task<bool> IsInRoleAsync(MemberIdentityUser user, string roleNam MemberIdentityUser? user = await FindUserAsync(userId, cancellationToken); if (user?.Id is null) { - return await Task.FromResult<IdentityUserLogin<string>?>(null); + return null; } IList<UserLoginInfo> logins = await GetLoginsAsync(user, cancellationToken); - UserLoginInfo? found = - logins.FirstOrDefault(x => x.ProviderKey == providerKey && x.LoginProvider == loginProvider); + UserLoginInfo? found = logins.FirstOrDefault(x => x.ProviderKey == providerKey && x.LoginProvider == loginProvider); if (found is null) { - return await Task.FromResult<IdentityUserLogin<string>?>(null); + return null; } return new IdentityUserLogin<string> @@ -524,8 +522,7 @@ public override Task<bool> IsInRoleAsync(MemberIdentityUser user, string roleNam } /// <inheritdoc /> - protected override Task<IdentityUserLogin<string>?> FindUserLoginAsync(string loginProvider, string providerKey, - CancellationToken cancellationToken) + protected override Task<IdentityUserLogin<string>?> FindUserLoginAsync(string loginProvider, string providerKey, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); diff --git a/src/Umbraco.Infrastructure/Services/Implement/ContentListViewService.cs b/src/Umbraco.Infrastructure/Services/Implement/ContentListViewService.cs index ecbc6e400389..e9d46ddaa85f 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/ContentListViewService.cs +++ b/src/Umbraco.Infrastructure/Services/Implement/ContentListViewService.cs @@ -49,7 +49,7 @@ public ContentListViewService( return await GetListViewResultAsync(user, content, dataTypeKey, orderBy, orderCulture, orderDirection, filter, skip, take); } - protected override async Task<PagedModel<IContent>> GetPagedChildrenAsync( + protected override Task<PagedModel<IContent>> GetPagedChildrenAsync( int id, IQuery<IContent>? filter, Ordering? ordering, @@ -58,13 +58,13 @@ protected override async Task<PagedModel<IContent>> GetPagedChildrenAsync( { PaginationHelper.ConvertSkipTakeToPaging(skip, take, out var pageNumber, out var pageSize); - IEnumerable<IContent> items = await Task.FromResult(_contentService.GetPagedChildren( + IEnumerable<IContent> items = _contentService.GetPagedChildren( id, pageNumber, pageSize, out var total, filter, - ordering)); + ordering); var pagedResult = new PagedModel<IContent> { @@ -72,7 +72,7 @@ protected override async Task<PagedModel<IContent>> GetPagedChildrenAsync( Total = total, }; - return pagedResult; + return Task.FromResult(pagedResult); } // We can use an authorizer here, as it already handles all the necessary checks for this filtering. diff --git a/src/Umbraco.Infrastructure/Services/Implement/MediaListViewService.cs b/src/Umbraco.Infrastructure/Services/Implement/MediaListViewService.cs index 42b6bbf42119..63d9298ec343 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/MediaListViewService.cs +++ b/src/Umbraco.Infrastructure/Services/Implement/MediaListViewService.cs @@ -50,17 +50,17 @@ public MediaListViewService( return await GetListViewResultAsync(user, media, dataTypeKey, orderBy, null, orderDirection, filter, skip, take); } - protected override async Task<PagedModel<IMedia>> GetPagedChildrenAsync(int id, IQuery<IMedia>? filter, Ordering? ordering, int skip, int take) + protected override Task<PagedModel<IMedia>> GetPagedChildrenAsync(int id, IQuery<IMedia>? filter, Ordering? ordering, int skip, int take) { PaginationHelper.ConvertSkipTakeToPaging(skip, take, out var pageNumber, out var pageSize); - IEnumerable<IMedia> items = await Task.FromResult(_mediaService.GetPagedChildren( + IEnumerable<IMedia> items = _mediaService.GetPagedChildren( id, pageNumber, pageSize, out var total, filter, - ordering)); + ordering); var pagedResult = new PagedModel<IMedia> { @@ -68,7 +68,7 @@ protected override async Task<PagedModel<IMedia>> GetPagedChildrenAsync(int id, Total = total, }; - return pagedResult; + return Task.FromResult(pagedResult); } // We can use an authorizer here, as it already handles all the necessary checks for this filtering. diff --git a/src/Umbraco.Infrastructure/Services/Implement/PackagingService.cs b/src/Umbraco.Infrastructure/Services/Implement/PackagingService.cs index c532d4ee68d5..2e3a3a57763d 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/PackagingService.cs +++ b/src/Umbraco.Infrastructure/Services/Implement/PackagingService.cs @@ -181,13 +181,13 @@ public void DeleteCreatedPackage(int id, int userId = Constants.Security.SuperUs } /// <inheritdoc/> - public async Task<PagedModel<PackageDefinition>> GetCreatedPackagesAsync(int skip, int take) + public Task<PagedModel<PackageDefinition>> GetCreatedPackagesAsync(int skip, int take) { using ICoreScope scope = _coreScopeProvider.CreateCoreScope(autoComplete: true); PackageDefinition[] packages = _createdPackages.GetAll().WhereNotNull().ToArray(); var pagedModel = new PagedModel<PackageDefinition>(packages.Length, packages.Skip(skip).Take(take)); - return pagedModel; + return Task.FromResult(pagedModel); } /// <inheritdoc/> @@ -353,7 +353,7 @@ public async Task<IEnumerable<InstalledPackage>> GetAllInstalledPackagesAsync() } // Set additional values - installedPackage.AllowPackageTelemetry = packageManifest.AllowTelemetry; + installedPackage.AllowPackageTelemetry = packageManifest is { AllowTelemetry: true, AllowPackageTelemetry: true }; if (!string.IsNullOrEmpty(packageManifest.Version)) { diff --git a/src/Umbraco.Infrastructure/Services/IndexingRebuilderService.cs b/src/Umbraco.Infrastructure/Services/IndexingRebuilderService.cs index aa61b42e7eb3..c014277b6b01 100644 --- a/src/Umbraco.Infrastructure/Services/IndexingRebuilderService.cs +++ b/src/Umbraco.Infrastructure/Services/IndexingRebuilderService.cs @@ -1,4 +1,4 @@ -using Examine; +using Examine; using Microsoft.Extensions.Logging; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Infrastructure.Examine; @@ -7,7 +7,8 @@ namespace Umbraco.Cms.Infrastructure.Services; public class IndexingRebuilderService : IIndexingRebuilderService { - private const string TempKey = "temp_indexing_op_"; + private const string IsRebuildingIndexRuntimeCacheKeyPrefix = "temp_indexing_op_"; + private readonly IAppPolicyCache _runtimeCache; private readonly IIndexRebuilder _indexRebuilder; private readonly ILogger<IndexingRebuilderService> _logger; @@ -49,7 +50,7 @@ public bool TryRebuild(IIndex index, string indexName) private void Set(string indexName) { - var cacheKey = TempKey + indexName; + var cacheKey = IsRebuildingIndexRuntimeCacheKeyPrefix + indexName; // put temp val in cache which is used as a rudimentary way to know when the indexing is done _runtimeCache.Insert(cacheKey, () => "tempValue", TimeSpan.FromMinutes(5)); @@ -57,13 +58,13 @@ private void Set(string indexName) private void Clear(string? indexName) { - var cacheKey = TempKey + indexName; + var cacheKey = IsRebuildingIndexRuntimeCacheKeyPrefix + indexName; _runtimeCache.Clear(cacheKey); } public bool IsRebuilding(string indexName) { - var cacheKey = "temp_indexing_op_" + indexName; + var cacheKey = IsRebuildingIndexRuntimeCacheKeyPrefix + indexName; return _runtimeCache.Get(cacheKey) is not null; } diff --git a/src/Umbraco.Infrastructure/Services/MemberEditingService.cs b/src/Umbraco.Infrastructure/Services/MemberEditingService.cs index f7cd7060c3d0..efae531d3eee 100644 --- a/src/Umbraco.Infrastructure/Services/MemberEditingService.cs +++ b/src/Umbraco.Infrastructure/Services/MemberEditingService.cs @@ -70,8 +70,8 @@ public MemberEditingService( _securitySettings = securitySettings.Value; } - public async Task<IMember?> GetAsync(Guid key) - => await Task.FromResult(_memberService.GetByKey(key)); + public Task<IMember?> GetAsync(Guid key) + => Task.FromResult(_memberService.GetByKey(key)); public async Task<Attempt<ContentValidationResult, ContentEditingOperationStatus>> ValidateCreateAsync(MemberCreateModel createModel) => await _memberContentEditingService.ValidateAsync(createModel, createModel.ContentTypeKey); diff --git a/src/Umbraco.PublishedCache.HybridCache/DatabaseCacheRebuilder.cs b/src/Umbraco.PublishedCache.HybridCache/DatabaseCacheRebuilder.cs index 429c923a8e61..e6daf8b3b618 100644 --- a/src/Umbraco.PublishedCache.HybridCache/DatabaseCacheRebuilder.cs +++ b/src/Umbraco.PublishedCache.HybridCache/DatabaseCacheRebuilder.cs @@ -1,30 +1,45 @@ -using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Logging; using Umbraco.Cms.Core.PublishedCache; using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Infrastructure.HostedServices; using Umbraco.Cms.Infrastructure.HybridCache.Persistence; namespace Umbraco.Cms.Infrastructure.HybridCache; +/// <summary> +/// Rebuilds the published content cache in the database. +/// </summary> internal class DatabaseCacheRebuilder : IDatabaseCacheRebuilder { private const string NuCacheSerializerKey = "Umbraco.Web.PublishedCache.NuCache.Serializer"; + private const string IsRebuildingDatabaseCacheRuntimeCacheKey = "temp_database_cache_rebuild_op"; + private readonly IDatabaseCacheRepository _databaseCacheRepository; private readonly ICoreScopeProvider _coreScopeProvider; private readonly IOptions<NuCacheSettings> _nucacheSettings; private readonly IKeyValueService _keyValueService; private readonly ILogger<DatabaseCacheRebuilder> _logger; private readonly IProfilingLogger _profilingLogger; + private readonly IBackgroundTaskQueue _backgroundTaskQueue; + private readonly IAppPolicyCache _runtimeCache; + /// <summary> + /// Initializes a new instance of the <see cref="DatabaseCacheRebuilder"/> class. + /// </summary> public DatabaseCacheRebuilder( IDatabaseCacheRepository databaseCacheRepository, ICoreScopeProvider coreScopeProvider, IOptions<NuCacheSettings> nucacheSettings, IKeyValueService keyValueService, - ILogger<DatabaseCacheRebuilder> logger, IProfilingLogger profilingLogger) + ILogger<DatabaseCacheRebuilder> logger, + IProfilingLogger profilingLogger, + IBackgroundTaskQueue backgroundTaskQueue, + IAppPolicyCache runtimeCache) { _databaseCacheRepository = databaseCacheRepository; _coreScopeProvider = coreScopeProvider; @@ -32,18 +47,63 @@ public DatabaseCacheRebuilder( _keyValueService = keyValueService; _logger = logger; _profilingLogger = profilingLogger; + _backgroundTaskQueue = backgroundTaskQueue; + _runtimeCache = runtimeCache; } - public void Rebuild() + /// <inheritdoc/> + public bool IsRebuilding() => _runtimeCache.Get(IsRebuildingDatabaseCacheRuntimeCacheKey) is not null; + + /// <inheritdoc/> + public void Rebuild() => Rebuild(false); + + /// <inheritdoc/> + public void Rebuild(bool useBackgroundThread) { - using ICoreScope scope = _coreScopeProvider.CreateCoreScope(); - _databaseCacheRepository.Rebuild(); - scope.Complete(); + if (useBackgroundThread) + { + _logger.LogInformation("Starting async background thread for rebuilding database cache."); + + _backgroundTaskQueue.QueueBackgroundWorkItem( + cancellationToken => + { + using (ExecutionContext.SuppressFlow()) + { + Task.Run(() => PerformRebuild()); + return Task.CompletedTask; + } + }); + } + else + { + PerformRebuild(); + } } + private void PerformRebuild() + { + try + { + SetIsRebuilding(); + + using ICoreScope scope = _coreScopeProvider.CreateCoreScope(); + _databaseCacheRepository.Rebuild(); + scope.Complete(); + } + finally + { + ClearIsRebuilding(); + } + } + + private void SetIsRebuilding() => _runtimeCache.Insert(IsRebuildingDatabaseCacheRuntimeCacheKey, () => "tempValue", TimeSpan.FromMinutes(10)); + + private void ClearIsRebuilding() => _runtimeCache.Clear(IsRebuildingDatabaseCacheRuntimeCacheKey); + + /// <inheritdoc/> public void RebuildDatabaseCacheIfSerializerChanged() { - using var scope = _coreScopeProvider.CreateCoreScope(); + using ICoreScope scope = _coreScopeProvider.CreateCoreScope(); NuCacheSerializerType serializer = _nucacheSettings.Value.NuCacheSerializerType; var currentSerializerValue = _keyValueService.GetValue(NuCacheSerializerKey); @@ -54,11 +114,12 @@ public void RebuildDatabaseCacheIfSerializerChanged() _logger.LogWarning( "Database cache was serialized using {CurrentSerializer}. Currently configured cache serializer {Serializer}. Rebuilding database cache.", - currentSerializer, serializer); + currentSerializer, + serializer); using (_profilingLogger.TraceDuration<DatabaseCacheRebuilder>($"Rebuilding database cache with {serializer} serializer")) { - Rebuild(); + Rebuild(false); _keyValueService.SetValue(NuCacheSerializerKey, serializer.ToString()); } diff --git a/src/Umbraco.Web.Common/AspNetCore/AspNetCoreBackOfficeInfo.cs b/src/Umbraco.Web.Common/AspNetCore/AspNetCoreBackOfficeInfo.cs index 8522ef59c55a..b1351d4d3dd8 100644 --- a/src/Umbraco.Web.Common/AspNetCore/AspNetCoreBackOfficeInfo.cs +++ b/src/Umbraco.Web.Common/AspNetCore/AspNetCoreBackOfficeInfo.cs @@ -3,23 +3,21 @@ using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Hosting; using Umbraco.Cms.Core.Routing; -using static Umbraco.Cms.Core.Constants; namespace Umbraco.Cms.Web.Common.AspNetCore; public class AspNetCoreBackOfficeInfo : IBackOfficeInfo { - private readonly IOptionsMonitor<GlobalSettings> _globalSettings; private readonly IHostingEnvironment _hostingEnvironment; private string? _getAbsoluteUrl; - public AspNetCoreBackOfficeInfo( - IOptionsMonitor<GlobalSettings> globalSettings, - IHostingEnvironment hostingEnviroment) - { - _globalSettings = globalSettings; - _hostingEnvironment = hostingEnviroment; - } + public AspNetCoreBackOfficeInfo(IHostingEnvironment hostingEnviroment) + => _hostingEnvironment = hostingEnviroment; + + [Obsolete("The globalSettings parameter is not required anymore, use the other constructor instead. Scheduled for removal in Umbraco 17.")] + public AspNetCoreBackOfficeInfo(IOptionsMonitor<GlobalSettings> globalSettings, IHostingEnvironment hostingEnviroment) + : this(hostingEnviroment) + { } public string GetAbsoluteUrl { @@ -34,7 +32,7 @@ public string GetAbsoluteUrl _getAbsoluteUrl = WebPath.Combine( _hostingEnvironment.ApplicationMainUrl.ToString(), - Core.Constants.System.DefaultUmbracoPath.TrimStart(CharArrays.TildeForwardSlash)); + Core.Constants.System.DefaultUmbracoPath.TrimStart(Core.Constants.CharArrays.TildeForwardSlash)); } return _getAbsoluteUrl; diff --git a/src/Umbraco.Web.Common/Extensions/HttpContextExtensions.cs b/src/Umbraco.Web.Common/Extensions/HttpContextExtensions.cs index fd46ef6903af..cf9022951386 100644 --- a/src/Umbraco.Web.Common/Extensions/HttpContextExtensions.cs +++ b/src/Umbraco.Web.Common/Extensions/HttpContextExtensions.cs @@ -68,7 +68,13 @@ public static async Task<AuthenticateResult> AuthenticateBackOfficeAsync(this Ht // Otherwise we can't log in as both a member and a backoffice user // For instance if you've enabled basic auth. ClaimsPrincipal? authenticatedPrincipal = result.Principal; - IEnumerable<ClaimsIdentity> existingIdentities = httpContext.User.Identities.Where(x => x.IsAuthenticated && x.AuthenticationType != authenticatedPrincipal.Identity.AuthenticationType); + + // Make sure to copy into a list before attempting to update the authenticated principal, so we don't attempt to modify + // the collection while iterating it. + // See: https://github.com/umbraco/Umbraco-CMS/issues/18509 + var existingIdentities = httpContext.User.Identities + .Where(x => x.IsAuthenticated && x.AuthenticationType != authenticatedPrincipal.Identity.AuthenticationType) + .ToList(); authenticatedPrincipal.AddIdentities(existingIdentities); httpContext.User = authenticatedPrincipal; diff --git a/src/Umbraco.Web.Common/Extensions/ServiceCollectionExtensions.cs b/src/Umbraco.Web.Common/Extensions/ServiceCollectionExtensions.cs index 581508ecf6d9..ce75b4b1b5a1 100644 --- a/src/Umbraco.Web.Common/Extensions/ServiceCollectionExtensions.cs +++ b/src/Umbraco.Web.Common/Extensions/ServiceCollectionExtensions.cs @@ -208,7 +208,7 @@ public static TypeLoader AddTypeLoader( var typeFinder = new TypeFinder( loggerFactory.CreateLogger<TypeFinder>(), assemblyProvider, - typeFinderSettings.AdditionalAssemblyExclusionEntries, + typeFinderSettings.AdditionalAssemblyExclusionEntries.ToArray(), typeFinderConfig); var typeLoader = new TypeLoader(typeFinder, loggerFactory.CreateLogger<TypeLoader>()); diff --git a/src/Umbraco.Web.Common/Hosting/UmbracoBackOfficePathGenerator.cs b/src/Umbraco.Web.Common/Hosting/UmbracoBackOfficePathGenerator.cs index 8928f986c7ea..ba5d3c0d83fe 100644 --- a/src/Umbraco.Web.Common/Hosting/UmbracoBackOfficePathGenerator.cs +++ b/src/Umbraco.Web.Common/Hosting/UmbracoBackOfficePathGenerator.cs @@ -1,6 +1,4 @@ -using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration; -using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Hosting; using Umbraco.Extensions; @@ -12,12 +10,9 @@ public class UmbracoBackOfficePathGenerator : IBackOfficePathGenerator private string? _backofficeAssetsPath; private string? _backOfficeVirtualDirectory; - public UmbracoBackOfficePathGenerator( - IHostingEnvironment hostingEnvironment, - IUmbracoVersion umbracoVersion, - IOptions<GlobalSettings> globalSettings) + public UmbracoBackOfficePathGenerator(IHostingEnvironment hostingEnvironment, IUmbracoVersion umbracoVersion) { - BackOfficePath = globalSettings.Value.GetBackOfficePath(hostingEnvironment); + BackOfficePath = hostingEnvironment.GetBackOfficePath(); BackOfficeCacheBustHash = UrlHelperExtensions.GetCacheBustHash(hostingEnvironment, umbracoVersion); } diff --git a/src/Umbraco.Web.Common/ModelsBuilder/DependencyInjection/UmbracoBuilderDependencyInjectionExtensions.cs b/src/Umbraco.Web.Common/ModelsBuilder/DependencyInjection/UmbracoBuilderDependencyInjectionExtensions.cs index 3784781413e2..045a5107bb21 100644 --- a/src/Umbraco.Web.Common/ModelsBuilder/DependencyInjection/UmbracoBuilderDependencyInjectionExtensions.cs +++ b/src/Umbraco.Web.Common/ModelsBuilder/DependencyInjection/UmbracoBuilderDependencyInjectionExtensions.cs @@ -15,6 +15,7 @@ using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Infrastructure.ModelsBuilder; using Umbraco.Cms.Infrastructure.ModelsBuilder.Building; +using Umbraco.Cms.Infrastructure.ModelsBuilder.Options; using Umbraco.Cms.Web.Common.ModelsBuilder; using Umbraco.Cms.Web.Common.ModelsBuilder.InMemoryAuto; @@ -141,6 +142,8 @@ public static IUmbracoBuilder AddModelsBuilder(this IUmbracoBuilder builder) builder.Services.AddSingleton<OutOfDateModelsStatus>(); builder.Services.AddSingleton<ModelsGenerationError>(); + builder.Services.ConfigureOptions<ConfigurePropertySettingsOptions>(); + return builder; } diff --git a/src/Umbraco.Web.Common/Mvc/UmbracoMvcConfigureOptions.cs b/src/Umbraco.Web.Common/Mvc/UmbracoMvcConfigureOptions.cs index 8131a03160e0..32769f61384f 100644 --- a/src/Umbraco.Web.Common/Mvc/UmbracoMvcConfigureOptions.cs +++ b/src/Umbraco.Web.Common/Mvc/UmbracoMvcConfigureOptions.cs @@ -16,10 +16,12 @@ namespace Umbraco.Cms.Web.Common.Mvc; /// </remarks> public class UmbracoMvcConfigureOptions : IConfigureOptions<MvcOptions> { - private readonly GlobalSettings _globalSettings; + public UmbracoMvcConfigureOptions() + { } + [Obsolete("The global settings is not required anymore, use the default constructor instead. Scheduled for removal in Umbraco 17.")] public UmbracoMvcConfigureOptions(IOptions<GlobalSettings> globalSettings) - => _globalSettings = globalSettings.Value; + { } /// <inheritdoc /> public void Configure(MvcOptions options) diff --git a/src/Umbraco.Web.Common/Profiler/ConfigureMiniProfilerOptions.cs b/src/Umbraco.Web.Common/Profiler/ConfigureMiniProfilerOptions.cs index a3cda29434ca..fc0565116a3a 100644 --- a/src/Umbraco.Web.Common/Profiler/ConfigureMiniProfilerOptions.cs +++ b/src/Umbraco.Web.Common/Profiler/ConfigureMiniProfilerOptions.cs @@ -4,7 +4,6 @@ using Microsoft.Extensions.Options; using OpenIddict.Abstractions; using StackExchange.Profiling; -using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Hosting; using Umbraco.Cms.Core.Routing; using Umbraco.Extensions; @@ -15,8 +14,8 @@ internal sealed class ConfigureMiniProfilerOptions : IConfigureOptions<MiniProfi { private readonly string _backOfficePath; - public ConfigureMiniProfilerOptions(IOptions<GlobalSettings> globalSettings, IHostingEnvironment hostingEnvironment) - => _backOfficePath = globalSettings.Value.GetBackOfficePath(hostingEnvironment); + public ConfigureMiniProfilerOptions(IHostingEnvironment hostingEnvironment) + => _backOfficePath = hostingEnvironment.GetBackOfficePath(); public void Configure(MiniProfilerOptions options) { @@ -41,6 +40,5 @@ private async Task<bool> IsBackofficeUserAuthorized(HttpRequest request) return identity?.GetClaims(Core.Constants.Security.AllowedApplicationsClaimType) .InvariantContains(Core.Constants.Applications.Settings) ?? false; - } } diff --git a/src/Umbraco.Web.Common/Views/UmbracoViewPage.cs b/src/Umbraco.Web.Common/Views/UmbracoViewPage.cs index 70d2663864d3..c20866855728 100644 --- a/src/Umbraco.Web.Common/Views/UmbracoViewPage.cs +++ b/src/Umbraco.Web.Common/Views/UmbracoViewPage.cs @@ -9,6 +9,7 @@ using Microsoft.Extensions.Options; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Hosting; using Umbraco.Cms.Core.Logging; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.PublishedContent; @@ -141,7 +142,7 @@ public void WriteUmbracoContent(TagHelperOutput tagHelperOutput) markupToInject = string.Format( ContentSettings.PreviewBadge, - HostingEnvironment.ToAbsolute(Core.Constants.System.DefaultUmbracoPath), + HostingEnvironment.GetBackOfficePath(), System.Web.HttpUtility.HtmlEncode(Context.Request.GetEncodedUrl()), // Belt and braces - via a browser at least it doesn't seem possible to have anything other than // a valid culture code provided in the querystring of this URL. // But just to be sure of prevention of an XSS vulnterablity we'll HTML encode here too. diff --git a/src/Umbraco.Web.UI.Client/package-lock.json b/src/Umbraco.Web.UI.Client/package-lock.json index e7d343747c64..577fc2af425b 100644 --- a/src/Umbraco.Web.UI.Client/package-lock.json +++ b/src/Umbraco.Web.UI.Client/package-lock.json @@ -1,12 +1,12 @@ { "name": "@umbraco-cms/backoffice", - "version": "15.3.0-rc", + "version": "16.0.0-rc", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@umbraco-cms/backoffice", - "version": "15.3.0-rc", + "version": "16.0.0-rc", "license": "MIT", "workspaces": [ "./src/packages/*" @@ -27,8 +27,8 @@ "@tiptap/pm": "2.11.5", "@tiptap/starter-kit": "2.11.5", "@types/diff": "^7.0.1", - "@umbraco-ui/uui": "^1.12.2", - "@umbraco-ui/uui-css": "^1.12.1", + "@umbraco-ui/uui": "^1.13.0-rc.1", + "@umbraco-ui/uui-css": "^1.13.0-rc.0", "diff": "^7.0.0", "dompurify": "^3.2.4", "element-internals-polyfill": "^1.3.13", @@ -4453,897 +4453,908 @@ "link": true }, "node_modules/@umbraco-ui/uui": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui/-/uui-1.12.2.tgz", - "integrity": "sha512-oEqt0ysOpqlpMk7AOX+88aV0dgnHfSXxE6imJw0KQKNMnZNOKv7EpndGliLJW/N2hgXQoVPESeYAfbLLt8J0MQ==", - "license": "MIT", - "dependencies": { - "@umbraco-ui/uui-action-bar": "1.12.2", - "@umbraco-ui/uui-avatar": "1.12.2", - "@umbraco-ui/uui-avatar-group": "1.12.2", - "@umbraco-ui/uui-badge": "1.12.2", - "@umbraco-ui/uui-base": "1.12.2", - "@umbraco-ui/uui-boolean-input": "1.12.2", - "@umbraco-ui/uui-box": "1.12.2", - "@umbraco-ui/uui-breadcrumbs": "1.12.2", - "@umbraco-ui/uui-button": "1.12.2", - "@umbraco-ui/uui-button-group": "1.12.2", - "@umbraco-ui/uui-button-inline-create": "1.12.2", - "@umbraco-ui/uui-card": "1.12.2", - "@umbraco-ui/uui-card-block-type": "1.12.2", - "@umbraco-ui/uui-card-content-node": "1.12.2", - "@umbraco-ui/uui-card-media": "1.12.2", - "@umbraco-ui/uui-card-user": "1.12.2", - "@umbraco-ui/uui-caret": "1.12.2", - "@umbraco-ui/uui-checkbox": "1.12.2", - "@umbraco-ui/uui-color-area": "1.12.2", - "@umbraco-ui/uui-color-picker": "1.12.2", - "@umbraco-ui/uui-color-slider": "1.12.2", - "@umbraco-ui/uui-color-swatch": "1.12.2", - "@umbraco-ui/uui-color-swatches": "1.12.2", - "@umbraco-ui/uui-combobox": "1.12.2", - "@umbraco-ui/uui-combobox-list": "1.12.2", - "@umbraco-ui/uui-css": "1.12.1", - "@umbraco-ui/uui-dialog": "1.12.2", - "@umbraco-ui/uui-dialog-layout": "1.12.2", - "@umbraco-ui/uui-file-dropzone": "1.12.2", - "@umbraco-ui/uui-file-preview": "1.12.2", - "@umbraco-ui/uui-form": "1.12.2", - "@umbraco-ui/uui-form-layout-item": "1.12.2", - "@umbraco-ui/uui-form-validation-message": "1.12.2", - "@umbraco-ui/uui-icon": "1.12.2", - "@umbraco-ui/uui-icon-registry": "1.12.2", - "@umbraco-ui/uui-icon-registry-essential": "1.12.2", - "@umbraco-ui/uui-input": "1.12.2", - "@umbraco-ui/uui-input-file": "1.12.2", - "@umbraco-ui/uui-input-lock": "1.12.2", - "@umbraco-ui/uui-input-password": "1.12.2", - "@umbraco-ui/uui-keyboard-shortcut": "1.12.2", - "@umbraco-ui/uui-label": "1.12.2", - "@umbraco-ui/uui-loader": "1.12.2", - "@umbraco-ui/uui-loader-bar": "1.12.2", - "@umbraco-ui/uui-loader-circle": "1.12.2", - "@umbraco-ui/uui-menu-item": "1.12.2", - "@umbraco-ui/uui-modal": "1.12.2", - "@umbraco-ui/uui-pagination": "1.12.2", - "@umbraco-ui/uui-popover": "1.12.2", - "@umbraco-ui/uui-popover-container": "1.12.2", - "@umbraco-ui/uui-progress-bar": "1.12.2", - "@umbraco-ui/uui-radio": "1.12.2", - "@umbraco-ui/uui-range-slider": "1.12.2", - "@umbraco-ui/uui-ref": "1.12.2", - "@umbraco-ui/uui-ref-list": "1.12.2", - "@umbraco-ui/uui-ref-node": "1.12.2", - "@umbraco-ui/uui-ref-node-data-type": "1.12.2", - "@umbraco-ui/uui-ref-node-document-type": "1.12.2", - "@umbraco-ui/uui-ref-node-form": "1.12.2", - "@umbraco-ui/uui-ref-node-member": "1.12.2", - "@umbraco-ui/uui-ref-node-package": "1.12.2", - "@umbraco-ui/uui-ref-node-user": "1.12.2", - "@umbraco-ui/uui-scroll-container": "1.12.2", - "@umbraco-ui/uui-select": "1.12.2", - "@umbraco-ui/uui-slider": "1.12.2", - "@umbraco-ui/uui-symbol-expand": "1.12.2", - "@umbraco-ui/uui-symbol-file": "1.12.2", - "@umbraco-ui/uui-symbol-file-dropzone": "1.12.2", - "@umbraco-ui/uui-symbol-file-thumbnail": "1.12.2", - "@umbraco-ui/uui-symbol-folder": "1.12.2", - "@umbraco-ui/uui-symbol-lock": "1.12.2", - "@umbraco-ui/uui-symbol-more": "1.12.2", - "@umbraco-ui/uui-symbol-sort": "1.12.2", - "@umbraco-ui/uui-table": "1.12.2", - "@umbraco-ui/uui-tabs": "1.12.2", - "@umbraco-ui/uui-tag": "1.12.2", - "@umbraco-ui/uui-textarea": "1.12.2", - "@umbraco-ui/uui-toast-notification": "1.12.2", - "@umbraco-ui/uui-toast-notification-container": "1.12.2", - "@umbraco-ui/uui-toast-notification-layout": "1.12.2", - "@umbraco-ui/uui-toggle": "1.12.2", - "@umbraco-ui/uui-visually-hidden": "1.12.2" + "version": "1.13.0-rc.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui/-/uui-1.13.0-rc.1.tgz", + "integrity": "sha512-3UB6ceGw95V6yln72WOXH2FRA/MDK32DroK+HxlubhG7JzkhlhMSeQdF9P4Awb16i4ZEaWz6wY5XAP9VlFpimQ==", + "license": "MIT", + "dependencies": { + "@umbraco-ui/uui-action-bar": "1.13.0-rc.0", + "@umbraco-ui/uui-avatar": "1.13.0-rc.0", + "@umbraco-ui/uui-avatar-group": "1.13.0-rc.0", + "@umbraco-ui/uui-badge": "1.13.0-rc.0", + "@umbraco-ui/uui-base": "1.13.0-rc.0", + "@umbraco-ui/uui-boolean-input": "1.13.0-rc.0", + "@umbraco-ui/uui-box": "1.13.0-rc.0", + "@umbraco-ui/uui-breadcrumbs": "1.13.0-rc.0", + "@umbraco-ui/uui-button": "1.13.0-rc.0", + "@umbraco-ui/uui-button-copy-text": "1.13.0-rc.0", + "@umbraco-ui/uui-button-group": "1.13.0-rc.0", + "@umbraco-ui/uui-button-inline-create": "1.13.0-rc.0", + "@umbraco-ui/uui-card": "1.13.0-rc.0", + "@umbraco-ui/uui-card-block-type": "1.13.0-rc.0", + "@umbraco-ui/uui-card-content-node": "1.13.0-rc.0", + "@umbraco-ui/uui-card-media": "1.13.0-rc.0", + "@umbraco-ui/uui-card-user": "1.13.0-rc.0", + "@umbraco-ui/uui-caret": "1.13.0-rc.0", + "@umbraco-ui/uui-checkbox": "1.13.0-rc.0", + "@umbraco-ui/uui-color-area": "1.13.0-rc.0", + "@umbraco-ui/uui-color-picker": "1.13.0-rc.0", + "@umbraco-ui/uui-color-slider": "1.13.0-rc.0", + "@umbraco-ui/uui-color-swatch": "1.13.0-rc.0", + "@umbraco-ui/uui-color-swatches": "1.13.0-rc.0", + "@umbraco-ui/uui-combobox": "1.13.0-rc.0", + "@umbraco-ui/uui-combobox-list": "1.13.0-rc.0", + "@umbraco-ui/uui-css": "1.13.0-rc.0", + "@umbraco-ui/uui-dialog": "1.13.0-rc.0", + "@umbraco-ui/uui-dialog-layout": "1.13.0-rc.0", + "@umbraco-ui/uui-file-dropzone": "1.13.0-rc.0", + "@umbraco-ui/uui-file-preview": "1.13.0-rc.0", + "@umbraco-ui/uui-form": "1.13.0-rc.0", + "@umbraco-ui/uui-form-layout-item": "1.13.0-rc.0", + "@umbraco-ui/uui-form-validation-message": "1.13.0-rc.0", + "@umbraco-ui/uui-icon": "1.13.0-rc.0", + "@umbraco-ui/uui-icon-registry": "1.13.0-rc.0", + "@umbraco-ui/uui-icon-registry-essential": "1.13.0-rc.0", + "@umbraco-ui/uui-input": "1.13.0-rc.0", + "@umbraco-ui/uui-input-file": "1.13.0-rc.0", + "@umbraco-ui/uui-input-lock": "1.13.0-rc.0", + "@umbraco-ui/uui-input-password": "1.13.0-rc.0", + "@umbraco-ui/uui-keyboard-shortcut": "1.13.0-rc.0", + "@umbraco-ui/uui-label": "1.13.0-rc.0", + "@umbraco-ui/uui-loader": "1.13.0-rc.0", + "@umbraco-ui/uui-loader-bar": "1.13.0-rc.0", + "@umbraco-ui/uui-loader-circle": "1.13.0-rc.0", + "@umbraco-ui/uui-menu-item": "1.13.0-rc.0", + "@umbraco-ui/uui-modal": "1.13.0-rc.0", + "@umbraco-ui/uui-pagination": "1.13.0-rc.0", + "@umbraco-ui/uui-popover": "1.13.0-rc.0", + "@umbraco-ui/uui-popover-container": "1.13.0-rc.0", + "@umbraco-ui/uui-progress-bar": "1.13.0-rc.0", + "@umbraco-ui/uui-radio": "1.13.0-rc.0", + "@umbraco-ui/uui-range-slider": "1.13.0-rc.0", + "@umbraco-ui/uui-ref": "1.13.0-rc.0", + "@umbraco-ui/uui-ref-list": "1.13.0-rc.0", + "@umbraco-ui/uui-ref-node": "1.13.0-rc.0", + "@umbraco-ui/uui-ref-node-data-type": "1.13.0-rc.0", + "@umbraco-ui/uui-ref-node-document-type": "1.13.0-rc.0", + "@umbraco-ui/uui-ref-node-form": "1.13.0-rc.0", + "@umbraco-ui/uui-ref-node-member": "1.13.0-rc.0", + "@umbraco-ui/uui-ref-node-package": "1.13.0-rc.0", + "@umbraco-ui/uui-ref-node-user": "1.13.0-rc.0", + "@umbraco-ui/uui-scroll-container": "1.13.0-rc.0", + "@umbraco-ui/uui-select": "1.13.0-rc.0", + "@umbraco-ui/uui-slider": "1.13.0-rc.0", + "@umbraco-ui/uui-symbol-expand": "1.13.0-rc.0", + "@umbraco-ui/uui-symbol-file": "1.13.0-rc.0", + "@umbraco-ui/uui-symbol-file-dropzone": "1.13.0-rc.0", + "@umbraco-ui/uui-symbol-file-thumbnail": "1.13.0-rc.0", + "@umbraco-ui/uui-symbol-folder": "1.13.0-rc.0", + "@umbraco-ui/uui-symbol-lock": "1.13.0-rc.0", + "@umbraco-ui/uui-symbol-more": "1.13.0-rc.0", + "@umbraco-ui/uui-symbol-sort": "1.13.0-rc.0", + "@umbraco-ui/uui-table": "1.13.0-rc.0", + "@umbraco-ui/uui-tabs": "1.13.0-rc.0", + "@umbraco-ui/uui-tag": "1.13.0-rc.0", + "@umbraco-ui/uui-textarea": "1.13.0-rc.0", + "@umbraco-ui/uui-toast-notification": "1.13.0-rc.0", + "@umbraco-ui/uui-toast-notification-container": "1.13.0-rc.0", + "@umbraco-ui/uui-toast-notification-layout": "1.13.0-rc.0", + "@umbraco-ui/uui-toggle": "1.13.0-rc.0", + "@umbraco-ui/uui-visually-hidden": "1.13.0-rc.0" } }, "node_modules/@umbraco-ui/uui-action-bar": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-action-bar/-/uui-action-bar-1.12.2.tgz", - "integrity": "sha512-ZWTO7//oKxo5vpA+RypyxpfVMPi5f8f1uevbJ8PMdizDi67VxN1kxYA4geMzG8OQ+x5IGp01DCTtVeAx3qoJbg==", + "version": "1.13.0-rc.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-action-bar/-/uui-action-bar-1.13.0-rc.0.tgz", + "integrity": "sha512-I8kRPYkvOrU6QNkt9Hnt5Xt3L2XhMx9RDq02iQSKTc/ye5twLapFRpVoGP7EgFsFlEcK3uNY6+1JKFvhtPbxvw==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2", - "@umbraco-ui/uui-button-group": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0-rc.0", + "@umbraco-ui/uui-button-group": "1.13.0-rc.0" } }, "node_modules/@umbraco-ui/uui-avatar": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-avatar/-/uui-avatar-1.12.2.tgz", - "integrity": "sha512-b/TkEIGJoouqCZLIBl/c0veJg8imImd35Ed+R1VPlcHFXrgpO8C54Fr0AEwsM5x5OeTtkfvs/18pveLPucraww==", + "version": "1.13.0-rc.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-avatar/-/uui-avatar-1.13.0-rc.0.tgz", + "integrity": "sha512-C/YgiwiAvTGe1rd038W9DTbXzsS5Wyo4VQX7ZwN/BrvxEp8AHOF0bZuDyk8dVIGRTNsbqcY87USy1YaaR7Tp9w==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0-rc.0" } }, "node_modules/@umbraco-ui/uui-avatar-group": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-avatar-group/-/uui-avatar-group-1.12.2.tgz", - "integrity": "sha512-QdymxxxC6qCRAu8vAM7Owgbe/ubZ+BL+wu0qk8RXz77CVORgLpiFeUM4YwOapOXvtogXR6haxf8m3/7nxedqdg==", + "version": "1.13.0-rc.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-avatar-group/-/uui-avatar-group-1.13.0-rc.0.tgz", + "integrity": "sha512-u84yuC9dF5oGy3lgQBjU8XYe4SeLebWrIJVdFFFHUn2kENpqpvY9ImtZZiKDcKdTAxvud4yS1zBgBDV5iF6VXg==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-avatar": "1.12.2", - "@umbraco-ui/uui-base": "1.12.2" + "@umbraco-ui/uui-avatar": "1.13.0-rc.0", + "@umbraco-ui/uui-base": "1.13.0-rc.0" } }, "node_modules/@umbraco-ui/uui-badge": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-badge/-/uui-badge-1.12.2.tgz", - "integrity": "sha512-jkD8rHvunbUDNZfDCekuP5DI23ufBZD+8Y3FHv5aLOAbRm9XrbJ0B4QHyKQoglQ2Yao6iKeYq+nxzG2x88Z7Dw==", + "version": "1.13.0-rc.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-badge/-/uui-badge-1.13.0-rc.0.tgz", + "integrity": "sha512-DcSW25MUHiUbMOlYaqrKX82wLQflT3ew9tpGCyLbgfT1/77NX9UL8i5F+KtMvcPmwaMJ9zJTMtCQE8pCcAbhhw==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0-rc.0" } }, "node_modules/@umbraco-ui/uui-base": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-base/-/uui-base-1.12.2.tgz", - "integrity": "sha512-EyPrP28teYlGeeTZvmq+4wzP8Gh9A963HbZ1nQ3oyGj+twN6QjEKUF7W4VVZ8RvFoyS1/6bWkRODuZAzAwX31g==", + "version": "1.13.0-rc.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-base/-/uui-base-1.13.0-rc.0.tgz", + "integrity": "sha512-4NJoGJ2gi8m0rFoDOEJOHNHTJTZ3H0kWHx1J5KXclHOmA7v27QifirAgKV/7M6AG3ZtTYuLoHZKNvRvOoGuDkg==", "license": "MIT", "peerDependencies": { "lit": ">=2.8.0" } }, "node_modules/@umbraco-ui/uui-boolean-input": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-boolean-input/-/uui-boolean-input-1.12.2.tgz", - "integrity": "sha512-/NGwAPgXLiaDIMwunTDth21jQ0+5ajH3gJ5JJH6IGIq+N2g7babAEKybkZybYq+mxH//7ljH/uKDHI9IztW58g==", + "version": "1.13.0-rc.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-boolean-input/-/uui-boolean-input-1.13.0-rc.0.tgz", + "integrity": "sha512-fR7iw8EmL5oyTv24tzHnLK2i5+wOpkyhZkId2JPliBYcv0nku7GAg4qc3MCFYnXOPL/qOFsgoVcdDS8n176X9Q==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0-rc.0" } }, "node_modules/@umbraco-ui/uui-box": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-box/-/uui-box-1.12.2.tgz", - "integrity": "sha512-JUxqsRjqUbZ5NM5S1w40NUlHUHPIcMFqYTeCq+nLHE9WSLchym3NN+0NZjS2+qpO70kYPGlKf39mahy+rbGP9Q==", + "version": "1.13.0-rc.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-box/-/uui-box-1.13.0-rc.0.tgz", + "integrity": "sha512-l9Q89lv5W+pRjYP2xmtGfxKXQDQqTkkrU5S6+DaD+tlODVhswQl2qzCS4D9ZDmuPa+fQuFrUhjd+eChmJtNtrQ==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2", - "@umbraco-ui/uui-css": "1.12.1" + "@umbraco-ui/uui-base": "1.13.0-rc.0", + "@umbraco-ui/uui-css": "1.13.0-rc.0" } }, "node_modules/@umbraco-ui/uui-breadcrumbs": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-breadcrumbs/-/uui-breadcrumbs-1.12.2.tgz", - "integrity": "sha512-P/L4q5whw1/HVMMUmzgq5CYOu3ZoLmtlTUoOnTXj+g5R0ziX5ikjJWF1JnLa6M7ES43aB/7su9GeyvOMkcxMpA==", + "version": "1.13.0-rc.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-breadcrumbs/-/uui-breadcrumbs-1.13.0-rc.0.tgz", + "integrity": "sha512-R66XNuYRFuk19/mpTsL4tkUEslt2/KTzz3p2s2caKr7caceEsuXE930+qcc00i8tpJyKRJvn3zNdxOZgvmKlSQ==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0-rc.0" } }, "node_modules/@umbraco-ui/uui-button": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-button/-/uui-button-1.12.2.tgz", - "integrity": "sha512-x3zF+GLwfpc6W2vB3xLRX6g+hdKdEWMKLXtfl+WPOkocu8+EYzodrUHQg24/lO43j7ovy8c3t+zN8OhjnZMu2Q==", + "version": "1.13.0-rc.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-button/-/uui-button-1.13.0-rc.0.tgz", + "integrity": "sha512-szlp5Yf0rDH0w6cgeSmFOb5o902ggXBgZaYEpTwDj2nTgM7cVTOBqD/nzm52IqH7Rcfc/GHWwcxR616iF/ca4g==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2", - "@umbraco-ui/uui-icon-registry-essential": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0-rc.0", + "@umbraco-ui/uui-icon-registry-essential": "1.13.0-rc.0" + } + }, + "node_modules/@umbraco-ui/uui-button-copy-text": { + "version": "1.13.0-rc.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-button-copy-text/-/uui-button-copy-text-1.13.0-rc.0.tgz", + "integrity": "sha512-O/n9PJXRiuCMXjP16nFQ6EGu7zK7aoo+Rwezf8nVgeOzskoxMe4CdUOdxtBseXX3tEaA1+DVaPSZIsynZcSHHg==", + "license": "MIT", + "dependencies": { + "@umbraco-ui/uui-base": "1.13.0-rc.0", + "@umbraco-ui/uui-button": "1.13.0-rc.0" } }, "node_modules/@umbraco-ui/uui-button-group": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-button-group/-/uui-button-group-1.12.2.tgz", - "integrity": "sha512-VxWICU4hmYCORmo8JzXgSyzpa82/M3OyTxfn/kX+jHg0rk9vMg4JArQJp4NF9qhgOWsHx0ED5yURTTOtbNqFTQ==", + "version": "1.13.0-rc.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-button-group/-/uui-button-group-1.13.0-rc.0.tgz", + "integrity": "sha512-x0T77lBtrDFkRhhWOLX86GBQKPg6b0aBNUT4+YmtvHTDbxkMkxgvOM5IJrdbWaQq28M9pPg731Do488u9lbagQ==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0-rc.0" } }, "node_modules/@umbraco-ui/uui-button-inline-create": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-button-inline-create/-/uui-button-inline-create-1.12.2.tgz", - "integrity": "sha512-YvJTwlA2ZUhepHsmc/WwP3OqG7lkrlVmAcgG7uBbasNMwDYtLWcudMrv/NSHFrCpQe0VePyr7U4YtJqyQrbDTg==", + "version": "1.13.0-rc.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-button-inline-create/-/uui-button-inline-create-1.13.0-rc.0.tgz", + "integrity": "sha512-PxFE4zVHId2VYSy6DloA3syx4R49vv3QADOhSEUlc2RE64/eSqemew2AJomAZy8ifLBb4NG9/S4mzHeVs7w2pw==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0-rc.0" } }, "node_modules/@umbraco-ui/uui-card": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-card/-/uui-card-1.12.2.tgz", - "integrity": "sha512-/FqFYrQxKu38+s3y7XpiO8wW7Z2T7cyst2LvMajG+3U9KPi4A0pwxaRBlli4ay79/9V9uFEGTc4dKjB+jFKl6w==", + "version": "1.13.0-rc.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-card/-/uui-card-1.13.0-rc.0.tgz", + "integrity": "sha512-VnO1HiQsc61VRlogwEkVwU29bAMbjOXiGO+7VUPe4kJgeozOLNeFk+ecSctVtCqc2ktQNqJwTy3LxDOjDDFlMA==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0-rc.0" } }, "node_modules/@umbraco-ui/uui-card-block-type": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-card-block-type/-/uui-card-block-type-1.12.2.tgz", - "integrity": "sha512-aydgrznHaIUrJpHrwftjPtnaXVQOLe+r6VWrtyWNSPM4ivUeT5WaH/FVMc90Q6yWfIF3y2a3yCIQAGEqAXghhQ==", + "version": "1.13.0-rc.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-card-block-type/-/uui-card-block-type-1.13.0-rc.0.tgz", + "integrity": "sha512-DSc1O/ikj93AfjtxP7o9suG6unuF7n1DJ5tB7ZoXSDmjuzV8uaGqOHdhiZAWEjOOgV90m8XczKKoSNExh1djqg==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2", - "@umbraco-ui/uui-card": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0-rc.0", + "@umbraco-ui/uui-card": "1.13.0-rc.0" } }, "node_modules/@umbraco-ui/uui-card-content-node": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-card-content-node/-/uui-card-content-node-1.12.2.tgz", - "integrity": "sha512-yuNlbrjwphzMPv2xMHca8YUr+NH7FqeP0EjVjhhDSsOJVUZ8uj8Udoq4YIkypOAGAyG+N63jCzLvVTTR71LxGA==", + "version": "1.13.0-rc.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-card-content-node/-/uui-card-content-node-1.13.0-rc.0.tgz", + "integrity": "sha512-2nynvhpJYyTdVKtDqXBo0D4+BxD3uUbl3Gu5/j+cVcH9HgLEhm3EfkAINbqoLLHRMBDz1DzPEMBUnI8uEfbmSQ==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2", - "@umbraco-ui/uui-card": "1.12.2", - "@umbraco-ui/uui-icon": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0-rc.0", + "@umbraco-ui/uui-card": "1.13.0-rc.0", + "@umbraco-ui/uui-icon": "1.13.0-rc.0" } }, "node_modules/@umbraco-ui/uui-card-media": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-card-media/-/uui-card-media-1.12.2.tgz", - "integrity": "sha512-37Zful2c9UhDxw7qYWR2F2wdt5Qs5yMjcE0Q5R1ZRA5SFba7qgY0W4YW2iAAPMk2xvDyueaTnbVy1v6gG/jtYw==", + "version": "1.13.0-rc.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-card-media/-/uui-card-media-1.13.0-rc.0.tgz", + "integrity": "sha512-XJ5/gB7hRwPA+hhXd2oAVyfztW1h7y3zJ9pfrwce/7ZMWyRRVBhA5iH9sMkDLW0JoJ2yF5nVuRL/9OKm4K6npw==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2", - "@umbraco-ui/uui-card": "1.12.2", - "@umbraco-ui/uui-symbol-file": "1.12.2", - "@umbraco-ui/uui-symbol-folder": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0-rc.0", + "@umbraco-ui/uui-card": "1.13.0-rc.0", + "@umbraco-ui/uui-symbol-file": "1.13.0-rc.0", + "@umbraco-ui/uui-symbol-folder": "1.13.0-rc.0" } }, "node_modules/@umbraco-ui/uui-card-user": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-card-user/-/uui-card-user-1.12.2.tgz", - "integrity": "sha512-fwuYQvXjjiLTv0ykDpg+GpcoG3af3ZHUPTRbDa5W8ygAYlTRUvENSXc2qOUocy9XmXOa0p+P0NhenVSqOJpSIw==", + "version": "1.13.0-rc.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-card-user/-/uui-card-user-1.13.0-rc.0.tgz", + "integrity": "sha512-rhaWr7hb703MlrgsBODS3itaX4FDQmPL5St8Ndavq3Yp8R1eq4UjAe1CR3jZCyYGgptXpU9tzfZ1mN98mKY8rg==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-avatar": "1.12.2", - "@umbraco-ui/uui-base": "1.12.2", - "@umbraco-ui/uui-card": "1.12.2" + "@umbraco-ui/uui-avatar": "1.13.0-rc.0", + "@umbraco-ui/uui-base": "1.13.0-rc.0", + "@umbraco-ui/uui-card": "1.13.0-rc.0" } }, "node_modules/@umbraco-ui/uui-caret": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-caret/-/uui-caret-1.12.2.tgz", - "integrity": "sha512-7zVDVzvLszVld9E/pGSGFRgpp+rIipB1sY/r4xDYQ70g+ljlegOfMc3bvGs/topcMM+IlcQO8EOotlps4P44Jw==", + "version": "1.13.0-rc.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-caret/-/uui-caret-1.13.0-rc.0.tgz", + "integrity": "sha512-HfSkAD7yxdSSZptkv/fP8OKcRLJ2+RaBsRL769vAtHcN3G0HA/C7sNsfjsTtYsbIBOHqXCY9/o0xjkPfGyu/rQ==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0-rc.0" } }, "node_modules/@umbraco-ui/uui-checkbox": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-checkbox/-/uui-checkbox-1.12.2.tgz", - "integrity": "sha512-C6SSAUq9JfHHWCz9LLlOOmwET1vDsLKKiYv94LIqn8Zj4H3f1bRgUnSfVPVCfy1+p//Ut8SLw2vTFcTz0F21EA==", + "version": "1.13.0-rc.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-checkbox/-/uui-checkbox-1.13.0-rc.0.tgz", + "integrity": "sha512-Etl1BcjshNvlEfvev7x8F8ggiOcMHpq2klPgb21LEuuGA+TFbdh2xMnNOE0JnjmbsSPk7XnqDOIrhu3lv8uFlw==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2", - "@umbraco-ui/uui-boolean-input": "1.12.2", - "@umbraco-ui/uui-icon-registry-essential": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0-rc.0", + "@umbraco-ui/uui-boolean-input": "1.13.0-rc.0", + "@umbraco-ui/uui-icon-registry-essential": "1.13.0-rc.0" } }, "node_modules/@umbraco-ui/uui-color-area": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-color-area/-/uui-color-area-1.12.2.tgz", - "integrity": "sha512-W5qOBIvTiHGxFJcc1h3H+CdLHLY4K6QRIXU7I2BEII296PbUMwKaA8WFXAvwSq1KzmCkOJP2hPa4yxQ/qKBzJQ==", + "version": "1.13.0-rc.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-color-area/-/uui-color-area-1.13.0-rc.0.tgz", + "integrity": "sha512-Z8v37jkKhx+5pAOAyI0dZZkJiIxUeLyAIB/mRtyhaK7rI7V4TzeLjkjebP6byzy5PKbrhbgZP7JLiqgJo5J9vA==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2", + "@umbraco-ui/uui-base": "1.13.0-rc.0", "colord": "^2.9.3" } }, "node_modules/@umbraco-ui/uui-color-picker": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-color-picker/-/uui-color-picker-1.12.2.tgz", - "integrity": "sha512-t/FB6h1rdNzPa94dIfjGG50yRNmk/7wMjrktKjkZHt+wGWKvjM+I1RjatArZbCAmSV4EQH/7hqyvP6R1OoLIog==", + "version": "1.13.0-rc.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-color-picker/-/uui-color-picker-1.13.0-rc.0.tgz", + "integrity": "sha512-zX7+RmBviOELyXmdmvqPmLZ/Do/nG5l56k9mar0XmMjTtXF10/oDte2eMzJGozECtcmqAUfi2/OFf5iMebtxyQ==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2", - "@umbraco-ui/uui-popover-container": "1.12.2", + "@umbraco-ui/uui-base": "1.13.0-rc.0", + "@umbraco-ui/uui-popover-container": "1.13.0-rc.0", "colord": "^2.9.3" } }, "node_modules/@umbraco-ui/uui-color-slider": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-color-slider/-/uui-color-slider-1.12.2.tgz", - "integrity": "sha512-00LxQigqY+04eG0IzHY//Uf010u50DeCQ88ZvCV1MjPNH7T4auEC2/H/O7FYoHhwQB6Ez+ZpYA9ds/NbmTCuVg==", + "version": "1.13.0-rc.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-color-slider/-/uui-color-slider-1.13.0-rc.0.tgz", + "integrity": "sha512-du0z25sdwg7vIHXQSlPhq7gBKu4KBZdVsLjvbFkNYkC5iTasZJA/mJ/WgB7ikmXs14IUEJ0AGOlu4K3PtOzIgw==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0-rc.0" } }, "node_modules/@umbraco-ui/uui-color-swatch": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-color-swatch/-/uui-color-swatch-1.12.2.tgz", - "integrity": "sha512-fDODPeuKirwSyIOhEY46J7Ml5RJcuaeMyLBshWT9bl8pNts9zIlKSvn3oSlZ9mZ7N/Ym/3R2c+33i5avoA+rIA==", + "version": "1.13.0-rc.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-color-swatch/-/uui-color-swatch-1.13.0-rc.0.tgz", + "integrity": "sha512-z2CeNMmvyxT2zlMW4y+1fEQ22ug+sJOFb/cb6QQIQaOG4L4ncTmLsxEC4u/56W7r3Z0OkqC0LOU01GbWntSP5g==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2", - "@umbraco-ui/uui-icon-registry-essential": "1.12.2", + "@umbraco-ui/uui-base": "1.13.0-rc.0", + "@umbraco-ui/uui-icon-registry-essential": "1.13.0-rc.0", "colord": "^2.9.3" } }, "node_modules/@umbraco-ui/uui-color-swatches": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-color-swatches/-/uui-color-swatches-1.12.2.tgz", - "integrity": "sha512-kr9gYjYFQR8mavmDJS+I2t/n5wC6kWbCaZHnJzcs3unOX2jzKHnOqJ8N05y8vc2NZP1pOKSOzoIN1Y6N3qxU+g==", + "version": "1.13.0-rc.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-color-swatches/-/uui-color-swatches-1.13.0-rc.0.tgz", + "integrity": "sha512-u0xY1BZSj9OcyvjItyJyUdx1drQ53nkfzlg2I88fP5uzThw3ZFbu1FenEU1F19GhmLAytfRI5Jx/ZO9jB8yTFA==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2", - "@umbraco-ui/uui-color-swatch": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0-rc.0", + "@umbraco-ui/uui-color-swatch": "1.13.0-rc.0" } }, "node_modules/@umbraco-ui/uui-combobox": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-combobox/-/uui-combobox-1.12.2.tgz", - "integrity": "sha512-ln7IoQQJ65zknIl5k44E61S0DgW1e7fo/IEuMlgbrmkPnEbkLqV5HVYXIR3377VvfwqbZ44npxegOZBUuuWGlw==", + "version": "1.13.0-rc.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-combobox/-/uui-combobox-1.13.0-rc.0.tgz", + "integrity": "sha512-wzm1YM8xco6hW5hVIfL6jTXzzI5vTMBNgHtUhMwhqID9dszv0aXCw34grxaRLFV0/cExwhzswe4y98134KRoIA==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2", - "@umbraco-ui/uui-button": "1.12.2", - "@umbraco-ui/uui-combobox-list": "1.12.2", - "@umbraco-ui/uui-icon": "1.12.2", - "@umbraco-ui/uui-popover-container": "1.12.2", - "@umbraco-ui/uui-scroll-container": "1.12.2", - "@umbraco-ui/uui-symbol-expand": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0-rc.0", + "@umbraco-ui/uui-button": "1.13.0-rc.0", + "@umbraco-ui/uui-combobox-list": "1.13.0-rc.0", + "@umbraco-ui/uui-icon": "1.13.0-rc.0", + "@umbraco-ui/uui-popover-container": "1.13.0-rc.0", + "@umbraco-ui/uui-scroll-container": "1.13.0-rc.0", + "@umbraco-ui/uui-symbol-expand": "1.13.0-rc.0" } }, "node_modules/@umbraco-ui/uui-combobox-list": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-combobox-list/-/uui-combobox-list-1.12.2.tgz", - "integrity": "sha512-tBtQgQKB6kgPwRSkXM9kShNfC4Zed7V1hstCjVFy1wkRU+IinVYiN28NMNdSvDWmmxkRcIVOt7lY70T0fgPPMw==", + "version": "1.13.0-rc.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-combobox-list/-/uui-combobox-list-1.13.0-rc.0.tgz", + "integrity": "sha512-GRb0HM9wR9pRpoIvoxcBgoP/G0eXPKJhVRPl61XoSkRteN+Onw8hePczKF0QwS3kbAvlhwWS9DWSKFZImjo7hw==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0-rc.0" } }, "node_modules/@umbraco-ui/uui-css": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-css/-/uui-css-1.12.1.tgz", - "integrity": "sha512-cWdoJw3OjdZ5QUoXhUufp/8mdGkVJ4DiI7/NgPaU2GrMbo+c1Q2cx4ST2/K0Q7nY6qa4P4WCSLMoFGyFoOwLKQ==", + "version": "1.13.0-rc.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-css/-/uui-css-1.13.0-rc.0.tgz", + "integrity": "sha512-gzGT2M/SuU6hCVW+RmCg6kkXo1Qxu9EUai2Wyo98Xipz89xz9hU6Wagc0HWDAF9p6hoxClhpdg1SEFWCgMPPcg==", "license": "MIT", "peerDependencies": { "lit": ">=2.8.0" } }, "node_modules/@umbraco-ui/uui-dialog": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-dialog/-/uui-dialog-1.12.2.tgz", - "integrity": "sha512-YfHE4RTRKJiSi/ZCnZMJs+eImXx64JrZmu39bEb6FBAnMpqAMxeq70Nll4Nk43nL6liARv1bXP8OKZd2b7CPgQ==", + "version": "1.13.0-rc.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-dialog/-/uui-dialog-1.13.0-rc.0.tgz", + "integrity": "sha512-9bOi3hpsqQ/GU6WggYlK8LtgEokEoKd+zJQ6aO6070FeOfQSHAkAr0tbTE1/jM1gm7fK8jCp0atJi+L27YhO+A==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2", - "@umbraco-ui/uui-css": "1.12.1" + "@umbraco-ui/uui-base": "1.13.0-rc.0", + "@umbraco-ui/uui-css": "1.13.0-rc.0" } }, "node_modules/@umbraco-ui/uui-dialog-layout": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-dialog-layout/-/uui-dialog-layout-1.12.2.tgz", - "integrity": "sha512-Xy+Ocwia0xRcpUUARTdXgSgf5NIG2mlneDkiz6dsrIsFZ1IysXCnfh/4dXw57fneO+PyHI86bDwb9aFlWvve7Q==", + "version": "1.13.0-rc.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-dialog-layout/-/uui-dialog-layout-1.13.0-rc.0.tgz", + "integrity": "sha512-VlcR3vLlrXAcUhdKy6rTWN3cV/Xa3aYUANDvy0C1337uvIq1+xZSez+teoPE8ZUTg0ZcHlGud7lWaoycpxcGxQ==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0-rc.0" } }, "node_modules/@umbraco-ui/uui-file-dropzone": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-file-dropzone/-/uui-file-dropzone-1.12.2.tgz", - "integrity": "sha512-5B/1umH72IrxwlQ+4ivKDSIXXcGbfFuhvo98v1nuIF5MGl6wmoiG/lDilhny08RJMHwlcRkdYCtCChtuWEyVUg==", + "version": "1.13.0-rc.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-file-dropzone/-/uui-file-dropzone-1.13.0-rc.0.tgz", + "integrity": "sha512-rLErrqwSkB/3r0igsWTVWrVLG9NsV5oDBwDPn4p0eUbcHApTcLO28gYFKwZymLLwnCqGbTYKEDUYXBpVWTvbKg==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2", - "@umbraco-ui/uui-symbol-file-dropzone": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0-rc.0", + "@umbraco-ui/uui-symbol-file-dropzone": "1.13.0-rc.0" } }, "node_modules/@umbraco-ui/uui-file-preview": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-file-preview/-/uui-file-preview-1.12.2.tgz", - "integrity": "sha512-Oxkm7x3V/aCHPQDNh8loMESWswYCyDJeZazbhGig7mU6zbms7Vl3Vm46CIKEBva6IMy1p1AsNOgSjY4wmIvXsw==", + "version": "1.13.0-rc.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-file-preview/-/uui-file-preview-1.13.0-rc.0.tgz", + "integrity": "sha512-gI85LgfNWESw+Ivsav9S6nUplTX4g2l8lwMq83+WlvNe+sjqLowUfgfK4JyfZwx3iN9epvOMZ3bD24AbA7Ykmg==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2", - "@umbraco-ui/uui-symbol-file": "1.12.2", - "@umbraco-ui/uui-symbol-file-thumbnail": "1.12.2", - "@umbraco-ui/uui-symbol-folder": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0-rc.0", + "@umbraco-ui/uui-symbol-file": "1.13.0-rc.0", + "@umbraco-ui/uui-symbol-file-thumbnail": "1.13.0-rc.0", + "@umbraco-ui/uui-symbol-folder": "1.13.0-rc.0" } }, "node_modules/@umbraco-ui/uui-form": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-form/-/uui-form-1.12.2.tgz", - "integrity": "sha512-35CEeSCODTMaJi7JlvBl988tB0MIbocNg5ewCLeqm2CLVvW1UQi4V+835CY1fjgiR6D8co6Kz6KCR/9aibX5Gg==", + "version": "1.13.0-rc.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-form/-/uui-form-1.13.0-rc.0.tgz", + "integrity": "sha512-UBr+E7IZCFNCP/Vs3Yj6HWV1M/uu5+m/tsyyZZBOJbTzP2XmfceCPk69FNEDWNlSBs2MvkrGtsxs6J+0JVC/yA==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0-rc.0" } }, "node_modules/@umbraco-ui/uui-form-layout-item": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-form-layout-item/-/uui-form-layout-item-1.12.2.tgz", - "integrity": "sha512-qc4JJhhtM7HsVT1DBtw2xRbayLEWvFDwXROXgmwTUMOVZJ9qGFpSN6EWymm9fr+gBYcbwii6ZKg0ujIeHDILTw==", + "version": "1.13.0-rc.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-form-layout-item/-/uui-form-layout-item-1.13.0-rc.0.tgz", + "integrity": "sha512-cxLzgi8D3rRLu+AiboUzZ9bjdpNzfhDuFZYFOiCA2Dytulc1pV/NFleGgO4tVoZzTvU62ENwSVCCB0xAwztPEQ==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2", - "@umbraco-ui/uui-form-validation-message": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0-rc.0", + "@umbraco-ui/uui-form-validation-message": "1.13.0-rc.0" } }, "node_modules/@umbraco-ui/uui-form-validation-message": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-form-validation-message/-/uui-form-validation-message-1.12.2.tgz", - "integrity": "sha512-MQ0nNQcNpawQUZA+JGYPbGW8Go9b9nj4loK26Op0qvInQpbe9mHbHAhWOdbPTBLoJSYnXpo90/3E9ycU9p9PEQ==", + "version": "1.13.0-rc.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-form-validation-message/-/uui-form-validation-message-1.13.0-rc.0.tgz", + "integrity": "sha512-IvGbFVhGp5YmLHPqJyOSTkk6KM4/g0HWT/3z2RCqkjXVYVsCynMoP7ZCtIzwO7q8e5Y2wrhYPOS9udY4Fkb1Dw==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0-rc.0" } }, "node_modules/@umbraco-ui/uui-icon": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-icon/-/uui-icon-1.12.2.tgz", - "integrity": "sha512-sAz08736Jt1y6pPZSBafNT04w9YCnck46whCZUhx7FX7kiKctJX0Xr9GVZH99YAGxnbXnNx0YsN6PqFfz92FzA==", + "version": "1.13.0-rc.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-icon/-/uui-icon-1.13.0-rc.0.tgz", + "integrity": "sha512-Xl4tkB3DjOT7xmoTeHDE9Vc8HzCVd85yuvJEULjm41/0QzqVEmzl3VqyS/wWrXJDfteHONLEi2DAChjjAPopdQ==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0-rc.0" } }, "node_modules/@umbraco-ui/uui-icon-registry": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-icon-registry/-/uui-icon-registry-1.12.2.tgz", - "integrity": "sha512-CXinq7uwca8QzIMCMBkUNkHoq9KV5ioxJSY4+2b5s7lpS8zK+Zoe+zzt5QL/bOCET6TTGZifpCiZRIiRy1Mffg==", + "version": "1.13.0-rc.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-icon-registry/-/uui-icon-registry-1.13.0-rc.0.tgz", + "integrity": "sha512-jrZHpcUteKA0268BRY+9PJsJD8ZRCmlhPzacII3EKOpRl3Bj0iV/7CeBkMotQNcrWqeyWv0BSuWVMtsK0pdO8A==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2", - "@umbraco-ui/uui-icon": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0-rc.0", + "@umbraco-ui/uui-icon": "1.13.0-rc.0" } }, "node_modules/@umbraco-ui/uui-icon-registry-essential": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-icon-registry-essential/-/uui-icon-registry-essential-1.12.2.tgz", - "integrity": "sha512-s53QmcXVzrLDwpVP3WZW1pekG95kVrjgHDyTo2T3a2J4ovvEEYpZ8/Jmf/3lJVj5CpvQV+I1l/Wx3zFtniT91g==", + "version": "1.13.0-rc.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-icon-registry-essential/-/uui-icon-registry-essential-1.13.0-rc.0.tgz", + "integrity": "sha512-HtftW5POcY99uNpDOscymuv8rUYhCz7JUmX+uWr3aN5vcWeGhze7ZyWQEX7rQqoFhSuuVffJT+KLO4MAXmHgKw==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2", - "@umbraco-ui/uui-icon-registry": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0-rc.0", + "@umbraco-ui/uui-icon-registry": "1.13.0-rc.0" } }, "node_modules/@umbraco-ui/uui-input": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-input/-/uui-input-1.12.2.tgz", - "integrity": "sha512-t/QsptHm9jMH8A0iWBvRZ2s/qeKaO5vp1Zf5oBG9RtgZoS7cNowdMQPVp6mXzc1gICc217lNFsxt+MUGVCud2w==", + "version": "1.13.0-rc.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-input/-/uui-input-1.13.0-rc.0.tgz", + "integrity": "sha512-yi11FOL3tD5KPLg2ffB1CkTaaUIXA5++OybIUKIPEg7jF/0RylSnWid+L5DsF2FXXd/3KS2p8twYXZVPYHZ3aA==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0-rc.0" } }, "node_modules/@umbraco-ui/uui-input-file": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-input-file/-/uui-input-file-1.12.2.tgz", - "integrity": "sha512-X/AeocW+1XLroIqsuxB4OBTmFy1n7ZzfxNrtwEsaqM1rbrA3RGY2EIjnt311eoxk9DvFWeG50/gICV85sWWNmQ==", + "version": "1.13.0-rc.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-input-file/-/uui-input-file-1.13.0-rc.0.tgz", + "integrity": "sha512-ae+tSDS52NwwwDl5K3ePozIYzWNZ9FZjqBGwusJWkWj4TtbZGOuyBZD/7JN/ZEcbK3m4LZY3ujX3BnenCko9Ow==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-action-bar": "1.12.2", - "@umbraco-ui/uui-base": "1.12.2", - "@umbraco-ui/uui-button": "1.12.2", - "@umbraco-ui/uui-file-dropzone": "1.12.2", - "@umbraco-ui/uui-icon": "1.12.2", - "@umbraco-ui/uui-icon-registry-essential": "1.12.2" + "@umbraco-ui/uui-action-bar": "1.13.0-rc.0", + "@umbraco-ui/uui-base": "1.13.0-rc.0", + "@umbraco-ui/uui-button": "1.13.0-rc.0", + "@umbraco-ui/uui-file-dropzone": "1.13.0-rc.0", + "@umbraco-ui/uui-icon": "1.13.0-rc.0", + "@umbraco-ui/uui-icon-registry-essential": "1.13.0-rc.0" } }, "node_modules/@umbraco-ui/uui-input-lock": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-input-lock/-/uui-input-lock-1.12.2.tgz", - "integrity": "sha512-EAjzK0xZbjEEyIqHjMdDPmBQMSay/vbYj65YHb8aJBtYyL17qIqVRMEB9D/tV7cGBp5FbpkpZtb5qWmNVFQtcg==", + "version": "1.13.0-rc.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-input-lock/-/uui-input-lock-1.13.0-rc.0.tgz", + "integrity": "sha512-irNjmrVgWH6LEWoMif8Ck6J2faSZcaoEUQDa4BWjt560lUw7SaTmcp295wddRvWoQ/DmW1KS/H+p7ciihEWGBw==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2", - "@umbraco-ui/uui-button": "1.12.2", - "@umbraco-ui/uui-icon": "1.12.2", - "@umbraco-ui/uui-input": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0-rc.0", + "@umbraco-ui/uui-button": "1.13.0-rc.0", + "@umbraco-ui/uui-icon": "1.13.0-rc.0", + "@umbraco-ui/uui-input": "1.13.0-rc.0" } }, "node_modules/@umbraco-ui/uui-input-password": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-input-password/-/uui-input-password-1.12.2.tgz", - "integrity": "sha512-CYNHiaDmaBDXUYE6XFpO3lpmClwjC6aCgtlYFe8SqFlcyU1KABal36PopxpnIMuKrmMv3LFHw1Jpg5dnjk/hNA==", + "version": "1.13.0-rc.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-input-password/-/uui-input-password-1.13.0-rc.0.tgz", + "integrity": "sha512-WZ80Xa2veYekHXJfdAHhYcBpjxz5zFh8N4siuOWpj0G50oKjHOgmwC/FInmtVZcVUVUTsKT7U4qbkTl1lKgF7Q==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2", - "@umbraco-ui/uui-icon-registry-essential": "1.12.2", - "@umbraco-ui/uui-input": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0-rc.0", + "@umbraco-ui/uui-icon-registry-essential": "1.13.0-rc.0", + "@umbraco-ui/uui-input": "1.13.0-rc.0" } }, "node_modules/@umbraco-ui/uui-keyboard-shortcut": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-keyboard-shortcut/-/uui-keyboard-shortcut-1.12.2.tgz", - "integrity": "sha512-X4ZpIP6AQbx5d3zLVVGqHKIDBli4HwkOsTnepHYFPTykTTiCVBxRiVQ5TRgAM4GjeEaUe/mOyPOCYkVBJ0bKmA==", + "version": "1.13.0-rc.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-keyboard-shortcut/-/uui-keyboard-shortcut-1.13.0-rc.0.tgz", + "integrity": "sha512-8ak58f25XgpJEuRqcWxR8jRe2EPIsse5nSjPMVO1JMx6EVQd788/bnRan8bTYYe3whs4DbVdSxH3cx3cf4jUXQ==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0-rc.0" } }, "node_modules/@umbraco-ui/uui-label": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-label/-/uui-label-1.12.2.tgz", - "integrity": "sha512-D4j2XBwtYq2tK/pP+QJuLSxg5NtD+jGEy5DO2qhoRm2VPzGjCWw3irdykVoTIgMRjJiWOQMvE8tpgqPBsBygHw==", + "version": "1.13.0-rc.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-label/-/uui-label-1.13.0-rc.0.tgz", + "integrity": "sha512-hBQD3VSKBwKYCfs65y40/vN2EoexdW2KeePcOcKybj9B1suFGirzutpA1yGZArTTFYiTP5VPPHoinUldJSvXQg==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0-rc.0" } }, "node_modules/@umbraco-ui/uui-loader": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-loader/-/uui-loader-1.12.2.tgz", - "integrity": "sha512-vbAds+57/wFelt+F4YdCdZ9dyR9DjBtEEPhcJDbd5yLwbgKnl+ITL6pDtu2kT45cVMacaxxZAdP5SzcwVSnR7g==", + "version": "1.13.0-rc.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-loader/-/uui-loader-1.13.0-rc.0.tgz", + "integrity": "sha512-sT+l7SBoqi2Omx9EwRG5t72pnTcxxritI+9ByiY6B5fFO9u7wbkFE0e+kannoxCRTl8OWeECXOyJv+OHKUNwbg==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0-rc.0" } }, "node_modules/@umbraco-ui/uui-loader-bar": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-loader-bar/-/uui-loader-bar-1.12.2.tgz", - "integrity": "sha512-nC678xqAJFH8vKqhewfFi1CEZ8dR5r/s88REILZOwQM8S0c2z9J4bxesmjpr2ZIQ4KQ2l7BCzBdWbyqs+GUHUA==", + "version": "1.13.0-rc.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-loader-bar/-/uui-loader-bar-1.13.0-rc.0.tgz", + "integrity": "sha512-eur3QjsAl0efG3pXS7IccG/cR40u4vEbmO9Uy4TnntT+1qAAzOxij42V6Ul6ue1esDV0Shfnovej14KkdgJJeg==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0-rc.0" } }, "node_modules/@umbraco-ui/uui-loader-circle": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-loader-circle/-/uui-loader-circle-1.12.2.tgz", - "integrity": "sha512-CmjdLDdUM1pRp3dE+WKVEc9dTIQlvYtPtJIjCyNwP403YcKvreGMW6wKMxV/+69IEPjRtTjyaKyprNGnRVRpwg==", + "version": "1.13.0-rc.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-loader-circle/-/uui-loader-circle-1.13.0-rc.0.tgz", + "integrity": "sha512-bMwbApvhmeX1OUIg28cANqTqeAgYg72IMwE2duKpyQo3X037OGV+ShWKqm0ur4UAaqm4dv9wljMhTCNh2mHiNQ==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0-rc.0" } }, "node_modules/@umbraco-ui/uui-menu-item": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-menu-item/-/uui-menu-item-1.12.2.tgz", - "integrity": "sha512-CvrkPWvfRLGSWFNDq+SCLKUm08DjWzw/nYtGLSmQL9QsXa/SMJMtmmcw2H+OYzk4d/9ME+r0GRralZgDlx08iA==", + "version": "1.13.0-rc.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-menu-item/-/uui-menu-item-1.13.0-rc.0.tgz", + "integrity": "sha512-1Tt8GeCY5maCt8aAgr7qKvoqWL1TvjdpctMlCh3scAcgN6nbBPvoI8oJv2oqoVDjETnsxEPFB5quFqLNkgkaMw==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2", - "@umbraco-ui/uui-loader-bar": "1.12.2", - "@umbraco-ui/uui-symbol-expand": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0-rc.0", + "@umbraco-ui/uui-loader-bar": "1.13.0-rc.0", + "@umbraco-ui/uui-symbol-expand": "1.13.0-rc.0" } }, "node_modules/@umbraco-ui/uui-modal": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-modal/-/uui-modal-1.12.2.tgz", - "integrity": "sha512-0ZJuMwdpIFDD+vi59gakhL4jsEb+/f/sMIH4yE/np8ccbZNnGSIT0RJPe94lv6b2wPKrjVIQ1VGGrqzY2znh2A==", + "version": "1.13.0-rc.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-modal/-/uui-modal-1.13.0-rc.0.tgz", + "integrity": "sha512-ToZhlEI95RiLOSylikoOQg8eXyjWAEgfkva//eQ32D4m66pz9O19yvL8/s8hP8i9IDSdE9t1eGNveYGYv0UFvQ==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0-rc.0" } }, "node_modules/@umbraco-ui/uui-pagination": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-pagination/-/uui-pagination-1.12.2.tgz", - "integrity": "sha512-TvP0GKewUZndpO7rHlPqbsw5dPqmKBJXs33berhn/crIE2pGnPVEBey3NYLIHBd5CZI5ufn+gGn4NPNVGF+Q9A==", + "version": "1.13.0-rc.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-pagination/-/uui-pagination-1.13.0-rc.0.tgz", + "integrity": "sha512-qLc04X62+cI0djoKUKm35jlniX1omgMLcK73oa9M81uxXhaDvAphXI8gRWNNViVPd1p1KGuQh0ebYVw9YYz2Xg==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2", - "@umbraco-ui/uui-button": "1.12.2", - "@umbraco-ui/uui-button-group": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0-rc.0", + "@umbraco-ui/uui-button": "1.13.0-rc.0", + "@umbraco-ui/uui-button-group": "1.13.0-rc.0" } }, "node_modules/@umbraco-ui/uui-popover": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-popover/-/uui-popover-1.12.2.tgz", - "integrity": "sha512-gvSUe7wox0VY/wEm8LLUV//aLVwz7twswWQd9QniR6MdahvwhjWhQ90hTVpir3VAj5GFBaTfSitqeFBElyT1og==", + "version": "1.13.0-rc.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-popover/-/uui-popover-1.13.0-rc.0.tgz", + "integrity": "sha512-N8D1CwQDaGNBdzojUdVXzeRfzPN+p9BllWDfHcdMQyQ2z866cubWpcaBX0RaeqT+hbYpclVfvqs05k+f7i3JZA==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0-rc.0" } }, "node_modules/@umbraco-ui/uui-popover-container": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-popover-container/-/uui-popover-container-1.12.2.tgz", - "integrity": "sha512-2z//P49B1zyoN/tWdVZp6Q+8qRnbbtGb4CBveXZeuuirzNxhMOA/E77Y0aJmzjn8yTRoooMGmYzRYd+4zJGNJQ==", + "version": "1.13.0-rc.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-popover-container/-/uui-popover-container-1.13.0-rc.0.tgz", + "integrity": "sha512-+LtKf7uvfQZncH/WT9pa1fVezLvOJv3iUBqZqOTSVzQNPIfnlagclt9QXoHDN4h3TtyZ+DeDlt9hxsuRZYDWuw==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0-rc.0" } }, "node_modules/@umbraco-ui/uui-progress-bar": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-progress-bar/-/uui-progress-bar-1.12.2.tgz", - "integrity": "sha512-PW5TKeg58Lv3WfX6Sp/EPWCsl9oYqQovvl/7y0pxy7xFnSYma5tFQ+XX0mD1rKw7ed3Unlek/Ma9u79Z9GVhDQ==", + "version": "1.13.0-rc.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-progress-bar/-/uui-progress-bar-1.13.0-rc.0.tgz", + "integrity": "sha512-I4o8xW1nTBeyzYWzIHd0ijq3UVfvOiFF7Cm0QDHycqg80iD6M3c+0N+MTOBN2Ae3CDeqjdOVY7gRnwcFXyxQsw==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0-rc.0" } }, "node_modules/@umbraco-ui/uui-radio": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-radio/-/uui-radio-1.12.2.tgz", - "integrity": "sha512-KfXA6+YtueMsxQTjzjp8gVgGJAk17BW9d4Da4h7kYhZGekfWK996ohEgGWF7vj/Q4Ai229OuX7zNJdufCGZIfw==", + "version": "1.13.0-rc.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-radio/-/uui-radio-1.13.0-rc.0.tgz", + "integrity": "sha512-PYka+JON/UCYTIDjOrEl9AOQKb8rlNmvBMtejVMCKo5nv3YbbbOaWKBzwxw3QE6THiWuUP9Q0oFG1ZQKSD+tgA==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0-rc.0" } }, "node_modules/@umbraco-ui/uui-range-slider": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-range-slider/-/uui-range-slider-1.12.2.tgz", - "integrity": "sha512-m4ATwJYdasF4jfLLHxfFw+2n0uQmZdOha4vxzHbTreyO/gnwn8hLfICA1h9zjoZIqUGMtQ9KlhIaUezvgMpGFw==", + "version": "1.13.0-rc.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-range-slider/-/uui-range-slider-1.13.0-rc.0.tgz", + "integrity": "sha512-C4w//f61mlcs46aQoEl5xkwRhCW1D8C3cQekHXXVW/XbxxBA+qE7tDpQwWBCJnt5dKcxiH0sbBhYD7BCPfDbGA==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0-rc.0" } }, "node_modules/@umbraco-ui/uui-ref": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref/-/uui-ref-1.12.2.tgz", - "integrity": "sha512-uwQmaiuwphD1ereZLBhcUDMUaUosO0sV6NrBOh9KLWhkmeqYjuFFG2+CRxdhQrKb1ltZfLzAmzYfGp6AoFkvmw==", + "version": "1.13.0-rc.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref/-/uui-ref-1.13.0-rc.0.tgz", + "integrity": "sha512-oYsfeslRHm72JWdG5xUr5q3w5joU4PoAYBnlQB+AN1k3jA9L0w89SelzAcKOl+Qp3DQHwL7WEFDzL1O3tZvJyg==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0-rc.0" } }, "node_modules/@umbraco-ui/uui-ref-list": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-list/-/uui-ref-list-1.12.2.tgz", - "integrity": "sha512-b7reEiwfGy17Ns3qFQoO0TnngxAUclhj0jR7gLIk7dHNJZw45r37crPMkVs2CnRj657nn4DmghjQgCLDSCre9w==", + "version": "1.13.0-rc.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-list/-/uui-ref-list-1.13.0-rc.0.tgz", + "integrity": "sha512-yxlN+B8+xEbECBosiPzNSk5C8Xmz88RD577pcXlpwJ1jRsV24tvWhSPLXccf2qKyv6FnSAXzJxKWg0jb0lvItw==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0-rc.0" } }, "node_modules/@umbraco-ui/uui-ref-node": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node/-/uui-ref-node-1.12.2.tgz", - "integrity": "sha512-RFma47ixyYNdcMwel1+dte5fGnULczWZpzh1CvAiI9JNKzy9ItUFi70UiFKMrkOY0gT+910xgeWhk4jPTJJgpQ==", + "version": "1.13.0-rc.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node/-/uui-ref-node-1.13.0-rc.0.tgz", + "integrity": "sha512-rMZKKhhSkqbJtjfBa+ANlkiWXovTzjtIx9UIq78JoIzzh6WTMrHEcL3Gyx3Y03+iYiVPCZbCbOnhCnV0qo3k9g==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2", - "@umbraco-ui/uui-icon": "1.12.2", - "@umbraco-ui/uui-ref": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0-rc.0", + "@umbraco-ui/uui-icon": "1.13.0-rc.0", + "@umbraco-ui/uui-ref": "1.13.0-rc.0" } }, "node_modules/@umbraco-ui/uui-ref-node-data-type": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-data-type/-/uui-ref-node-data-type-1.12.2.tgz", - "integrity": "sha512-s8eviANQTHaNXSVa4U61wJcPCAwzUj6YrIvw7T3Ioe4HgIQvTotIWaCkek+p4ttl3irnnBsRXfGdW+yWuaEnEg==", + "version": "1.13.0-rc.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-data-type/-/uui-ref-node-data-type-1.13.0-rc.0.tgz", + "integrity": "sha512-UgLkT2iUcHcJMnNqmnCmnIP/ohcLcNRQzthZB/W46v1kvX98I6ZF15LI1Y7CgKFoOd9StEjxG0GUdrrfpBslPQ==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2", - "@umbraco-ui/uui-ref-node": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0-rc.0", + "@umbraco-ui/uui-ref-node": "1.13.0-rc.0" } }, "node_modules/@umbraco-ui/uui-ref-node-document-type": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-document-type/-/uui-ref-node-document-type-1.12.2.tgz", - "integrity": "sha512-Dg+SAAcMSqr0EvX6IY2jjGk9I8bbgo1Pe6L5c9g0CBPmQ8H+0qOKDdSojWzn/qohtfdAIvN+21Q0AvCovVA9rA==", + "version": "1.13.0-rc.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-document-type/-/uui-ref-node-document-type-1.13.0-rc.0.tgz", + "integrity": "sha512-h+ygKaBUhxiJSv9CHrw4X0gaPxsMFx79sJdxs8l+X17E9+5TEARPg+aUn75V4FatZZcsPYhGVGCXmFyDZZdGfg==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2", - "@umbraco-ui/uui-ref-node": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0-rc.0", + "@umbraco-ui/uui-ref-node": "1.13.0-rc.0" } }, "node_modules/@umbraco-ui/uui-ref-node-form": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-form/-/uui-ref-node-form-1.12.2.tgz", - "integrity": "sha512-jnPNmLK8LvZenH2MY9Ea8R+4JkuDNMoBfUFVnhaLg+dHp7tsrg9opIONDNOIJJTYHryHdZ+/ksvQGW6ZWlACgQ==", + "version": "1.13.0-rc.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-form/-/uui-ref-node-form-1.13.0-rc.0.tgz", + "integrity": "sha512-fDDu4S6y+rOrcOMOcspk1n+fQoSLAotcdW/ymofSxIkMb8JWM5lPlD1+i6+jJRILlIu4v0w0lTpBZpivHJQ/4w==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2", - "@umbraco-ui/uui-ref-node": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0-rc.0", + "@umbraco-ui/uui-ref-node": "1.13.0-rc.0" } }, "node_modules/@umbraco-ui/uui-ref-node-member": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-member/-/uui-ref-node-member-1.12.2.tgz", - "integrity": "sha512-ft0SRlDZ49eRbV3Xk7JtDfR5UraULoeTfYe/MHZkmAzhrDKeTtnd9oVYUQ27qsYs6EVneQ8byydwXrmSMloc8A==", + "version": "1.13.0-rc.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-member/-/uui-ref-node-member-1.13.0-rc.0.tgz", + "integrity": "sha512-USZDfYzxgH7j/VH7UWqxTH7BWH1qqmStcFV7PKSr6/vfdyv8eF0jj3wCG36bdzzW8MwVJM0cHezlq5FJ4rM7AQ==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2", - "@umbraco-ui/uui-ref-node": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0-rc.0", + "@umbraco-ui/uui-ref-node": "1.13.0-rc.0" } }, "node_modules/@umbraco-ui/uui-ref-node-package": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-package/-/uui-ref-node-package-1.12.2.tgz", - "integrity": "sha512-TX9PCPpeOWpl5vK8o/QjXgEWXOt7z0lQK8wlUHYSz+a3/wcmDZD0J/OXkmpvVyS2lXe6pqR8HJ/+FwcnrOm/9w==", + "version": "1.13.0-rc.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-package/-/uui-ref-node-package-1.13.0-rc.0.tgz", + "integrity": "sha512-bAAaNM3H4d0epJUl+IA6ykhbJaHcJUbCeNdvpWO5BVa7u/UjBofNEsfVrWe82O3IPJfMtjrZzOPX+Wa8jhdfjw==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2", - "@umbraco-ui/uui-ref-node": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0-rc.0", + "@umbraco-ui/uui-ref-node": "1.13.0-rc.0" } }, "node_modules/@umbraco-ui/uui-ref-node-user": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-user/-/uui-ref-node-user-1.12.2.tgz", - "integrity": "sha512-sBMICX3vxJd9WjJPWqVnhUhJL+JMuzGzZVUfHlzIjrdpANZZ6FrhnvYkHXhW83KsrfwLsY5/3CXr22xZSsVajA==", + "version": "1.13.0-rc.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-user/-/uui-ref-node-user-1.13.0-rc.0.tgz", + "integrity": "sha512-4Uu+ZV1cs77m8ZZulbwJtGTJn3jT1EIljw9E8e0xtXrSe+COzxmxdnjXMAX2nvVN+qKePVw3vSp5HC5loJC/Pw==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2", - "@umbraco-ui/uui-ref-node": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0-rc.0", + "@umbraco-ui/uui-ref-node": "1.13.0-rc.0" } }, "node_modules/@umbraco-ui/uui-scroll-container": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-scroll-container/-/uui-scroll-container-1.12.2.tgz", - "integrity": "sha512-MI5lpiUeLg1Scf2xHaFzBADAW8CAwcU2yEKOOfOgONuaP6PiUA80YqtE2hCm5BmoldbOYBufCJlFFi2cyuq7HQ==", + "version": "1.13.0-rc.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-scroll-container/-/uui-scroll-container-1.13.0-rc.0.tgz", + "integrity": "sha512-kJeTD7nLF7lNkkqhKBAdD2c7zgaBiTcEOhXLHWcuMfS1TYtTDgVkGaWMpUgfS+tLaxp+pkzSVjkYLgP60NvJNw==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0-rc.0" } }, "node_modules/@umbraco-ui/uui-select": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-select/-/uui-select-1.12.2.tgz", - "integrity": "sha512-TOGodRtumlh1cgC9iKxsV/jEGH2w7bKBjIhyQ42sJ3DXyLPcXVEUooZYmh/3dOf7R/7eHSsZOxH/sskbQlNS2A==", + "version": "1.13.0-rc.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-select/-/uui-select-1.13.0-rc.0.tgz", + "integrity": "sha512-hl2bQv21z7L4WLNlLsabjXvEP7+jt9C6McvroTbiIX+thnerIBMLEn0/4W25G4d7f2Hn9DSwaBDZ/OoLBVDEqg==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0-rc.0" } }, "node_modules/@umbraco-ui/uui-slider": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-slider/-/uui-slider-1.12.2.tgz", - "integrity": "sha512-Eg0XqIIXwibxq7y4qe0OB9+t7QLetnlBY3i2BSeMPMfarG1NQ6jhWVOv//RKmZ1kqfUh9MCE5tya9T9h68zR1A==", + "version": "1.13.0-rc.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-slider/-/uui-slider-1.13.0-rc.0.tgz", + "integrity": "sha512-QHeGcmlEJ4eCIdijOBvO60LUFJUu08rB1pYgpGqVtHlwU7974c97FJUy/KsKheVEc3fvbeDATATwBk1dGaw0Xw==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0-rc.0" } }, "node_modules/@umbraco-ui/uui-symbol-expand": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-expand/-/uui-symbol-expand-1.12.2.tgz", - "integrity": "sha512-zW/ClcJuPCe7ELYHCyoSMm6sGWVPLDbjz8TlE1qambwmFefqTfv69p3nB0YF7QnB+7LR5ePOV63vjZSYWT9/Aw==", + "version": "1.13.0-rc.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-expand/-/uui-symbol-expand-1.13.0-rc.0.tgz", + "integrity": "sha512-5/pcW59GtZK/u/kJfsoQmBPKM5o8c/cv6tHeGq4Ln2viqP1R8qhamDqhbAgOxdEnrMYq7Pt2hRWBS39Xm0KU9Q==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0-rc.0" } }, "node_modules/@umbraco-ui/uui-symbol-file": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-file/-/uui-symbol-file-1.12.2.tgz", - "integrity": "sha512-+af95C4eZOdOpqJrt8br1pic1P/NPrnyC1Q4sKLaCReuBqBdaWLl502kAXjlkkoJZsv4GsyzmjiSbBkbRIZCFQ==", + "version": "1.13.0-rc.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-file/-/uui-symbol-file-1.13.0-rc.0.tgz", + "integrity": "sha512-h5RZLCFhzmwYhxZjgyE4G9jgKbJ+grCqCS1G9X4bPZHVDMztTS2erW0u6scNkL2IgO5hrUPy7O5sAcfW/o3QgQ==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0-rc.0" } }, "node_modules/@umbraco-ui/uui-symbol-file-dropzone": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-file-dropzone/-/uui-symbol-file-dropzone-1.12.2.tgz", - "integrity": "sha512-8vmHw+nYZdWgeUVNCJhTvJg4iw0zTCxQ6H5tguN1Qepc+XD1NdlRTi8yicnEKSLcq20qzI3KxxwToNLnFKseSQ==", + "version": "1.13.0-rc.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-file-dropzone/-/uui-symbol-file-dropzone-1.13.0-rc.0.tgz", + "integrity": "sha512-ifa0WAoXZoDv7r0UnykBFAS8JjJohG9sE31NYW7AXuxcoVd5I9YN7nWSia3yM1YZM0gHUCxzSRkSTokRGy9T5g==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0-rc.0" } }, "node_modules/@umbraco-ui/uui-symbol-file-thumbnail": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-file-thumbnail/-/uui-symbol-file-thumbnail-1.12.2.tgz", - "integrity": "sha512-tQsQTjgZti4zB327Xd2ql8lz9rj07aVwKfJcV2bClHwyQbRb370KRAS4m6MiaT587+6qVcjRwG3Sya1blpNMfg==", + "version": "1.13.0-rc.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-file-thumbnail/-/uui-symbol-file-thumbnail-1.13.0-rc.0.tgz", + "integrity": "sha512-Tuqbjbk3ryd/EEGhV/nHcKGPN0n+TYp3b69vLC9hcutz9jcU1vci0fEGny4XnbbMqNbjLfelsUeWMFjc4W33yA==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0-rc.0" } }, "node_modules/@umbraco-ui/uui-symbol-folder": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-folder/-/uui-symbol-folder-1.12.2.tgz", - "integrity": "sha512-v3bYEpbomOmt2J+LYuB3HqzzZW+LzK/Ufpvr3Km9Gl4eXjPUnrAzBn3PSdq7w5ZvR3vfEV017coPTSX0wncjKQ==", + "version": "1.13.0-rc.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-folder/-/uui-symbol-folder-1.13.0-rc.0.tgz", + "integrity": "sha512-mtAuHC1AqKWTwGjTWUIWoV8aO5N3kKGpModLzMGnAZEJZXqMj4R+nbs7Zku1lgIgsrjfS0fV9GmEsQlVI4IxXg==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0-rc.0" } }, "node_modules/@umbraco-ui/uui-symbol-lock": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-lock/-/uui-symbol-lock-1.12.2.tgz", - "integrity": "sha512-syW+kTYq7W9coBc7ov1BbDhRTmAMh77GacfQt4XSayHgE/hhO6UvG95uk0POaooQ0UfBW1bDv9r3/wJNZBTfmw==", + "version": "1.13.0-rc.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-lock/-/uui-symbol-lock-1.13.0-rc.0.tgz", + "integrity": "sha512-RIIZJPy3jNZ8VkbFZqmfsszuJ2E2H1UmQAP68yr7UNlGfaat131p61cEmAOCpeb0G2kl1HN9bBjpYByTufT92w==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0-rc.0" } }, "node_modules/@umbraco-ui/uui-symbol-more": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-more/-/uui-symbol-more-1.12.2.tgz", - "integrity": "sha512-lxcw/B6zl3TJ7mZDYgXKvX6D/1gYYLmrLvKV7J5iSTGxDNiLji8NAXu2/rgffKMGIFaLfZicEENSLLX/JF8QGQ==", + "version": "1.13.0-rc.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-more/-/uui-symbol-more-1.13.0-rc.0.tgz", + "integrity": "sha512-JVeUyqH6DmBiYGJWlcqNDlHxI1O5QySxHjNAZYAdNMrGcIvXOGjBJAxOJUb6vYSDfkvTW+hScjpB7ffSc+FkhA==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0-rc.0" } }, "node_modules/@umbraco-ui/uui-symbol-sort": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-sort/-/uui-symbol-sort-1.12.2.tgz", - "integrity": "sha512-iDLs6Ph9BGrLRifU6oGZr7UCOsoOKk5NMxnP7eP/sy0geq30kHlI/mcBu6XUrtYiFsy3+l8b8gSFdLxEHQrcgQ==", + "version": "1.13.0-rc.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-sort/-/uui-symbol-sort-1.13.0-rc.0.tgz", + "integrity": "sha512-F9PgfmHhOo44DMSWMWf4EmfmPw6TSTs8WvzYWOAwLFlNWNGq/yKL3Usp0Hrn+RIWiRAHxnfaBhr4VKQiVg6eTg==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0-rc.0" } }, "node_modules/@umbraco-ui/uui-table": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-table/-/uui-table-1.12.2.tgz", - "integrity": "sha512-aHSArtedBiQBcz++eXomQvTys4Q0P7/SNEUcsG/CbPS7uDWXQZJK/KajtI7rMjU/d63dtavIXq9v0LatKTM/sw==", + "version": "1.13.0-rc.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-table/-/uui-table-1.13.0-rc.0.tgz", + "integrity": "sha512-7LE5CpksJ9Pj5EVosS3G4YuILX2SP/lCQejgmHb8E7ATVuO+TNOqrQLBOKfaQVvd+hOUhhGAzZMaPSb2Pqpcgg==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0-rc.0" } }, "node_modules/@umbraco-ui/uui-tabs": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-tabs/-/uui-tabs-1.12.2.tgz", - "integrity": "sha512-20ZmwGiLFtFA5a1CkBo713Ua508d0VwaCWnaKkhoE8Kl/ttlWhlKg+PSB26wkcwB0QonWrH1clMRalwKqRhjvg==", + "version": "1.13.0-rc.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-tabs/-/uui-tabs-1.13.0-rc.0.tgz", + "integrity": "sha512-7aC8nYNf6/A0LtfC0bpzIECOS5Mf/Yt9htiNnw0wUNYz8EoLBFI1DrL0CaWdJXsfT2mLXAILrJn/lIRjZ0Ywgg==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2", - "@umbraco-ui/uui-button": "1.12.2", - "@umbraco-ui/uui-popover-container": "1.12.2", - "@umbraco-ui/uui-symbol-more": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0-rc.0", + "@umbraco-ui/uui-button": "1.13.0-rc.0", + "@umbraco-ui/uui-popover-container": "1.13.0-rc.0", + "@umbraco-ui/uui-symbol-more": "1.13.0-rc.0" } }, "node_modules/@umbraco-ui/uui-tag": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-tag/-/uui-tag-1.12.2.tgz", - "integrity": "sha512-15omQCZmBeW3U6E0kCoFQs3ckUsNqWOCjslGfDMe+0x0a+r5hntam05OrUlF523plD/SG6utXGI/tRYdTidh1g==", + "version": "1.13.0-rc.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-tag/-/uui-tag-1.13.0-rc.0.tgz", + "integrity": "sha512-SMFP3/rVvM08b0RUI1RC9Ng9qQ5ZuccOBpSQrahKk+OLJY35RNupVj6vXdQaiSzOvRegCSNODwPu3lIo2WJKEw==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0-rc.0" } }, "node_modules/@umbraco-ui/uui-textarea": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-textarea/-/uui-textarea-1.12.2.tgz", - "integrity": "sha512-dlT0fZ0zjdj4BouWhjqA4UBBj4YRFGxWZkMhbP/+g2lAnsl11GN2yMzOvfv7R6Zo3pmV6/qavtEk+XRKBaAihg==", + "version": "1.13.0-rc.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-textarea/-/uui-textarea-1.13.0-rc.0.tgz", + "integrity": "sha512-A6jZlqZQhzRDEQFcd0EK5Q+U9ST0/MNH7BBtfRQWtRyBDkEXwLgE0nvj2luDwKlLipgKjd0cdvdTZJORqaGiyg==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0-rc.0" } }, "node_modules/@umbraco-ui/uui-toast-notification": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-toast-notification/-/uui-toast-notification-1.12.2.tgz", - "integrity": "sha512-gtVAoGPd4G0VWVdSyyhaDQupzuLLfFzuaVTVai0970hLAZAzcbodG3W382iPhPIbHwQX7T8LMV02ScPfGuhjbA==", + "version": "1.13.0-rc.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-toast-notification/-/uui-toast-notification-1.13.0-rc.0.tgz", + "integrity": "sha512-9BZhybMvZWsE7buTZX/C3isXpLN47gI6cnaKfyAoqCaOe7iF/aZF7YGapT5UD4Z6O2W+HhxsKkp5O+AtasRUOw==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2", - "@umbraco-ui/uui-button": "1.12.2", - "@umbraco-ui/uui-css": "1.12.1", - "@umbraco-ui/uui-icon": "1.12.2", - "@umbraco-ui/uui-icon-registry-essential": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0-rc.0", + "@umbraco-ui/uui-button": "1.13.0-rc.0", + "@umbraco-ui/uui-css": "1.13.0-rc.0", + "@umbraco-ui/uui-icon": "1.13.0-rc.0", + "@umbraco-ui/uui-icon-registry-essential": "1.13.0-rc.0" } }, "node_modules/@umbraco-ui/uui-toast-notification-container": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-toast-notification-container/-/uui-toast-notification-container-1.12.2.tgz", - "integrity": "sha512-Zu70rQzYV+QegV2kwNmpUDGU75z6u9B3ujFzVN2u+oi1y0kkR6wgXIczExQ4PeqEBZM252ZWbCIDQ66gX1+thw==", + "version": "1.13.0-rc.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-toast-notification-container/-/uui-toast-notification-container-1.13.0-rc.0.tgz", + "integrity": "sha512-481Edc+gzJ3kjSQmi5mIkdDkh95owzF6qwskbDu3UX+yFmhH07yGIpnQeXEHHiVjHrHE/jT9mF3s8YiQpLPGkw==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2", - "@umbraco-ui/uui-toast-notification": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0-rc.0", + "@umbraco-ui/uui-toast-notification": "1.13.0-rc.0" } }, "node_modules/@umbraco-ui/uui-toast-notification-layout": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-toast-notification-layout/-/uui-toast-notification-layout-1.12.2.tgz", - "integrity": "sha512-b0kgRwc744RpBjJW5URKRwGXzbGWU12OuFqIXq6BSl8LuFci9uh62V2J7Jj5xnx6v1jqZi/RRRKRwiqQOa3AWw==", + "version": "1.13.0-rc.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-toast-notification-layout/-/uui-toast-notification-layout-1.13.0-rc.0.tgz", + "integrity": "sha512-ZdNPpcMWONqrN0yFMRfKpJqQm+GdKUw65slQiwcM0NyZAUA/oNHTWPRZonImwOXEfB9qTIgSYIQ/TpAhA7Sv+Q==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2", - "@umbraco-ui/uui-css": "1.12.1" + "@umbraco-ui/uui-base": "1.13.0-rc.0", + "@umbraco-ui/uui-css": "1.13.0-rc.0" } }, "node_modules/@umbraco-ui/uui-toggle": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-toggle/-/uui-toggle-1.12.2.tgz", - "integrity": "sha512-hQCQJUEYjNL/2a/vldTlkFhTLiAF+P1UKxhPDqxCQlO/GsOihefcRhchOPmx4ptvjadvSc7J/MJPhAYC2RB0gw==", + "version": "1.13.0-rc.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-toggle/-/uui-toggle-1.13.0-rc.0.tgz", + "integrity": "sha512-zWQMKzR5QytJ2C3doqfd7MEKZnlLW3vRm4y8GscvpU2yc9qlVzDWDGCLfIw0m7NJVBirZbISy5Qv+b4o6INwgA==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2", - "@umbraco-ui/uui-boolean-input": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0-rc.0", + "@umbraco-ui/uui-boolean-input": "1.13.0-rc.0" } }, "node_modules/@umbraco-ui/uui-visually-hidden": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-visually-hidden/-/uui-visually-hidden-1.12.2.tgz", - "integrity": "sha512-3VC4UUcalOl93pkwVWxbSxnIEyN9e5Soy+V3HKQDifWZ536NjBRvMzw+jib5BFLBzrfmRjX68lxNbE2t/EDydA==", + "version": "1.13.0-rc.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-visually-hidden/-/uui-visually-hidden-1.13.0-rc.0.tgz", + "integrity": "sha512-GdfwsMy8TdefwTBlbCLQlWewVDArFpLUgUYkX73e8bP2rVVUrR5DkN+bW9jnlC8Xr2bJxcyOiIFUr7hidYZImA==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0-rc.0" } }, "node_modules/@vitest/expect": { diff --git a/src/Umbraco.Web.UI.Client/package.json b/src/Umbraco.Web.UI.Client/package.json index 369297a9bb02..530f88ac2764 100644 --- a/src/Umbraco.Web.UI.Client/package.json +++ b/src/Umbraco.Web.UI.Client/package.json @@ -1,7 +1,7 @@ { "name": "@umbraco-cms/backoffice", "license": "MIT", - "version": "15.3.0-rc", + "version": "16.0.0-rc", "type": "module", "exports": { ".": null, @@ -13,7 +13,6 @@ "./embedded-media": "./dist-cms/packages/embedded-media/index.js", "./extension-api": "./dist-cms/libs/extension-api/index.js", "./extension-types": "./dist-cms/packages/extension-types/index.d.ts", - "./formatting-api": "./dist-cms/libs/formatting-api/index.js", "./localization-api": "./dist-cms/libs/localization-api/index.js", "./observable-api": "./dist-cms/libs/observable-api/index.js", "./action": "./dist-cms/packages/core/action/index.js", @@ -173,8 +172,8 @@ "generate:icons": "node ./devops/icons/index.js", "generate:overrides": "node ./devops/tsc/index.js", "generate:jsonschema:imports": "node ./devops/json-schema-generator/index.js", - "generate:jsonschema:dist": "typescript-json-schema --required --include \"./src/json-schema/umbraco-package-schema.ts\" --out dist-cms/umbraco-package-schema.json tsconfig.json UmbracoPackage", - "generate:jsonschema": "typescript-json-schema --required --include \"./src/json-schema/umbraco-package-schema.ts\"", + "generate:jsonschema:dist": "npm run generate:jsonschema -- --out dist-cms/umbraco-package-schema.json tsconfig.json UmbracoPackage", + "generate:jsonschema": "typescript-json-schema --skipLibCheck --ignoreErrors --excludePrivate --required --include \"./src/json-schema/umbraco-package-schema.ts\"", "generate:check-const-test": "node ./devops/generate-check-const-test/index.js", "lint:errors": "npm run lint -- --quiet", "lint:fix": "npm run lint -- --fix", @@ -194,7 +193,7 @@ "generate:tsconfig": "node ./devops/tsconfig/index.js", "generate:manifest": "node ./devops/build/create-umbraco-package.js", "package:validate": "node ./devops/package/validate-exports.js", - "generate:ui-api-docs": "typedoc --options typedoc.config.js" + "generate:ui-api-docs": "npm run generate:check-const-test && typedoc --options typedoc.config.js" }, "engines": { "node": ">=22", @@ -216,8 +215,8 @@ "@tiptap/pm": "2.11.5", "@tiptap/starter-kit": "2.11.5", "@types/diff": "^7.0.1", - "@umbraco-ui/uui": "^1.12.2", - "@umbraco-ui/uui-css": "^1.12.1", + "@umbraco-ui/uui": "^1.13.0-rc.1", + "@umbraco-ui/uui-css": "^1.13.0-rc.0", "diff": "^7.0.0", "dompurify": "^3.2.4", "element-internals-polyfill": "^1.3.13", diff --git a/src/Umbraco.Web.UI.Client/public-assets/App_Plugins/custom-bundle-package/index.js b/src/Umbraco.Web.UI.Client/public-assets/App_Plugins/custom-bundle-package/index.js index 79a68d028c34..ba0e344d9b3f 100644 --- a/src/Umbraco.Web.UI.Client/public-assets/App_Plugins/custom-bundle-package/index.js +++ b/src/Umbraco.Web.UI.Client/public-assets/App_Plugins/custom-bundle-package/index.js @@ -1,9 +1,9 @@ -export const manifests: Array<UmbExtensionManifest> = [ +export const manifests = [ { type: 'section', alias: 'MyBundle.Section.Custom', name: 'Custom Section', - js: '/App_Plugins/section.js', + element: () => import('./section.js'), weight: 1, meta: { label: 'My Bundle Section', diff --git a/src/Umbraco.Web.UI.Client/src/apps/preview/apps/preview-culture.element.ts b/src/Umbraco.Web.UI.Client/src/apps/preview/apps/preview-culture.element.ts index 827bcfe9257e..81d425e2a088 100644 --- a/src/Umbraco.Web.UI.Client/src/apps/preview/apps/preview-culture.element.ts +++ b/src/Umbraco.Web.UI.Client/src/apps/preview/apps/preview-culture.element.ts @@ -4,9 +4,7 @@ import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import { UmbLanguageCollectionRepository } from '@umbraco-cms/backoffice/language'; import type { UmbLanguageDetailModel } from '@umbraco-cms/backoffice/language'; -const elementName = 'umb-preview-culture'; - -@customElement(elementName) +@customElement('umb-preview-culture') export class UmbPreviewCultureElement extends UmbLitElement { #languageRepository = new UmbLanguageCollectionRepository(this); @@ -98,6 +96,6 @@ export { UmbPreviewCultureElement as element }; declare global { interface HTMLElementTagNameMap { - [elementName]: UmbPreviewCultureElement; + 'umb-preview-culture': UmbPreviewCultureElement; } } diff --git a/src/Umbraco.Web.UI.Client/src/apps/preview/apps/preview-device.element.ts b/src/Umbraco.Web.UI.Client/src/apps/preview/apps/preview-device.element.ts index 74e08fff02cb..92cb0412b198 100644 --- a/src/Umbraco.Web.UI.Client/src/apps/preview/apps/preview-device.element.ts +++ b/src/Umbraco.Web.UI.Client/src/apps/preview/apps/preview-device.element.ts @@ -10,9 +10,7 @@ export interface UmbPreviewDevice { dimensions: { height: string; width: string }; } -const elementName = 'umb-preview-device'; - -@customElement(elementName) +@customElement('umb-preview-device') export class UmbPreviewDeviceElement extends UmbLitElement { #devices: Array<UmbPreviewDevice> = [ { @@ -144,6 +142,6 @@ export { UmbPreviewDeviceElement as element }; declare global { interface HTMLElementTagNameMap { - [elementName]: UmbPreviewDeviceElement; + 'umb-preview-device': UmbPreviewDeviceElement; } } diff --git a/src/Umbraco.Web.UI.Client/src/apps/preview/apps/preview-exit.element.ts b/src/Umbraco.Web.UI.Client/src/apps/preview/apps/preview-exit.element.ts index 849a1f073f36..e751e42ad3d8 100644 --- a/src/Umbraco.Web.UI.Client/src/apps/preview/apps/preview-exit.element.ts +++ b/src/Umbraco.Web.UI.Client/src/apps/preview/apps/preview-exit.element.ts @@ -2,8 +2,7 @@ import { UMB_PREVIEW_CONTEXT } from '../preview.context.js'; import { css, customElement, html } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; -const elementName = 'umb-preview-exit'; -@customElement(elementName) +@customElement('umb-preview-exit') export class UmbPreviewExitElement extends UmbLitElement { async #onClick() { const previewContext = await this.getContext(UMB_PREVIEW_CONTEXT); @@ -44,6 +43,6 @@ export { UmbPreviewExitElement as element }; declare global { interface HTMLElementTagNameMap { - [elementName]: UmbPreviewExitElement; + 'umb-preview-exit': UmbPreviewExitElement; } } diff --git a/src/Umbraco.Web.UI.Client/src/apps/preview/apps/preview-open-website.element.ts b/src/Umbraco.Web.UI.Client/src/apps/preview/apps/preview-open-website.element.ts index c0bbfe581c58..95dd68f91736 100644 --- a/src/Umbraco.Web.UI.Client/src/apps/preview/apps/preview-open-website.element.ts +++ b/src/Umbraco.Web.UI.Client/src/apps/preview/apps/preview-open-website.element.ts @@ -2,8 +2,7 @@ import { UMB_PREVIEW_CONTEXT } from '../preview.context.js'; import { css, customElement, html } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; -const elementName = 'umb-preview-open-website'; -@customElement(elementName) +@customElement('umb-preview-open-website') export class UmbPreviewOpenWebsiteElement extends UmbLitElement { async #onClick() { const previewContext = await this.getContext(UMB_PREVIEW_CONTEXT); @@ -44,6 +43,6 @@ export { UmbPreviewOpenWebsiteElement as element }; declare global { interface HTMLElementTagNameMap { - [elementName]: UmbPreviewOpenWebsiteElement; + 'umb-preview-open-website': UmbPreviewOpenWebsiteElement; } } diff --git a/src/Umbraco.Web.UI.Client/src/apps/preview/preview.element.ts b/src/Umbraco.Web.UI.Client/src/apps/preview/preview.element.ts index 2a426d4bae91..3a49b330737d 100644 --- a/src/Umbraco.Web.UI.Client/src/apps/preview/preview.element.ts +++ b/src/Umbraco.Web.UI.Client/src/apps/preview/preview.element.ts @@ -4,12 +4,10 @@ import { css, customElement, html, nothing, state, when } from '@umbraco-cms/bac import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; -const elementName = 'umb-preview'; - /** * @element umb-preview */ -@customElement(elementName) +@customElement('umb-preview') export class UmbPreviewElement extends UmbLitElement { #context = new UmbPreviewContext(this); @@ -199,6 +197,6 @@ export default UmbPreviewElement; declare global { interface HTMLElementTagNameMap { - [elementName]: UmbPreviewElement; + 'umb-preview': UmbPreviewElement; } } diff --git a/src/Umbraco.Web.UI.Client/src/assets/lang/da-dk.ts b/src/Umbraco.Web.UI.Client/src/assets/lang/da-dk.ts index f8452c5332a5..16ffe924be78 100644 --- a/src/Umbraco.Web.UI.Client/src/assets/lang/da-dk.ts +++ b/src/Umbraco.Web.UI.Client/src/assets/lang/da-dk.ts @@ -298,9 +298,6 @@ export default { removeTextBox: 'Fjern denne tekstboks', contentRoot: 'Indholdsrod', includeUnpublished: 'Inkluder ikke-udgivet indhold.', - forceRepublish: 'Udgiv uændrede elementer.', - forceRepublishWarning: 'ADVARSEL: Udgivelse af alle sider under denne i indholdstræet, uanset om de er ændret eller ej, kan være en ressourcekrævende og langvarig proces.', - forceRepublishAdvisory: 'Dette bør ikke være nødvendigt under normale omstændigheder, så fortsæt kun med denne handling, hvis du er sikker på, at det er nødvendigt.', isSensitiveValue: 'Denne værdi er skjult.Hvis du har brug for adgang til at se denne værdi, bedes du\n kontakte din web-administrator.\n ', isSensitiveValue_short: 'Denne værdi er skjult.', diff --git a/src/Umbraco.Web.UI.Client/src/assets/lang/en-us.ts b/src/Umbraco.Web.UI.Client/src/assets/lang/en-us.ts index cb7ab765540a..d864c5ce7fa9 100644 --- a/src/Umbraco.Web.UI.Client/src/assets/lang/en-us.ts +++ b/src/Umbraco.Web.UI.Client/src/assets/lang/en-us.ts @@ -35,8 +35,8 @@ export default { export: 'Export', exportDocumentType: 'Export Document Type', folderCreate: 'Create folder', - folderDelete: 'Delete folder', - folderRename: 'Rename folder', + folderDelete: 'Delete', + folderRename: 'Rename', import: 'Import', importdocumenttype: 'Import Document Type', importPackage: 'Import Package', @@ -48,7 +48,7 @@ export default { notify: 'Notifications', protect: 'Public Access', publish: 'Publish', - refreshNode: 'Reload', + refreshNode: 'Reload children', remove: 'Remove', rename: 'Rename', republish: 'Republish entire site', @@ -320,9 +320,6 @@ export default { removeTextBox: 'Remove this text box', contentRoot: 'Content root', includeUnpublished: 'Include unpublished content items.', - forceRepublish: 'Publish unchanged items.', - forceRepublishWarning: 'WARNING: Publishing all pages below this one in the content tree, whether or not they have changed, can be an expensive and long-running operation.', - forceRepublishAdvisory: 'This should not be necessary in normal circumstances so please only proceed with this option selected if you are certain it is required.', isSensitiveValue: 'This value is hidden. If you need access to view this value please contact your\n website administrator.\n ', isSensitiveValue_short: 'This value is hidden.', @@ -623,13 +620,15 @@ export default { }, dictionary: { importDictionaryItemHelp: - '\n To import a dictionary item, find the ".udt" file on your computer by clicking the\n "Import" button (you\'ll be asked for confirmation on the next screen)\n ', + 'To import a dictionary item, find the ".udt" file on your computer by clicking the "Add" button (you\'ll be asked for confirmation on the next screen).', itemDoesNotExists: 'Dictionary item does not exist.', parentDoesNotExists: 'Parent item does not exist.', noItems: 'There are no dictionary items.', noItemsInFile: 'There are no dictionary items in this file.', noItemsFound: 'There were no dictionary items found.', createNew: 'Create dictionary item', + pickFile: 'Select file', + pickFileRequired: 'Please select a ".udt" file', }, dictionaryItem: { description: "Edit the different language versions for the dictionary item '%0%' below", diff --git a/src/Umbraco.Web.UI.Client/src/assets/lang/en.ts b/src/Umbraco.Web.UI.Client/src/assets/lang/en.ts index 02354a5a98ca..84ffec33a5be 100644 --- a/src/Umbraco.Web.UI.Client/src/assets/lang/en.ts +++ b/src/Umbraco.Web.UI.Client/src/assets/lang/en.ts @@ -35,8 +35,8 @@ export default { export: 'Export', exportDocumentType: 'Export Document Type', folderCreate: 'Create folder', - folderDelete: 'Delete folder', - folderRename: 'Rename folder', + folderDelete: 'Delete', + folderRename: 'Rename', import: 'Import', importdocumenttype: 'Import Document Type', importPackage: 'Import Package', @@ -49,7 +49,7 @@ export default { protect: 'Public Access', publish: 'Publish', readOnly: 'Read-only', - refreshNode: 'Reload', + refreshNode: 'Reload children', remove: 'Remove', rename: 'Rename', republish: 'Republish entire site', @@ -317,9 +317,6 @@ export default { removeTextBox: 'Remove this text box', contentRoot: 'Content root', includeUnpublished: 'Include unpublished content items.', - forceRepublish: 'Publish unchanged items.', - forceRepublishWarning: 'WARNING: Publishing all pages below this one in the content tree, whether or not they have changed, can be an expensive and long-running operation.', - forceRepublishAdvisory: 'This should not be necessary in normal circumstances so please only proceed with this option selected if you are certain it is required.', isSensitiveValue: 'This value is hidden. If you need access to view this value please contact your\n website administrator.\n ', isSensitiveValue_short: 'This value is hidden.', @@ -625,13 +622,15 @@ export default { }, dictionary: { importDictionaryItemHelp: - '\n To import a dictionary item, find the ".udt" file on your computer by clicking the\n "Import" button (you\'ll be asked for confirmation on the next screen)\n ', + 'To import a dictionary item, find the ".udt" file on your computer by clicking the "Add" button (you\'ll be asked for confirmation on the next screen).', itemDoesNotExists: 'Dictionary item does not exist.', parentDoesNotExists: 'Parent item does not exist.', noItems: 'There are no dictionary items.', noItemsInFile: 'There are no dictionary items in this file.', noItemsFound: 'There were no dictionary items found.', createNew: 'Create dictionary item', + pickFile: 'Select file', + pickFileRequired: 'Please select a ".udt" file', }, dictionaryItem: { description: "Edit the different language versions for the dictionary item '%0%' below", @@ -2735,6 +2734,8 @@ export default { config_overlaySize_description: 'Select the width of the overlay (link picker).', }, tiptap: { + anchor: 'Anchor', + anchor_input: 'Enter an anchor ID', config_dimensions_description: 'Set the maximum width and height of the editor. This excludes the toolbar height.', config_extensions: 'Capabilities', config_toolbar: 'Toolbar', @@ -2754,6 +2755,15 @@ export default { toolbar_removeItem: 'Remove action', toolbar_emptyGroup: 'Empty', sourceCodeEdit: 'Edit source code', + charmap: 'Character map', + charmap_headline: 'Special character', + charmap_currency: 'Currency', + charmap_text: 'Text', + charmap_quotations: 'Quotations', + charmap_maths: 'Mathematical', + charmap_extlatin: 'Extended Latin', + charmap_symbols: 'Symbols', + charmap_arrows: 'Arrows', }, linkPicker: { modalSource: 'Source', diff --git a/src/Umbraco.Web.UI.Client/src/css/rte-content.css b/src/Umbraco.Web.UI.Client/src/css/rte-content.css index 05aa9227a538..f6ec3cbabe8b 100644 --- a/src/Umbraco.Web.UI.Client/src/css/rte-content.css +++ b/src/Umbraco.Web.UI.Client/src/css/rte-content.css @@ -1,24 +1,5 @@ +/* TinyMCE specific styles */ #tinymce { - .umb-macro-holder { - border: 3px dotted red; - padding: 7px; - margin: 3px; - display: block; - position: relative; - } - - .umb-macro-holder::after { - content: 'Macros are no longer supported. Please use the block picker instead.'; - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - color: white; - background-color: rgba(0, 0, 0, 0.7); - padding: 10px; - border-radius: 5px; - } - .umb-embed-holder { position: relative; } @@ -49,3 +30,25 @@ outline: 2px solid var(--uui-palette-spanish-pink-light); } } + +/* General styles (that apply to both TinyMCE and Tiptap RTEs) */ + +.umb-macro-holder { + border: 3px dotted red; + padding: 7px; + margin: 3px; + display: block; + position: relative; +} + +.umb-macro-holder::after { + content: 'Macros are no longer supported. Please use the block picker instead.'; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + color: white; + background-color: rgba(0, 0, 0, 0.7); + padding: 10px; + border-radius: 5px; +} diff --git a/src/Umbraco.Web.UI.Client/src/external/backend-api/src/sdk.gen.ts b/src/Umbraco.Web.UI.Client/src/external/backend-api/src/sdk.gen.ts index 9026fd4055c5..9b36b8ffd2a2 100644 --- a/src/Umbraco.Web.UI.Client/src/external/backend-api/src/sdk.gen.ts +++ b/src/Umbraco.Web.UI.Client/src/external/backend-api/src/sdk.gen.ts @@ -3,7 +3,7 @@ import type { CancelablePromise } from './core/CancelablePromise'; import { OpenAPI } from './core/OpenAPI'; import { request as __request } from './core/request'; -import type { GetCultureData, GetCultureResponse, PostDataTypeData, PostDataTypeResponse, GetDataTypeByIdData, GetDataTypeByIdResponse, DeleteDataTypeByIdData, DeleteDataTypeByIdResponse, PutDataTypeByIdData, PutDataTypeByIdResponse, PostDataTypeByIdCopyData, PostDataTypeByIdCopyResponse, GetDataTypeByIdIsUsedData, GetDataTypeByIdIsUsedResponse, PutDataTypeByIdMoveData, PutDataTypeByIdMoveResponse, GetDataTypeByIdReferencesData, GetDataTypeByIdReferencesResponse, GetDataTypeConfigurationResponse, PostDataTypeFolderData, PostDataTypeFolderResponse, GetDataTypeFolderByIdData, GetDataTypeFolderByIdResponse, DeleteDataTypeFolderByIdData, DeleteDataTypeFolderByIdResponse, PutDataTypeFolderByIdData, PutDataTypeFolderByIdResponse, GetFilterDataTypeData, GetFilterDataTypeResponse, GetItemDataTypeData, GetItemDataTypeResponse, GetItemDataTypeSearchData, GetItemDataTypeSearchResponse, GetTreeDataTypeAncestorsData, GetTreeDataTypeAncestorsResponse, GetTreeDataTypeChildrenData, GetTreeDataTypeChildrenResponse, GetTreeDataTypeRootData, GetTreeDataTypeRootResponse, GetDictionaryData, GetDictionaryResponse, PostDictionaryData, PostDictionaryResponse, GetDictionaryByIdData, GetDictionaryByIdResponse, DeleteDictionaryByIdData, DeleteDictionaryByIdResponse, PutDictionaryByIdData, PutDictionaryByIdResponse, GetDictionaryByIdExportData, GetDictionaryByIdExportResponse, PutDictionaryByIdMoveData, PutDictionaryByIdMoveResponse, PostDictionaryImportData, PostDictionaryImportResponse, GetItemDictionaryData, GetItemDictionaryResponse, GetTreeDictionaryAncestorsData, GetTreeDictionaryAncestorsResponse, GetTreeDictionaryChildrenData, GetTreeDictionaryChildrenResponse, GetTreeDictionaryRootData, GetTreeDictionaryRootResponse, GetCollectionDocumentByIdData, GetCollectionDocumentByIdResponse, PostDocumentData, PostDocumentResponse, GetDocumentByIdData, GetDocumentByIdResponse, DeleteDocumentByIdData, DeleteDocumentByIdResponse, PutDocumentByIdData, PutDocumentByIdResponse, GetDocumentByIdAuditLogData, GetDocumentByIdAuditLogResponse, PostDocumentByIdCopyData, PostDocumentByIdCopyResponse, GetDocumentByIdDomainsData, GetDocumentByIdDomainsResponse, PutDocumentByIdDomainsData, PutDocumentByIdDomainsResponse, PutDocumentByIdMoveData, PutDocumentByIdMoveResponse, PutDocumentByIdMoveToRecycleBinData, PutDocumentByIdMoveToRecycleBinResponse, GetDocumentByIdNotificationsData, GetDocumentByIdNotificationsResponse, PutDocumentByIdNotificationsData, PutDocumentByIdNotificationsResponse, PostDocumentByIdPublicAccessData, PostDocumentByIdPublicAccessResponse, DeleteDocumentByIdPublicAccessData, DeleteDocumentByIdPublicAccessResponse, GetDocumentByIdPublicAccessData, GetDocumentByIdPublicAccessResponse, PutDocumentByIdPublicAccessData, PutDocumentByIdPublicAccessResponse, PutDocumentByIdPublishData, PutDocumentByIdPublishResponse, PutDocumentByIdPublishWithDescendantsData, PutDocumentByIdPublishWithDescendantsResponse, GetDocumentByIdPublishedData, GetDocumentByIdPublishedResponse, GetDocumentByIdReferencedByData, GetDocumentByIdReferencedByResponse, GetDocumentByIdReferencedDescendantsData, GetDocumentByIdReferencedDescendantsResponse, PutDocumentByIdUnpublishData, PutDocumentByIdUnpublishResponse, PutDocumentByIdValidateData, PutDocumentByIdValidateResponse, PutUmbracoManagementApiV11DocumentByIdValidate11Data, PutUmbracoManagementApiV11DocumentByIdValidate11Response, GetDocumentAreReferencedData, GetDocumentAreReferencedResponse, GetDocumentConfigurationResponse, PutDocumentSortData, PutDocumentSortResponse, GetDocumentUrlsData, GetDocumentUrlsResponse, PostDocumentValidateData, PostDocumentValidateResponse, GetItemDocumentData, GetItemDocumentResponse, GetItemDocumentSearchData, GetItemDocumentSearchResponse, DeleteRecycleBinDocumentResponse, DeleteRecycleBinDocumentByIdData, DeleteRecycleBinDocumentByIdResponse, GetRecycleBinDocumentByIdOriginalParentData, GetRecycleBinDocumentByIdOriginalParentResponse, PutRecycleBinDocumentByIdRestoreData, PutRecycleBinDocumentByIdRestoreResponse, GetRecycleBinDocumentChildrenData, GetRecycleBinDocumentChildrenResponse, GetRecycleBinDocumentRootData, GetRecycleBinDocumentRootResponse, GetTreeDocumentAncestorsData, GetTreeDocumentAncestorsResponse, GetTreeDocumentChildrenData, GetTreeDocumentChildrenResponse, GetTreeDocumentRootData, GetTreeDocumentRootResponse, PostDocumentBlueprintData, PostDocumentBlueprintResponse, GetDocumentBlueprintByIdData, GetDocumentBlueprintByIdResponse, DeleteDocumentBlueprintByIdData, DeleteDocumentBlueprintByIdResponse, PutDocumentBlueprintByIdData, PutDocumentBlueprintByIdResponse, PutDocumentBlueprintByIdMoveData, PutDocumentBlueprintByIdMoveResponse, PostDocumentBlueprintFolderData, PostDocumentBlueprintFolderResponse, GetDocumentBlueprintFolderByIdData, GetDocumentBlueprintFolderByIdResponse, DeleteDocumentBlueprintFolderByIdData, DeleteDocumentBlueprintFolderByIdResponse, PutDocumentBlueprintFolderByIdData, PutDocumentBlueprintFolderByIdResponse, PostDocumentBlueprintFromDocumentData, PostDocumentBlueprintFromDocumentResponse, GetItemDocumentBlueprintData, GetItemDocumentBlueprintResponse, GetTreeDocumentBlueprintAncestorsData, GetTreeDocumentBlueprintAncestorsResponse, GetTreeDocumentBlueprintChildrenData, GetTreeDocumentBlueprintChildrenResponse, GetTreeDocumentBlueprintRootData, GetTreeDocumentBlueprintRootResponse, PostDocumentTypeData, PostDocumentTypeResponse, GetDocumentTypeByIdData, GetDocumentTypeByIdResponse, DeleteDocumentTypeByIdData, DeleteDocumentTypeByIdResponse, PutDocumentTypeByIdData, PutDocumentTypeByIdResponse, GetDocumentTypeByIdAllowedChildrenData, GetDocumentTypeByIdAllowedChildrenResponse, GetDocumentTypeByIdBlueprintData, GetDocumentTypeByIdBlueprintResponse, GetDocumentTypeByIdCompositionReferencesData, GetDocumentTypeByIdCompositionReferencesResponse, PostDocumentTypeByIdCopyData, PostDocumentTypeByIdCopyResponse, GetDocumentTypeByIdExportData, GetDocumentTypeByIdExportResponse, PutDocumentTypeByIdImportData, PutDocumentTypeByIdImportResponse, PutDocumentTypeByIdMoveData, PutDocumentTypeByIdMoveResponse, GetDocumentTypeAllowedAtRootData, GetDocumentTypeAllowedAtRootResponse, PostDocumentTypeAvailableCompositionsData, PostDocumentTypeAvailableCompositionsResponse, GetDocumentTypeConfigurationResponse, PostDocumentTypeFolderData, PostDocumentTypeFolderResponse, GetDocumentTypeFolderByIdData, GetDocumentTypeFolderByIdResponse, DeleteDocumentTypeFolderByIdData, DeleteDocumentTypeFolderByIdResponse, PutDocumentTypeFolderByIdData, PutDocumentTypeFolderByIdResponse, PostDocumentTypeImportData, PostDocumentTypeImportResponse, GetItemDocumentTypeData, GetItemDocumentTypeResponse, GetItemDocumentTypeSearchData, GetItemDocumentTypeSearchResponse, GetTreeDocumentTypeAncestorsData, GetTreeDocumentTypeAncestorsResponse, GetTreeDocumentTypeChildrenData, GetTreeDocumentTypeChildrenResponse, GetTreeDocumentTypeRootData, GetTreeDocumentTypeRootResponse, GetDocumentVersionData, GetDocumentVersionResponse, GetDocumentVersionByIdData, GetDocumentVersionByIdResponse, PutDocumentVersionByIdPreventCleanupData, PutDocumentVersionByIdPreventCleanupResponse, PostDocumentVersionByIdRollbackData, PostDocumentVersionByIdRollbackResponse, PostDynamicRootQueryData, PostDynamicRootQueryResponse, GetDynamicRootStepsResponse, GetHealthCheckGroupData, GetHealthCheckGroupResponse, GetHealthCheckGroupByNameData, GetHealthCheckGroupByNameResponse, PostHealthCheckGroupByNameCheckData, PostHealthCheckGroupByNameCheckResponse, PostHealthCheckExecuteActionData, PostHealthCheckExecuteActionResponse, GetHelpData, GetHelpResponse, GetImagingResizeUrlsData, GetImagingResizeUrlsResponse, GetImportAnalyzeData, GetImportAnalyzeResponse, GetIndexerData, GetIndexerResponse, GetIndexerByIndexNameData, GetIndexerByIndexNameResponse, PostIndexerByIndexNameRebuildData, PostIndexerByIndexNameRebuildResponse, GetInstallSettingsResponse, PostInstallSetupData, PostInstallSetupResponse, PostInstallValidateDatabaseData, PostInstallValidateDatabaseResponse, GetItemLanguageData, GetItemLanguageResponse, GetItemLanguageDefaultResponse, GetLanguageData, GetLanguageResponse, PostLanguageData, PostLanguageResponse, GetLanguageByIsoCodeData, GetLanguageByIsoCodeResponse, DeleteLanguageByIsoCodeData, DeleteLanguageByIsoCodeResponse, PutLanguageByIsoCodeData, PutLanguageByIsoCodeResponse, GetLogViewerLevelData, GetLogViewerLevelResponse, GetLogViewerLevelCountData, GetLogViewerLevelCountResponse, GetLogViewerLogData, GetLogViewerLogResponse, GetLogViewerMessageTemplateData, GetLogViewerMessageTemplateResponse, GetLogViewerSavedSearchData, GetLogViewerSavedSearchResponse, PostLogViewerSavedSearchData, PostLogViewerSavedSearchResponse, GetLogViewerSavedSearchByNameData, GetLogViewerSavedSearchByNameResponse, DeleteLogViewerSavedSearchByNameData, DeleteLogViewerSavedSearchByNameResponse, GetLogViewerValidateLogsSizeData, GetLogViewerValidateLogsSizeResponse, GetManifestManifestResponse, GetManifestManifestPrivateResponse, GetManifestManifestPublicResponse, GetCollectionMediaData, GetCollectionMediaResponse, GetItemMediaData, GetItemMediaResponse, GetItemMediaSearchData, GetItemMediaSearchResponse, PostMediaData, PostMediaResponse, GetMediaByIdData, GetMediaByIdResponse, DeleteMediaByIdData, DeleteMediaByIdResponse, PutMediaByIdData, PutMediaByIdResponse, GetMediaByIdAuditLogData, GetMediaByIdAuditLogResponse, PutMediaByIdMoveData, PutMediaByIdMoveResponse, PutMediaByIdMoveToRecycleBinData, PutMediaByIdMoveToRecycleBinResponse, GetMediaByIdReferencedByData, GetMediaByIdReferencedByResponse, GetMediaByIdReferencedDescendantsData, GetMediaByIdReferencedDescendantsResponse, PutMediaByIdValidateData, PutMediaByIdValidateResponse, GetMediaAreReferencedData, GetMediaAreReferencedResponse, GetMediaConfigurationResponse, PutMediaSortData, PutMediaSortResponse, GetMediaUrlsData, GetMediaUrlsResponse, PostMediaValidateData, PostMediaValidateResponse, DeleteRecycleBinMediaResponse, DeleteRecycleBinMediaByIdData, DeleteRecycleBinMediaByIdResponse, GetRecycleBinMediaByIdOriginalParentData, GetRecycleBinMediaByIdOriginalParentResponse, PutRecycleBinMediaByIdRestoreData, PutRecycleBinMediaByIdRestoreResponse, GetRecycleBinMediaChildrenData, GetRecycleBinMediaChildrenResponse, GetRecycleBinMediaRootData, GetRecycleBinMediaRootResponse, GetTreeMediaAncestorsData, GetTreeMediaAncestorsResponse, GetTreeMediaChildrenData, GetTreeMediaChildrenResponse, GetTreeMediaRootData, GetTreeMediaRootResponse, GetItemMediaTypeData, GetItemMediaTypeResponse, GetItemMediaTypeAllowedData, GetItemMediaTypeAllowedResponse, GetItemMediaTypeFoldersData, GetItemMediaTypeFoldersResponse, GetItemMediaTypeSearchData, GetItemMediaTypeSearchResponse, PostMediaTypeData, PostMediaTypeResponse, GetMediaTypeByIdData, GetMediaTypeByIdResponse, DeleteMediaTypeByIdData, DeleteMediaTypeByIdResponse, PutMediaTypeByIdData, PutMediaTypeByIdResponse, GetMediaTypeByIdAllowedChildrenData, GetMediaTypeByIdAllowedChildrenResponse, GetMediaTypeByIdCompositionReferencesData, GetMediaTypeByIdCompositionReferencesResponse, PostMediaTypeByIdCopyData, PostMediaTypeByIdCopyResponse, GetMediaTypeByIdExportData, GetMediaTypeByIdExportResponse, PutMediaTypeByIdImportData, PutMediaTypeByIdImportResponse, PutMediaTypeByIdMoveData, PutMediaTypeByIdMoveResponse, GetMediaTypeAllowedAtRootData, GetMediaTypeAllowedAtRootResponse, PostMediaTypeAvailableCompositionsData, PostMediaTypeAvailableCompositionsResponse, GetMediaTypeConfigurationResponse, PostMediaTypeFolderData, PostMediaTypeFolderResponse, GetMediaTypeFolderByIdData, GetMediaTypeFolderByIdResponse, DeleteMediaTypeFolderByIdData, DeleteMediaTypeFolderByIdResponse, PutMediaTypeFolderByIdData, PutMediaTypeFolderByIdResponse, PostMediaTypeImportData, PostMediaTypeImportResponse, GetTreeMediaTypeAncestorsData, GetTreeMediaTypeAncestorsResponse, GetTreeMediaTypeChildrenData, GetTreeMediaTypeChildrenResponse, GetTreeMediaTypeRootData, GetTreeMediaTypeRootResponse, GetFilterMemberData, GetFilterMemberResponse, GetItemMemberData, GetItemMemberResponse, GetItemMemberSearchData, GetItemMemberSearchResponse, PostMemberData, PostMemberResponse, GetMemberByIdData, GetMemberByIdResponse, DeleteMemberByIdData, DeleteMemberByIdResponse, PutMemberByIdData, PutMemberByIdResponse, PutMemberByIdValidateData, PutMemberByIdValidateResponse, GetMemberConfigurationResponse, PostMemberValidateData, PostMemberValidateResponse, GetItemMemberGroupData, GetItemMemberGroupResponse, GetMemberGroupData, GetMemberGroupResponse, PostMemberGroupData, PostMemberGroupResponse, GetMemberGroupByIdData, GetMemberGroupByIdResponse, DeleteMemberGroupByIdData, DeleteMemberGroupByIdResponse, PutMemberGroupByIdData, PutMemberGroupByIdResponse, GetTreeMemberGroupRootData, GetTreeMemberGroupRootResponse, GetItemMemberTypeData, GetItemMemberTypeResponse, GetItemMemberTypeSearchData, GetItemMemberTypeSearchResponse, PostMemberTypeData, PostMemberTypeResponse, GetMemberTypeByIdData, GetMemberTypeByIdResponse, DeleteMemberTypeByIdData, DeleteMemberTypeByIdResponse, PutMemberTypeByIdData, PutMemberTypeByIdResponse, GetMemberTypeByIdCompositionReferencesData, GetMemberTypeByIdCompositionReferencesResponse, PostMemberTypeByIdCopyData, PostMemberTypeByIdCopyResponse, PostMemberTypeAvailableCompositionsData, PostMemberTypeAvailableCompositionsResponse, GetMemberTypeConfigurationResponse, GetTreeMemberTypeRootData, GetTreeMemberTypeRootResponse, PostModelsBuilderBuildResponse, GetModelsBuilderDashboardResponse, GetModelsBuilderStatusResponse, GetObjectTypesData, GetObjectTypesResponse, GetOembedQueryData, GetOembedQueryResponse, PostPackageByNameRunMigrationData, PostPackageByNameRunMigrationResponse, GetPackageConfigurationResponse, GetPackageCreatedData, GetPackageCreatedResponse, PostPackageCreatedData, PostPackageCreatedResponse, GetPackageCreatedByIdData, GetPackageCreatedByIdResponse, DeletePackageCreatedByIdData, DeletePackageCreatedByIdResponse, PutPackageCreatedByIdData, PutPackageCreatedByIdResponse, GetPackageCreatedByIdDownloadData, GetPackageCreatedByIdDownloadResponse, GetPackageMigrationStatusData, GetPackageMigrationStatusResponse, GetItemPartialViewData, GetItemPartialViewResponse, PostPartialViewData, PostPartialViewResponse, GetPartialViewByPathData, GetPartialViewByPathResponse, DeletePartialViewByPathData, DeletePartialViewByPathResponse, PutPartialViewByPathData, PutPartialViewByPathResponse, PutPartialViewByPathRenameData, PutPartialViewByPathRenameResponse, PostPartialViewFolderData, PostPartialViewFolderResponse, GetPartialViewFolderByPathData, GetPartialViewFolderByPathResponse, DeletePartialViewFolderByPathData, DeletePartialViewFolderByPathResponse, GetPartialViewSnippetData, GetPartialViewSnippetResponse, GetPartialViewSnippetByIdData, GetPartialViewSnippetByIdResponse, GetTreePartialViewAncestorsData, GetTreePartialViewAncestorsResponse, GetTreePartialViewChildrenData, GetTreePartialViewChildrenResponse, GetTreePartialViewRootData, GetTreePartialViewRootResponse, DeletePreviewResponse, PostPreviewResponse, GetProfilingStatusResponse, PutProfilingStatusData, PutProfilingStatusResponse, GetPropertyTypeIsUsedData, GetPropertyTypeIsUsedResponse, PostPublishedCacheRebuildResponse, PostPublishedCacheReloadResponse, GetRedirectManagementData, GetRedirectManagementResponse, GetRedirectManagementByIdData, GetRedirectManagementByIdResponse, DeleteRedirectManagementByIdData, DeleteRedirectManagementByIdResponse, GetRedirectManagementStatusResponse, PostRedirectManagementStatusData, PostRedirectManagementStatusResponse, GetRelationByRelationTypeIdData, GetRelationByRelationTypeIdResponse, GetItemRelationTypeData, GetItemRelationTypeResponse, GetRelationTypeData, GetRelationTypeResponse, GetRelationTypeByIdData, GetRelationTypeByIdResponse, GetItemScriptData, GetItemScriptResponse, PostScriptData, PostScriptResponse, GetScriptByPathData, GetScriptByPathResponse, DeleteScriptByPathData, DeleteScriptByPathResponse, PutScriptByPathData, PutScriptByPathResponse, PutScriptByPathRenameData, PutScriptByPathRenameResponse, PostScriptFolderData, PostScriptFolderResponse, GetScriptFolderByPathData, GetScriptFolderByPathResponse, DeleteScriptFolderByPathData, DeleteScriptFolderByPathResponse, GetTreeScriptAncestorsData, GetTreeScriptAncestorsResponse, GetTreeScriptChildrenData, GetTreeScriptChildrenResponse, GetTreeScriptRootData, GetTreeScriptRootResponse, GetSearcherData, GetSearcherResponse, GetSearcherBySearcherNameQueryData, GetSearcherBySearcherNameQueryResponse, GetSecurityConfigurationResponse, PostSecurityForgotPasswordData, PostSecurityForgotPasswordResponse, PostSecurityForgotPasswordResetData, PostSecurityForgotPasswordResetResponse, PostSecurityForgotPasswordVerifyData, PostSecurityForgotPasswordVerifyResponse, GetSegmentData, GetSegmentResponse, GetServerConfigurationResponse, GetServerInformationResponse, GetServerStatusResponse, GetServerTroubleshootingResponse, GetServerUpgradeCheckResponse, GetItemStaticFileData, GetItemStaticFileResponse, GetTreeStaticFileAncestorsData, GetTreeStaticFileAncestorsResponse, GetTreeStaticFileChildrenData, GetTreeStaticFileChildrenResponse, GetTreeStaticFileRootData, GetTreeStaticFileRootResponse, GetItemStylesheetData, GetItemStylesheetResponse, PostStylesheetData, PostStylesheetResponse, GetStylesheetByPathData, GetStylesheetByPathResponse, DeleteStylesheetByPathData, DeleteStylesheetByPathResponse, PutStylesheetByPathData, PutStylesheetByPathResponse, PutStylesheetByPathRenameData, PutStylesheetByPathRenameResponse, PostStylesheetFolderData, PostStylesheetFolderResponse, GetStylesheetFolderByPathData, GetStylesheetFolderByPathResponse, DeleteStylesheetFolderByPathData, DeleteStylesheetFolderByPathResponse, GetTreeStylesheetAncestorsData, GetTreeStylesheetAncestorsResponse, GetTreeStylesheetChildrenData, GetTreeStylesheetChildrenResponse, GetTreeStylesheetRootData, GetTreeStylesheetRootResponse, GetTagData, GetTagResponse, GetTelemetryData, GetTelemetryResponse, GetTelemetryLevelResponse, PostTelemetryLevelData, PostTelemetryLevelResponse, GetItemTemplateData, GetItemTemplateResponse, GetItemTemplateSearchData, GetItemTemplateSearchResponse, PostTemplateData, PostTemplateResponse, GetTemplateByIdData, GetTemplateByIdResponse, DeleteTemplateByIdData, DeleteTemplateByIdResponse, PutTemplateByIdData, PutTemplateByIdResponse, GetTemplateConfigurationResponse, PostTemplateQueryExecuteData, PostTemplateQueryExecuteResponse, GetTemplateQuerySettingsResponse, GetTreeTemplateAncestorsData, GetTreeTemplateAncestorsResponse, GetTreeTemplateChildrenData, GetTreeTemplateChildrenResponse, GetTreeTemplateRootData, GetTreeTemplateRootResponse, PostTemporaryFileData, PostTemporaryFileResponse, GetTemporaryFileByIdData, GetTemporaryFileByIdResponse, DeleteTemporaryFileByIdData, DeleteTemporaryFileByIdResponse, GetTemporaryFileConfigurationResponse, PostUpgradeAuthorizeResponse, GetUpgradeSettingsResponse, GetFilterUserData, GetFilterUserResponse, GetItemUserData, GetItemUserResponse, PostUserData, PostUserResponse, DeleteUserData, DeleteUserResponse, GetUserData, GetUserResponse, GetUserByIdData, GetUserByIdResponse, DeleteUserByIdData, DeleteUserByIdResponse, PutUserByIdData, PutUserByIdResponse, GetUserById2FaData, GetUserById2FaResponse, DeleteUserById2FaByProviderNameData, DeleteUserById2FaByProviderNameResponse, GetUserByIdCalculateStartNodesData, GetUserByIdCalculateStartNodesResponse, PostUserByIdChangePasswordData, PostUserByIdChangePasswordResponse, PostUserByIdClientCredentialsData, PostUserByIdClientCredentialsResponse, GetUserByIdClientCredentialsData, GetUserByIdClientCredentialsResponse, DeleteUserByIdClientCredentialsByClientIdData, DeleteUserByIdClientCredentialsByClientIdResponse, PostUserByIdResetPasswordData, PostUserByIdResetPasswordResponse, DeleteUserAvatarByIdData, DeleteUserAvatarByIdResponse, PostUserAvatarByIdData, PostUserAvatarByIdResponse, GetUserConfigurationResponse, GetUserCurrentResponse, GetUserCurrent2FaResponse, DeleteUserCurrent2FaByProviderNameData, DeleteUserCurrent2FaByProviderNameResponse, PostUserCurrent2FaByProviderNameData, PostUserCurrent2FaByProviderNameResponse, GetUserCurrent2FaByProviderNameData, GetUserCurrent2FaByProviderNameResponse, PostUserCurrentAvatarData, PostUserCurrentAvatarResponse, PostUserCurrentChangePasswordData, PostUserCurrentChangePasswordResponse, GetUserCurrentConfigurationResponse, GetUserCurrentLoginProvidersResponse, GetUserCurrentPermissionsData, GetUserCurrentPermissionsResponse, GetUserCurrentPermissionsDocumentData, GetUserCurrentPermissionsDocumentResponse, GetUserCurrentPermissionsMediaData, GetUserCurrentPermissionsMediaResponse, PostUserDisableData, PostUserDisableResponse, PostUserEnableData, PostUserEnableResponse, PostUserInviteData, PostUserInviteResponse, PostUserInviteCreatePasswordData, PostUserInviteCreatePasswordResponse, PostUserInviteResendData, PostUserInviteResendResponse, PostUserInviteVerifyData, PostUserInviteVerifyResponse, PostUserSetUserGroupsData, PostUserSetUserGroupsResponse, PostUserUnlockData, PostUserUnlockResponse, PostUserDataData, PostUserDataResponse, GetUserDataData, GetUserDataResponse, PutUserDataData, PutUserDataResponse, GetUserDataByIdData, GetUserDataByIdResponse, GetFilterUserGroupData, GetFilterUserGroupResponse, GetItemUserGroupData, GetItemUserGroupResponse, DeleteUserGroupData, DeleteUserGroupResponse, PostUserGroupData, PostUserGroupResponse, GetUserGroupData, GetUserGroupResponse, GetUserGroupByIdData, GetUserGroupByIdResponse, DeleteUserGroupByIdData, DeleteUserGroupByIdResponse, PutUserGroupByIdData, PutUserGroupByIdResponse, DeleteUserGroupByIdUsersData, DeleteUserGroupByIdUsersResponse, PostUserGroupByIdUsersData, PostUserGroupByIdUsersResponse, GetItemWebhookData, GetItemWebhookResponse, GetWebhookData, GetWebhookResponse, PostWebhookData, PostWebhookResponse, GetWebhookByIdData, GetWebhookByIdResponse, DeleteWebhookByIdData, DeleteWebhookByIdResponse, PutWebhookByIdData, PutWebhookByIdResponse, GetWebhookByIdLogsData, GetWebhookByIdLogsResponse, GetWebhookEventsData, GetWebhookEventsResponse, GetWebhookLogsData, GetWebhookLogsResponse } from './types.gen'; +import type { GetCultureData, GetCultureResponse, PostDataTypeData, PostDataTypeResponse, GetDataTypeByIdData, GetDataTypeByIdResponse, DeleteDataTypeByIdData, DeleteDataTypeByIdResponse, PutDataTypeByIdData, PutDataTypeByIdResponse, PostDataTypeByIdCopyData, PostDataTypeByIdCopyResponse, GetDataTypeByIdIsUsedData, GetDataTypeByIdIsUsedResponse, PutDataTypeByIdMoveData, PutDataTypeByIdMoveResponse, GetDataTypeByIdReferencesData, GetDataTypeByIdReferencesResponse, GetDataTypeConfigurationResponse, PostDataTypeFolderData, PostDataTypeFolderResponse, GetDataTypeFolderByIdData, GetDataTypeFolderByIdResponse, DeleteDataTypeFolderByIdData, DeleteDataTypeFolderByIdResponse, PutDataTypeFolderByIdData, PutDataTypeFolderByIdResponse, GetFilterDataTypeData, GetFilterDataTypeResponse, GetItemDataTypeData, GetItemDataTypeResponse, GetItemDataTypeSearchData, GetItemDataTypeSearchResponse, GetTreeDataTypeAncestorsData, GetTreeDataTypeAncestorsResponse, GetTreeDataTypeChildrenData, GetTreeDataTypeChildrenResponse, GetTreeDataTypeRootData, GetTreeDataTypeRootResponse, GetDictionaryData, GetDictionaryResponse, PostDictionaryData, PostDictionaryResponse, GetDictionaryByIdData, GetDictionaryByIdResponse, DeleteDictionaryByIdData, DeleteDictionaryByIdResponse, PutDictionaryByIdData, PutDictionaryByIdResponse, GetDictionaryByIdExportData, GetDictionaryByIdExportResponse, PutDictionaryByIdMoveData, PutDictionaryByIdMoveResponse, PostDictionaryImportData, PostDictionaryImportResponse, GetItemDictionaryData, GetItemDictionaryResponse, GetTreeDictionaryAncestorsData, GetTreeDictionaryAncestorsResponse, GetTreeDictionaryChildrenData, GetTreeDictionaryChildrenResponse, GetTreeDictionaryRootData, GetTreeDictionaryRootResponse, GetCollectionDocumentByIdData, GetCollectionDocumentByIdResponse, PostDocumentData, PostDocumentResponse, GetDocumentByIdData, GetDocumentByIdResponse, DeleteDocumentByIdData, DeleteDocumentByIdResponse, PutDocumentByIdData, PutDocumentByIdResponse, GetDocumentByIdAuditLogData, GetDocumentByIdAuditLogResponse, PostDocumentByIdCopyData, PostDocumentByIdCopyResponse, GetDocumentByIdDomainsData, GetDocumentByIdDomainsResponse, PutDocumentByIdDomainsData, PutDocumentByIdDomainsResponse, PutDocumentByIdMoveData, PutDocumentByIdMoveResponse, PutDocumentByIdMoveToRecycleBinData, PutDocumentByIdMoveToRecycleBinResponse, GetDocumentByIdNotificationsData, GetDocumentByIdNotificationsResponse, PutDocumentByIdNotificationsData, PutDocumentByIdNotificationsResponse, PostDocumentByIdPublicAccessData, PostDocumentByIdPublicAccessResponse, DeleteDocumentByIdPublicAccessData, DeleteDocumentByIdPublicAccessResponse, GetDocumentByIdPublicAccessData, GetDocumentByIdPublicAccessResponse, PutDocumentByIdPublicAccessData, PutDocumentByIdPublicAccessResponse, PutDocumentByIdPublishData, PutDocumentByIdPublishResponse, PutDocumentByIdPublishWithDescendantsData, PutDocumentByIdPublishWithDescendantsResponse, GetDocumentByIdPublishedData, GetDocumentByIdPublishedResponse, GetDocumentByIdReferencedByData, GetDocumentByIdReferencedByResponse, GetDocumentByIdReferencedDescendantsData, GetDocumentByIdReferencedDescendantsResponse, PutDocumentByIdUnpublishData, PutDocumentByIdUnpublishResponse, PutDocumentByIdValidateData, PutDocumentByIdValidateResponse, PutUmbracoManagementApiV11DocumentByIdValidate11Data, PutUmbracoManagementApiV11DocumentByIdValidate11Response, GetDocumentAreReferencedData, GetDocumentAreReferencedResponse, GetDocumentConfigurationResponse, PutDocumentSortData, PutDocumentSortResponse, GetDocumentUrlsData, GetDocumentUrlsResponse, PostDocumentValidateData, PostDocumentValidateResponse, GetItemDocumentData, GetItemDocumentResponse, GetItemDocumentSearchData, GetItemDocumentSearchResponse, DeleteRecycleBinDocumentResponse, DeleteRecycleBinDocumentByIdData, DeleteRecycleBinDocumentByIdResponse, GetRecycleBinDocumentByIdOriginalParentData, GetRecycleBinDocumentByIdOriginalParentResponse, PutRecycleBinDocumentByIdRestoreData, PutRecycleBinDocumentByIdRestoreResponse, GetRecycleBinDocumentChildrenData, GetRecycleBinDocumentChildrenResponse, GetRecycleBinDocumentRootData, GetRecycleBinDocumentRootResponse, GetTreeDocumentAncestorsData, GetTreeDocumentAncestorsResponse, GetTreeDocumentChildrenData, GetTreeDocumentChildrenResponse, GetTreeDocumentRootData, GetTreeDocumentRootResponse, PostDocumentBlueprintData, PostDocumentBlueprintResponse, GetDocumentBlueprintByIdData, GetDocumentBlueprintByIdResponse, DeleteDocumentBlueprintByIdData, DeleteDocumentBlueprintByIdResponse, PutDocumentBlueprintByIdData, PutDocumentBlueprintByIdResponse, PutDocumentBlueprintByIdMoveData, PutDocumentBlueprintByIdMoveResponse, PostDocumentBlueprintFolderData, PostDocumentBlueprintFolderResponse, GetDocumentBlueprintFolderByIdData, GetDocumentBlueprintFolderByIdResponse, DeleteDocumentBlueprintFolderByIdData, DeleteDocumentBlueprintFolderByIdResponse, PutDocumentBlueprintFolderByIdData, PutDocumentBlueprintFolderByIdResponse, PostDocumentBlueprintFromDocumentData, PostDocumentBlueprintFromDocumentResponse, GetItemDocumentBlueprintData, GetItemDocumentBlueprintResponse, GetTreeDocumentBlueprintAncestorsData, GetTreeDocumentBlueprintAncestorsResponse, GetTreeDocumentBlueprintChildrenData, GetTreeDocumentBlueprintChildrenResponse, GetTreeDocumentBlueprintRootData, GetTreeDocumentBlueprintRootResponse, PostDocumentTypeData, PostDocumentTypeResponse, GetDocumentTypeByIdData, GetDocumentTypeByIdResponse, DeleteDocumentTypeByIdData, DeleteDocumentTypeByIdResponse, PutDocumentTypeByIdData, PutDocumentTypeByIdResponse, GetDocumentTypeByIdAllowedChildrenData, GetDocumentTypeByIdAllowedChildrenResponse, GetDocumentTypeByIdBlueprintData, GetDocumentTypeByIdBlueprintResponse, GetDocumentTypeByIdCompositionReferencesData, GetDocumentTypeByIdCompositionReferencesResponse, PostDocumentTypeByIdCopyData, PostDocumentTypeByIdCopyResponse, GetDocumentTypeByIdExportData, GetDocumentTypeByIdExportResponse, PutDocumentTypeByIdImportData, PutDocumentTypeByIdImportResponse, PutDocumentTypeByIdMoveData, PutDocumentTypeByIdMoveResponse, GetDocumentTypeAllowedAtRootData, GetDocumentTypeAllowedAtRootResponse, PostDocumentTypeAvailableCompositionsData, PostDocumentTypeAvailableCompositionsResponse, GetDocumentTypeConfigurationResponse, PostDocumentTypeFolderData, PostDocumentTypeFolderResponse, GetDocumentTypeFolderByIdData, GetDocumentTypeFolderByIdResponse, DeleteDocumentTypeFolderByIdData, DeleteDocumentTypeFolderByIdResponse, PutDocumentTypeFolderByIdData, PutDocumentTypeFolderByIdResponse, PostDocumentTypeImportData, PostDocumentTypeImportResponse, GetItemDocumentTypeData, GetItemDocumentTypeResponse, GetItemDocumentTypeSearchData, GetItemDocumentTypeSearchResponse, GetTreeDocumentTypeAncestorsData, GetTreeDocumentTypeAncestorsResponse, GetTreeDocumentTypeChildrenData, GetTreeDocumentTypeChildrenResponse, GetTreeDocumentTypeRootData, GetTreeDocumentTypeRootResponse, GetDocumentVersionData, GetDocumentVersionResponse, GetDocumentVersionByIdData, GetDocumentVersionByIdResponse, PutDocumentVersionByIdPreventCleanupData, PutDocumentVersionByIdPreventCleanupResponse, PostDocumentVersionByIdRollbackData, PostDocumentVersionByIdRollbackResponse, PostDynamicRootQueryData, PostDynamicRootQueryResponse, GetDynamicRootStepsResponse, GetHealthCheckGroupData, GetHealthCheckGroupResponse, GetHealthCheckGroupByNameData, GetHealthCheckGroupByNameResponse, PostHealthCheckGroupByNameCheckData, PostHealthCheckGroupByNameCheckResponse, PostHealthCheckExecuteActionData, PostHealthCheckExecuteActionResponse, GetHelpData, GetHelpResponse, GetImagingResizeUrlsData, GetImagingResizeUrlsResponse, GetImportAnalyzeData, GetImportAnalyzeResponse, GetIndexerData, GetIndexerResponse, GetIndexerByIndexNameData, GetIndexerByIndexNameResponse, PostIndexerByIndexNameRebuildData, PostIndexerByIndexNameRebuildResponse, GetInstallSettingsResponse, PostInstallSetupData, PostInstallSetupResponse, PostInstallValidateDatabaseData, PostInstallValidateDatabaseResponse, GetItemLanguageData, GetItemLanguageResponse, GetItemLanguageDefaultResponse, GetLanguageData, GetLanguageResponse, PostLanguageData, PostLanguageResponse, GetLanguageByIsoCodeData, GetLanguageByIsoCodeResponse, DeleteLanguageByIsoCodeData, DeleteLanguageByIsoCodeResponse, PutLanguageByIsoCodeData, PutLanguageByIsoCodeResponse, GetLogViewerLevelData, GetLogViewerLevelResponse, GetLogViewerLevelCountData, GetLogViewerLevelCountResponse, GetLogViewerLogData, GetLogViewerLogResponse, GetLogViewerMessageTemplateData, GetLogViewerMessageTemplateResponse, GetLogViewerSavedSearchData, GetLogViewerSavedSearchResponse, PostLogViewerSavedSearchData, PostLogViewerSavedSearchResponse, GetLogViewerSavedSearchByNameData, GetLogViewerSavedSearchByNameResponse, DeleteLogViewerSavedSearchByNameData, DeleteLogViewerSavedSearchByNameResponse, GetLogViewerValidateLogsSizeData, GetLogViewerValidateLogsSizeResponse, GetManifestManifestResponse, GetManifestManifestPrivateResponse, GetManifestManifestPublicResponse, GetCollectionMediaData, GetCollectionMediaResponse, GetItemMediaData, GetItemMediaResponse, GetItemMediaSearchData, GetItemMediaSearchResponse, PostMediaData, PostMediaResponse, GetMediaByIdData, GetMediaByIdResponse, DeleteMediaByIdData, DeleteMediaByIdResponse, PutMediaByIdData, PutMediaByIdResponse, GetMediaByIdAuditLogData, GetMediaByIdAuditLogResponse, PutMediaByIdMoveData, PutMediaByIdMoveResponse, PutMediaByIdMoveToRecycleBinData, PutMediaByIdMoveToRecycleBinResponse, GetMediaByIdReferencedByData, GetMediaByIdReferencedByResponse, GetMediaByIdReferencedDescendantsData, GetMediaByIdReferencedDescendantsResponse, PutMediaByIdValidateData, PutMediaByIdValidateResponse, GetMediaAreReferencedData, GetMediaAreReferencedResponse, GetMediaConfigurationResponse, PutMediaSortData, PutMediaSortResponse, GetMediaUrlsData, GetMediaUrlsResponse, PostMediaValidateData, PostMediaValidateResponse, DeleteRecycleBinMediaResponse, DeleteRecycleBinMediaByIdData, DeleteRecycleBinMediaByIdResponse, GetRecycleBinMediaByIdOriginalParentData, GetRecycleBinMediaByIdOriginalParentResponse, PutRecycleBinMediaByIdRestoreData, PutRecycleBinMediaByIdRestoreResponse, GetRecycleBinMediaChildrenData, GetRecycleBinMediaChildrenResponse, GetRecycleBinMediaRootData, GetRecycleBinMediaRootResponse, GetTreeMediaAncestorsData, GetTreeMediaAncestorsResponse, GetTreeMediaChildrenData, GetTreeMediaChildrenResponse, GetTreeMediaRootData, GetTreeMediaRootResponse, GetItemMediaTypeData, GetItemMediaTypeResponse, GetItemMediaTypeAllowedData, GetItemMediaTypeAllowedResponse, GetItemMediaTypeFoldersData, GetItemMediaTypeFoldersResponse, GetItemMediaTypeSearchData, GetItemMediaTypeSearchResponse, PostMediaTypeData, PostMediaTypeResponse, GetMediaTypeByIdData, GetMediaTypeByIdResponse, DeleteMediaTypeByIdData, DeleteMediaTypeByIdResponse, PutMediaTypeByIdData, PutMediaTypeByIdResponse, GetMediaTypeByIdAllowedChildrenData, GetMediaTypeByIdAllowedChildrenResponse, GetMediaTypeByIdCompositionReferencesData, GetMediaTypeByIdCompositionReferencesResponse, PostMediaTypeByIdCopyData, PostMediaTypeByIdCopyResponse, GetMediaTypeByIdExportData, GetMediaTypeByIdExportResponse, PutMediaTypeByIdImportData, PutMediaTypeByIdImportResponse, PutMediaTypeByIdMoveData, PutMediaTypeByIdMoveResponse, GetMediaTypeAllowedAtRootData, GetMediaTypeAllowedAtRootResponse, PostMediaTypeAvailableCompositionsData, PostMediaTypeAvailableCompositionsResponse, GetMediaTypeConfigurationResponse, PostMediaTypeFolderData, PostMediaTypeFolderResponse, GetMediaTypeFolderByIdData, GetMediaTypeFolderByIdResponse, DeleteMediaTypeFolderByIdData, DeleteMediaTypeFolderByIdResponse, PutMediaTypeFolderByIdData, PutMediaTypeFolderByIdResponse, PostMediaTypeImportData, PostMediaTypeImportResponse, GetTreeMediaTypeAncestorsData, GetTreeMediaTypeAncestorsResponse, GetTreeMediaTypeChildrenData, GetTreeMediaTypeChildrenResponse, GetTreeMediaTypeRootData, GetTreeMediaTypeRootResponse, GetFilterMemberData, GetFilterMemberResponse, GetItemMemberData, GetItemMemberResponse, GetItemMemberSearchData, GetItemMemberSearchResponse, PostMemberData, PostMemberResponse, GetMemberByIdData, GetMemberByIdResponse, DeleteMemberByIdData, DeleteMemberByIdResponse, PutMemberByIdData, PutMemberByIdResponse, PutMemberByIdValidateData, PutMemberByIdValidateResponse, GetMemberConfigurationResponse, PostMemberValidateData, PostMemberValidateResponse, GetItemMemberGroupData, GetItemMemberGroupResponse, GetMemberGroupData, GetMemberGroupResponse, PostMemberGroupData, PostMemberGroupResponse, GetMemberGroupByIdData, GetMemberGroupByIdResponse, DeleteMemberGroupByIdData, DeleteMemberGroupByIdResponse, PutMemberGroupByIdData, PutMemberGroupByIdResponse, GetTreeMemberGroupRootData, GetTreeMemberGroupRootResponse, GetItemMemberTypeData, GetItemMemberTypeResponse, GetItemMemberTypeSearchData, GetItemMemberTypeSearchResponse, PostMemberTypeData, PostMemberTypeResponse, GetMemberTypeByIdData, GetMemberTypeByIdResponse, DeleteMemberTypeByIdData, DeleteMemberTypeByIdResponse, PutMemberTypeByIdData, PutMemberTypeByIdResponse, GetMemberTypeByIdCompositionReferencesData, GetMemberTypeByIdCompositionReferencesResponse, PostMemberTypeByIdCopyData, PostMemberTypeByIdCopyResponse, PostMemberTypeAvailableCompositionsData, PostMemberTypeAvailableCompositionsResponse, GetMemberTypeConfigurationResponse, GetTreeMemberTypeRootData, GetTreeMemberTypeRootResponse, PostModelsBuilderBuildResponse, GetModelsBuilderDashboardResponse, GetModelsBuilderStatusResponse, GetObjectTypesData, GetObjectTypesResponse, GetOembedQueryData, GetOembedQueryResponse, PostPackageByNameRunMigrationData, PostPackageByNameRunMigrationResponse, GetPackageConfigurationResponse, GetPackageCreatedData, GetPackageCreatedResponse, PostPackageCreatedData, PostPackageCreatedResponse, GetPackageCreatedByIdData, GetPackageCreatedByIdResponse, DeletePackageCreatedByIdData, DeletePackageCreatedByIdResponse, PutPackageCreatedByIdData, PutPackageCreatedByIdResponse, GetPackageCreatedByIdDownloadData, GetPackageCreatedByIdDownloadResponse, GetPackageMigrationStatusData, GetPackageMigrationStatusResponse, GetItemPartialViewData, GetItemPartialViewResponse, PostPartialViewData, PostPartialViewResponse, GetPartialViewByPathData, GetPartialViewByPathResponse, DeletePartialViewByPathData, DeletePartialViewByPathResponse, PutPartialViewByPathData, PutPartialViewByPathResponse, PutPartialViewByPathRenameData, PutPartialViewByPathRenameResponse, PostPartialViewFolderData, PostPartialViewFolderResponse, GetPartialViewFolderByPathData, GetPartialViewFolderByPathResponse, DeletePartialViewFolderByPathData, DeletePartialViewFolderByPathResponse, GetPartialViewSnippetData, GetPartialViewSnippetResponse, GetPartialViewSnippetByIdData, GetPartialViewSnippetByIdResponse, GetTreePartialViewAncestorsData, GetTreePartialViewAncestorsResponse, GetTreePartialViewChildrenData, GetTreePartialViewChildrenResponse, GetTreePartialViewRootData, GetTreePartialViewRootResponse, DeletePreviewResponse, PostPreviewResponse, GetProfilingStatusResponse, PutProfilingStatusData, PutProfilingStatusResponse, GetPropertyTypeIsUsedData, GetPropertyTypeIsUsedResponse, PostPublishedCacheRebuildResponse, GetPublishedCacheRebuildStatusResponse, PostPublishedCacheReloadResponse, GetRedirectManagementData, GetRedirectManagementResponse, GetRedirectManagementByIdData, GetRedirectManagementByIdResponse, DeleteRedirectManagementByIdData, DeleteRedirectManagementByIdResponse, GetRedirectManagementStatusResponse, PostRedirectManagementStatusData, PostRedirectManagementStatusResponse, GetRelationByRelationTypeIdData, GetRelationByRelationTypeIdResponse, GetItemRelationTypeData, GetItemRelationTypeResponse, GetRelationTypeData, GetRelationTypeResponse, GetRelationTypeByIdData, GetRelationTypeByIdResponse, GetItemScriptData, GetItemScriptResponse, PostScriptData, PostScriptResponse, GetScriptByPathData, GetScriptByPathResponse, DeleteScriptByPathData, DeleteScriptByPathResponse, PutScriptByPathData, PutScriptByPathResponse, PutScriptByPathRenameData, PutScriptByPathRenameResponse, PostScriptFolderData, PostScriptFolderResponse, GetScriptFolderByPathData, GetScriptFolderByPathResponse, DeleteScriptFolderByPathData, DeleteScriptFolderByPathResponse, GetTreeScriptAncestorsData, GetTreeScriptAncestorsResponse, GetTreeScriptChildrenData, GetTreeScriptChildrenResponse, GetTreeScriptRootData, GetTreeScriptRootResponse, GetSearcherData, GetSearcherResponse, GetSearcherBySearcherNameQueryData, GetSearcherBySearcherNameQueryResponse, GetSecurityConfigurationResponse, PostSecurityForgotPasswordData, PostSecurityForgotPasswordResponse, PostSecurityForgotPasswordResetData, PostSecurityForgotPasswordResetResponse, PostSecurityForgotPasswordVerifyData, PostSecurityForgotPasswordVerifyResponse, GetSegmentData, GetSegmentResponse, GetServerConfigurationResponse, GetServerInformationResponse, GetServerStatusResponse, GetServerTroubleshootingResponse, GetServerUpgradeCheckResponse, GetItemStaticFileData, GetItemStaticFileResponse, GetTreeStaticFileAncestorsData, GetTreeStaticFileAncestorsResponse, GetTreeStaticFileChildrenData, GetTreeStaticFileChildrenResponse, GetTreeStaticFileRootData, GetTreeStaticFileRootResponse, GetItemStylesheetData, GetItemStylesheetResponse, PostStylesheetData, PostStylesheetResponse, GetStylesheetByPathData, GetStylesheetByPathResponse, DeleteStylesheetByPathData, DeleteStylesheetByPathResponse, PutStylesheetByPathData, PutStylesheetByPathResponse, PutStylesheetByPathRenameData, PutStylesheetByPathRenameResponse, PostStylesheetFolderData, PostStylesheetFolderResponse, GetStylesheetFolderByPathData, GetStylesheetFolderByPathResponse, DeleteStylesheetFolderByPathData, DeleteStylesheetFolderByPathResponse, GetTreeStylesheetAncestorsData, GetTreeStylesheetAncestorsResponse, GetTreeStylesheetChildrenData, GetTreeStylesheetChildrenResponse, GetTreeStylesheetRootData, GetTreeStylesheetRootResponse, GetTagData, GetTagResponse, GetTelemetryData, GetTelemetryResponse, GetTelemetryLevelResponse, PostTelemetryLevelData, PostTelemetryLevelResponse, GetItemTemplateData, GetItemTemplateResponse, GetItemTemplateSearchData, GetItemTemplateSearchResponse, PostTemplateData, PostTemplateResponse, GetTemplateByIdData, GetTemplateByIdResponse, DeleteTemplateByIdData, DeleteTemplateByIdResponse, PutTemplateByIdData, PutTemplateByIdResponse, GetTemplateConfigurationResponse, PostTemplateQueryExecuteData, PostTemplateQueryExecuteResponse, GetTemplateQuerySettingsResponse, GetTreeTemplateAncestorsData, GetTreeTemplateAncestorsResponse, GetTreeTemplateChildrenData, GetTreeTemplateChildrenResponse, GetTreeTemplateRootData, GetTreeTemplateRootResponse, PostTemporaryFileData, PostTemporaryFileResponse, GetTemporaryFileByIdData, GetTemporaryFileByIdResponse, DeleteTemporaryFileByIdData, DeleteTemporaryFileByIdResponse, GetTemporaryFileConfigurationResponse, PostUpgradeAuthorizeResponse, GetUpgradeSettingsResponse, GetFilterUserData, GetFilterUserResponse, GetItemUserData, GetItemUserResponse, PostUserData, PostUserResponse, DeleteUserData, DeleteUserResponse, GetUserData, GetUserResponse, GetUserByIdData, GetUserByIdResponse, DeleteUserByIdData, DeleteUserByIdResponse, PutUserByIdData, PutUserByIdResponse, GetUserById2FaData, GetUserById2FaResponse, DeleteUserById2FaByProviderNameData, DeleteUserById2FaByProviderNameResponse, GetUserByIdCalculateStartNodesData, GetUserByIdCalculateStartNodesResponse, PostUserByIdChangePasswordData, PostUserByIdChangePasswordResponse, PostUserByIdClientCredentialsData, PostUserByIdClientCredentialsResponse, GetUserByIdClientCredentialsData, GetUserByIdClientCredentialsResponse, DeleteUserByIdClientCredentialsByClientIdData, DeleteUserByIdClientCredentialsByClientIdResponse, PostUserByIdResetPasswordData, PostUserByIdResetPasswordResponse, DeleteUserAvatarByIdData, DeleteUserAvatarByIdResponse, PostUserAvatarByIdData, PostUserAvatarByIdResponse, GetUserConfigurationResponse, GetUserCurrentResponse, GetUserCurrent2FaResponse, DeleteUserCurrent2FaByProviderNameData, DeleteUserCurrent2FaByProviderNameResponse, PostUserCurrent2FaByProviderNameData, PostUserCurrent2FaByProviderNameResponse, GetUserCurrent2FaByProviderNameData, GetUserCurrent2FaByProviderNameResponse, PostUserCurrentAvatarData, PostUserCurrentAvatarResponse, PostUserCurrentChangePasswordData, PostUserCurrentChangePasswordResponse, GetUserCurrentConfigurationResponse, GetUserCurrentLoginProvidersResponse, GetUserCurrentPermissionsData, GetUserCurrentPermissionsResponse, GetUserCurrentPermissionsDocumentData, GetUserCurrentPermissionsDocumentResponse, GetUserCurrentPermissionsMediaData, GetUserCurrentPermissionsMediaResponse, PostUserDisableData, PostUserDisableResponse, PostUserEnableData, PostUserEnableResponse, PostUserInviteData, PostUserInviteResponse, PostUserInviteCreatePasswordData, PostUserInviteCreatePasswordResponse, PostUserInviteResendData, PostUserInviteResendResponse, PostUserInviteVerifyData, PostUserInviteVerifyResponse, PostUserSetUserGroupsData, PostUserSetUserGroupsResponse, PostUserUnlockData, PostUserUnlockResponse, PostUserDataData, PostUserDataResponse, GetUserDataData, GetUserDataResponse, PutUserDataData, PutUserDataResponse, GetUserDataByIdData, GetUserDataByIdResponse, GetFilterUserGroupData, GetFilterUserGroupResponse, GetItemUserGroupData, GetItemUserGroupResponse, DeleteUserGroupData, DeleteUserGroupResponse, PostUserGroupData, PostUserGroupResponse, GetUserGroupData, GetUserGroupResponse, GetUserGroupByIdData, GetUserGroupByIdResponse, DeleteUserGroupByIdData, DeleteUserGroupByIdResponse, PutUserGroupByIdData, PutUserGroupByIdResponse, DeleteUserGroupByIdUsersData, DeleteUserGroupByIdUsersResponse, PostUserGroupByIdUsersData, PostUserGroupByIdUsersResponse, GetItemWebhookData, GetItemWebhookResponse, GetWebhookData, GetWebhookResponse, PostWebhookData, PostWebhookResponse, GetWebhookByIdData, GetWebhookByIdResponse, DeleteWebhookByIdData, DeleteWebhookByIdResponse, PutWebhookByIdData, PutWebhookByIdResponse, GetWebhookByIdLogsData, GetWebhookByIdLogsResponse, GetWebhookEventsData, GetWebhookEventsResponse, GetWebhookLogsData, GetWebhookLogsResponse } from './types.gen'; export class CultureService { /** @@ -46,7 +46,7 @@ export class DataTypeService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -67,7 +67,7 @@ export class DataTypeService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -90,7 +90,7 @@ export class DataTypeService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -116,7 +116,7 @@ export class DataTypeService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -141,7 +141,7 @@ export class DataTypeService { responseHeader: 'Umb-Generated-Resource', errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -162,7 +162,7 @@ export class DataTypeService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -187,7 +187,7 @@ export class DataTypeService { responseHeader: 'Umb-Notifications', errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -208,7 +208,7 @@ export class DataTypeService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -224,7 +224,7 @@ export class DataTypeService { url: '/umbraco/management/api/v1/data-type/configuration', errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -245,7 +245,7 @@ export class DataTypeService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -266,7 +266,7 @@ export class DataTypeService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -289,7 +289,7 @@ export class DataTypeService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -315,7 +315,7 @@ export class DataTypeService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -344,7 +344,7 @@ export class DataTypeService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -406,7 +406,7 @@ export class DataTypeService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -432,7 +432,7 @@ export class DataTypeService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -456,7 +456,7 @@ export class DataTypeService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -483,7 +483,7 @@ export class DictionaryService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -504,7 +504,7 @@ export class DictionaryService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found', 409: 'Conflict' } @@ -526,7 +526,7 @@ export class DictionaryService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -549,7 +549,7 @@ export class DictionaryService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -575,7 +575,7 @@ export class DictionaryService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -600,7 +600,7 @@ export class DictionaryService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -626,7 +626,7 @@ export class DictionaryService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -648,7 +648,7 @@ export class DictionaryService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -688,7 +688,7 @@ export class DictionaryService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -712,7 +712,7 @@ export class DictionaryService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -734,7 +734,7 @@ export class DictionaryService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -774,7 +774,7 @@ export class DocumentService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -796,7 +796,7 @@ export class DocumentService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -817,7 +817,7 @@ export class DocumentService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -840,7 +840,7 @@ export class DocumentService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -866,7 +866,7 @@ export class DocumentService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -897,7 +897,7 @@ export class DocumentService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -921,7 +921,7 @@ export class DocumentService { responseHeader: 'Umb-Generated-Resource', errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -942,7 +942,7 @@ export class DocumentService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -968,7 +968,7 @@ export class DocumentService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found', 409: 'Conflict' } @@ -994,7 +994,7 @@ export class DocumentService { responseHeader: 'Umb-Notifications', errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -1017,7 +1017,7 @@ export class DocumentService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -1038,7 +1038,7 @@ export class DocumentService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -1063,7 +1063,7 @@ export class DocumentService { responseHeader: 'Umb-Notifications', errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -1088,7 +1088,7 @@ export class DocumentService { responseHeader: 'Umb-Generated-Resource', errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -1110,7 +1110,7 @@ export class DocumentService { responseHeader: 'Umb-Notifications', errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -1131,7 +1131,7 @@ export class DocumentService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -1156,7 +1156,7 @@ export class DocumentService { responseHeader: 'Umb-Notifications', errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -1182,7 +1182,7 @@ export class DocumentService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -1208,7 +1208,7 @@ export class DocumentService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -1229,7 +1229,7 @@ export class DocumentService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -1256,7 +1256,7 @@ export class DocumentService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -1282,7 +1282,7 @@ export class DocumentService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -1307,7 +1307,7 @@ export class DocumentService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -1334,7 +1334,7 @@ export class DocumentService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -1360,7 +1360,7 @@ export class DocumentService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -1385,7 +1385,7 @@ export class DocumentService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -1400,7 +1400,7 @@ export class DocumentService { url: '/umbraco/management/api/v1/document/configuration', errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -1421,7 +1421,7 @@ export class DocumentService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -1442,7 +1442,7 @@ export class DocumentService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -1463,7 +1463,7 @@ export class DocumentService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -1527,7 +1527,7 @@ export class DocumentService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -1549,7 +1549,7 @@ export class DocumentService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -1571,7 +1571,7 @@ export class DocumentService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -1597,7 +1597,7 @@ export class DocumentService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -1622,7 +1622,7 @@ export class DocumentService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -1644,7 +1644,7 @@ export class DocumentService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -1664,7 +1664,7 @@ export class DocumentService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -1690,7 +1690,7 @@ export class DocumentService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -1714,7 +1714,7 @@ export class DocumentService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -1738,7 +1738,7 @@ export class DocumentBlueprintService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -1759,7 +1759,7 @@ export class DocumentBlueprintService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -1782,7 +1782,7 @@ export class DocumentBlueprintService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -1808,7 +1808,7 @@ export class DocumentBlueprintService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -1833,7 +1833,7 @@ export class DocumentBlueprintService { responseHeader: 'Umb-Notifications', errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -1855,7 +1855,7 @@ export class DocumentBlueprintService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -1876,7 +1876,7 @@ export class DocumentBlueprintService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -1899,7 +1899,7 @@ export class DocumentBlueprintService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -1925,7 +1925,7 @@ export class DocumentBlueprintService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -1946,7 +1946,7 @@ export class DocumentBlueprintService { responseHeader: 'Umb-Generated-Resource', errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -1986,7 +1986,7 @@ export class DocumentBlueprintService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -2012,7 +2012,7 @@ export class DocumentBlueprintService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -2036,7 +2036,7 @@ export class DocumentBlueprintService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -2060,7 +2060,7 @@ export class DocumentTypeService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -2081,7 +2081,7 @@ export class DocumentTypeService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -2103,7 +2103,7 @@ export class DocumentTypeService { responseHeader: 'Umb-Notifications', errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -2129,7 +2129,7 @@ export class DocumentTypeService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -2138,6 +2138,7 @@ export class DocumentTypeService { /** * @param data The data for the request. * @param data.id + * @param data.parentContentKey * @param data.skip * @param data.take * @returns unknown OK @@ -2151,12 +2152,13 @@ export class DocumentTypeService { id: data.id }, query: { + parentContentKey: data.parentContentKey, skip: data.skip, take: data.take }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -2183,7 +2185,7 @@ export class DocumentTypeService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -2205,7 +2207,7 @@ export class DocumentTypeService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -2231,7 +2233,7 @@ export class DocumentTypeService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -2252,7 +2254,7 @@ export class DocumentTypeService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -2278,7 +2280,7 @@ export class DocumentTypeService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -2304,7 +2306,7 @@ export class DocumentTypeService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -2327,7 +2329,7 @@ export class DocumentTypeService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -2346,7 +2348,7 @@ export class DocumentTypeService { mediaType: 'application/json', errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -2361,7 +2363,7 @@ export class DocumentTypeService { url: '/umbraco/management/api/v1/document-type/configuration', errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -2382,7 +2384,7 @@ export class DocumentTypeService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -2403,7 +2405,7 @@ export class DocumentTypeService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -2426,7 +2428,7 @@ export class DocumentTypeService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -2452,7 +2454,7 @@ export class DocumentTypeService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -2474,7 +2476,7 @@ export class DocumentTypeService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -2537,7 +2539,7 @@ export class DocumentTypeService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -2563,7 +2565,7 @@ export class DocumentTypeService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -2587,7 +2589,7 @@ export class DocumentTypeService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -2617,7 +2619,7 @@ export class DocumentVersionService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -2639,7 +2641,7 @@ export class DocumentVersionService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -2666,7 +2668,7 @@ export class DocumentVersionService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -2693,7 +2695,7 @@ export class DocumentVersionService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -2716,7 +2718,7 @@ export class DynamicRootService { mediaType: 'application/json', errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -2731,7 +2733,7 @@ export class DynamicRootService { url: '/umbraco/management/api/v1/dynamic-root/steps', errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -2756,7 +2758,7 @@ export class HealthCheckService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -2776,7 +2778,7 @@ export class HealthCheckService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -2797,7 +2799,7 @@ export class HealthCheckService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -2818,7 +2820,7 @@ export class HealthCheckService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -2878,7 +2880,7 @@ export class ImagingService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -3102,7 +3104,7 @@ export class LanguageService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -3145,7 +3147,7 @@ export class LanguageService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -3171,7 +3173,7 @@ export class LanguageService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -3197,7 +3199,7 @@ export class LogViewerService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -3220,7 +3222,7 @@ export class LogViewerService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -3252,7 +3254,7 @@ export class LogViewerService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -3279,7 +3281,7 @@ export class LogViewerService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -3301,7 +3303,7 @@ export class LogViewerService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -3322,7 +3324,7 @@ export class LogViewerService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -3342,7 +3344,7 @@ export class LogViewerService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -3364,7 +3366,7 @@ export class LogViewerService { responseHeader: 'Umb-Notifications', errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -3388,7 +3390,7 @@ export class LogViewerService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -3406,7 +3408,7 @@ export class ManifestService { url: '/umbraco/management/api/v1/manifest/manifest', errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -3421,7 +3423,7 @@ export class ManifestService { url: '/umbraco/management/api/v1/manifest/manifest/private', errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -3468,7 +3470,7 @@ export class MediaService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -3536,7 +3538,7 @@ export class MediaService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -3557,7 +3559,7 @@ export class MediaService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -3580,7 +3582,7 @@ export class MediaService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -3606,7 +3608,7 @@ export class MediaService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -3637,7 +3639,7 @@ export class MediaService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -3661,7 +3663,7 @@ export class MediaService { responseHeader: 'Umb-Notifications', errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -3684,7 +3686,7 @@ export class MediaService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -3711,7 +3713,7 @@ export class MediaService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -3737,7 +3739,7 @@ export class MediaService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -3762,7 +3764,7 @@ export class MediaService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -3787,7 +3789,7 @@ export class MediaService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -3802,7 +3804,7 @@ export class MediaService { url: '/umbraco/management/api/v1/media/configuration', errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -3823,7 +3825,7 @@ export class MediaService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -3844,7 +3846,7 @@ export class MediaService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -3865,7 +3867,7 @@ export class MediaService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -3883,7 +3885,7 @@ export class MediaService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -3905,7 +3907,7 @@ export class MediaService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -3927,7 +3929,7 @@ export class MediaService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -3953,7 +3955,7 @@ export class MediaService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -3978,7 +3980,7 @@ export class MediaService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -4000,7 +4002,7 @@ export class MediaService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -4020,7 +4022,7 @@ export class MediaService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -4046,7 +4048,7 @@ export class MediaService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -4070,7 +4072,7 @@ export class MediaService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -4180,7 +4182,7 @@ export class MediaTypeService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -4201,7 +4203,7 @@ export class MediaTypeService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -4223,7 +4225,7 @@ export class MediaTypeService { responseHeader: 'Umb-Notifications', errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -4249,7 +4251,7 @@ export class MediaTypeService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -4258,6 +4260,7 @@ export class MediaTypeService { /** * @param data The data for the request. * @param data.id + * @param data.parentContentKey * @param data.skip * @param data.take * @returns unknown OK @@ -4271,12 +4274,13 @@ export class MediaTypeService { id: data.id }, query: { + parentContentKey: data.parentContentKey, skip: data.skip, take: data.take }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -4298,7 +4302,7 @@ export class MediaTypeService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -4324,7 +4328,7 @@ export class MediaTypeService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -4345,7 +4349,7 @@ export class MediaTypeService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -4371,7 +4375,7 @@ export class MediaTypeService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -4397,7 +4401,7 @@ export class MediaTypeService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -4420,7 +4424,7 @@ export class MediaTypeService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -4439,7 +4443,7 @@ export class MediaTypeService { mediaType: 'application/json', errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -4454,7 +4458,7 @@ export class MediaTypeService { url: '/umbraco/management/api/v1/media-type/configuration', errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -4475,7 +4479,7 @@ export class MediaTypeService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -4496,7 +4500,7 @@ export class MediaTypeService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -4519,7 +4523,7 @@ export class MediaTypeService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -4545,7 +4549,7 @@ export class MediaTypeService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -4567,7 +4571,7 @@ export class MediaTypeService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -4588,7 +4592,7 @@ export class MediaTypeService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -4614,7 +4618,7 @@ export class MediaTypeService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -4638,7 +4642,7 @@ export class MediaTypeService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -4677,7 +4681,7 @@ export class MemberService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -4743,7 +4747,7 @@ export class MemberService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -4764,7 +4768,7 @@ export class MemberService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -4787,7 +4791,7 @@ export class MemberService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -4813,7 +4817,7 @@ export class MemberService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -4839,7 +4843,7 @@ export class MemberService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -4855,7 +4859,7 @@ export class MemberService { url: '/umbraco/management/api/v1/member/configuration', errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -4876,7 +4880,7 @@ export class MemberService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -4921,7 +4925,7 @@ export class MemberGroupService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -4942,7 +4946,7 @@ export class MemberGroupService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -4962,7 +4966,7 @@ export class MemberGroupService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -4985,7 +4989,7 @@ export class MemberGroupService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -5011,7 +5015,7 @@ export class MemberGroupService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -5034,7 +5038,7 @@ export class MemberGroupService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -5100,7 +5104,7 @@ export class MemberTypeService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -5121,7 +5125,7 @@ export class MemberTypeService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -5143,7 +5147,7 @@ export class MemberTypeService { responseHeader: 'Umb-Notifications', errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -5169,7 +5173,7 @@ export class MemberTypeService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -5191,7 +5195,7 @@ export class MemberTypeService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -5214,7 +5218,7 @@ export class MemberTypeService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -5234,7 +5238,7 @@ export class MemberTypeService { mediaType: 'application/json', errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -5249,7 +5253,7 @@ export class MemberTypeService { url: '/umbraco/management/api/v1/member-type/configuration', errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -5271,7 +5275,7 @@ export class MemberTypeService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -5290,7 +5294,7 @@ export class ModelsBuilderService { responseHeader: 'Umb-Notifications', errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 428: 'Precondition Required' } }); @@ -5306,7 +5310,7 @@ export class ModelsBuilderService { url: '/umbraco/management/api/v1/models-builder/dashboard', errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -5321,7 +5325,7 @@ export class ModelsBuilderService { url: '/umbraco/management/api/v1/models-builder/status', errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -5372,7 +5376,7 @@ export class OEmbedService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -5396,7 +5400,7 @@ export class PackageService { responseHeader: 'Umb-Notifications', errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found', 409: 'Conflict' } @@ -5413,7 +5417,7 @@ export class PackageService { url: '/umbraco/management/api/v1/package/configuration', errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -5435,7 +5439,7 @@ export class PackageService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -5456,7 +5460,7 @@ export class PackageService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -5477,7 +5481,7 @@ export class PackageService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -5499,7 +5503,7 @@ export class PackageService { responseHeader: 'Umb-Notifications', errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -5524,7 +5528,7 @@ export class PackageService { responseHeader: 'Umb-Notifications', errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -5545,7 +5549,7 @@ export class PackageService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -5568,7 +5572,7 @@ export class PackageService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -5611,7 +5615,7 @@ export class PartialViewService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -5632,7 +5636,7 @@ export class PartialViewService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -5655,7 +5659,7 @@ export class PartialViewService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -5681,7 +5685,7 @@ export class PartialViewService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -5707,7 +5711,7 @@ export class PartialViewService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -5729,7 +5733,7 @@ export class PartialViewService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -5750,7 +5754,7 @@ export class PartialViewService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -5773,7 +5777,7 @@ export class PartialViewService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -5796,7 +5800,7 @@ export class PartialViewService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -5816,7 +5820,7 @@ export class PartialViewService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -5837,7 +5841,7 @@ export class PartialViewService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -5861,7 +5865,7 @@ export class PartialViewService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -5883,7 +5887,7 @@ export class PartialViewService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -5931,7 +5935,7 @@ export class ProfilingService { url: '/umbraco/management/api/v1/profiling/status', errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -5951,7 +5955,7 @@ export class ProfilingService { responseHeader: 'Umb-Notifications', errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -5977,7 +5981,7 @@ export class PropertyTypeService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -6015,6 +6019,20 @@ export class PublishedCacheService { }); } + /** + * @returns unknown OK + * @throws ApiError + */ + public static getPublishedCacheRebuildStatus(): CancelablePromise<GetPublishedCacheRebuildStatusResponse> { + return __request(OpenAPI, { + method: 'GET', + url: '/umbraco/management/api/v1/published-cache/rebuild/status', + errors: { + 401: 'The resource is protected and requires an authentication token' + } + }); + } + /** * @returns string OK * @throws ApiError @@ -6068,7 +6086,7 @@ export class RedirectManagementService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -6094,7 +6112,7 @@ export class RedirectManagementService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -6115,7 +6133,7 @@ export class RedirectManagementService { responseHeader: 'Umb-Notifications', errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -6130,7 +6148,7 @@ export class RedirectManagementService { url: '/umbraco/management/api/v1/redirect-management/status', errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -6151,7 +6169,7 @@ export class RedirectManagementService { responseHeader: 'Umb-Notifications', errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -6180,7 +6198,7 @@ export class RelationService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -6225,7 +6243,7 @@ export class RelationTypeService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -6245,7 +6263,7 @@ export class RelationTypeService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -6289,7 +6307,7 @@ export class ScriptService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -6310,7 +6328,7 @@ export class ScriptService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -6333,7 +6351,7 @@ export class ScriptService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -6359,7 +6377,7 @@ export class ScriptService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -6385,7 +6403,7 @@ export class ScriptService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -6407,7 +6425,7 @@ export class ScriptService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -6428,7 +6446,7 @@ export class ScriptService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -6451,7 +6469,7 @@ export class ScriptService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -6472,7 +6490,7 @@ export class ScriptService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -6496,7 +6514,7 @@ export class ScriptService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -6518,7 +6536,7 @@ export class ScriptService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -6588,7 +6606,7 @@ export class SecurityService { url: '/umbraco/management/api/v1/security/configuration', errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -6609,7 +6627,7 @@ export class SecurityService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -6630,7 +6648,7 @@ export class SecurityService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -6676,7 +6694,7 @@ export class SegmentService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -6747,7 +6765,7 @@ export class ServerService { url: '/umbraco/management/api/v1/server/upgrade-check', errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -6875,7 +6893,7 @@ export class StylesheetService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -6896,7 +6914,7 @@ export class StylesheetService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -6919,7 +6937,7 @@ export class StylesheetService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -6945,7 +6963,7 @@ export class StylesheetService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -6971,7 +6989,7 @@ export class StylesheetService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -6993,7 +7011,7 @@ export class StylesheetService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -7014,7 +7032,7 @@ export class StylesheetService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -7037,7 +7055,7 @@ export class StylesheetService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -7058,7 +7076,7 @@ export class StylesheetService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -7082,7 +7100,7 @@ export class StylesheetService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -7104,7 +7122,7 @@ export class StylesheetService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -7159,7 +7177,7 @@ export class TelemetryService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -7174,7 +7192,7 @@ export class TelemetryService { url: '/umbraco/management/api/v1/telemetry/level', errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -7195,7 +7213,7 @@ export class TelemetryService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -7261,7 +7279,7 @@ export class TemplateService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -7282,7 +7300,7 @@ export class TemplateService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -7305,7 +7323,7 @@ export class TemplateService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -7331,7 +7349,7 @@ export class TemplateService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -7347,7 +7365,7 @@ export class TemplateService { url: '/umbraco/management/api/v1/template/configuration', errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -7366,7 +7384,7 @@ export class TemplateService { mediaType: 'application/json', errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -7381,7 +7399,7 @@ export class TemplateService { url: '/umbraco/management/api/v1/template/query/settings', errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -7401,7 +7419,7 @@ export class TemplateService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -7425,7 +7443,7 @@ export class TemplateService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -7447,7 +7465,7 @@ export class TemplateService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -7546,7 +7564,7 @@ export class UpgradeService { responseHeader: 'Umb-Notifications', errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 428: 'Precondition Required', 500: 'Internal Server Error' } @@ -7563,7 +7581,7 @@ export class UpgradeService { url: '/umbraco/management/api/v1/upgrade/settings', errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 428: 'Precondition Required' } }); @@ -7600,7 +7618,7 @@ export class UserService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -7641,7 +7659,7 @@ export class UserService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -7663,7 +7681,7 @@ export class UserService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -7685,7 +7703,7 @@ export class UserService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -7706,7 +7724,7 @@ export class UserService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -7729,7 +7747,7 @@ export class UserService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -7755,7 +7773,7 @@ export class UserService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -7776,7 +7794,7 @@ export class UserService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -7801,7 +7819,7 @@ export class UserService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -7822,7 +7840,7 @@ export class UserService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -7848,7 +7866,7 @@ export class UserService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -7874,7 +7892,7 @@ export class UserService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -7894,7 +7912,7 @@ export class UserService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -7918,7 +7936,7 @@ export class UserService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -7939,7 +7957,7 @@ export class UserService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -7962,7 +7980,7 @@ export class UserService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -7988,7 +8006,7 @@ export class UserService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -8004,7 +8022,7 @@ export class UserService { url: '/umbraco/management/api/v1/user/configuration', errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -8158,7 +8176,7 @@ export class UserService { url: '/umbraco/management/api/v1/user/current/configuration', errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -8253,7 +8271,7 @@ export class UserService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -8275,7 +8293,7 @@ export class UserService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -8297,7 +8315,7 @@ export class UserService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -8318,7 +8336,7 @@ export class UserService { responseHeader: 'Umb-Notifications', errors: { 400: 'Bad Request', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -8340,7 +8358,7 @@ export class UserService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -8360,7 +8378,7 @@ export class UserService { mediaType: 'application/json', errors: { 400: 'Bad Request', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -8381,7 +8399,7 @@ export class UserService { responseHeader: 'Umb-Notifications', errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -8402,7 +8420,7 @@ export class UserService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -8520,7 +8538,7 @@ export class UserGroupService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -8560,7 +8578,7 @@ export class UserGroupService { responseHeader: 'Umb-Notifications', errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -8582,7 +8600,7 @@ export class UserGroupService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -8604,7 +8622,7 @@ export class UserGroupService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -8624,7 +8642,7 @@ export class UserGroupService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -8646,7 +8664,7 @@ export class UserGroupService { responseHeader: 'Umb-Notifications', errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -8671,7 +8689,7 @@ export class UserGroupService { responseHeader: 'Umb-Notifications', errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -8696,7 +8714,7 @@ export class UserGroupService { responseHeader: 'Umb-Notifications', errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -8721,7 +8739,7 @@ export class UserGroupService { responseHeader: 'Umb-Notifications', errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -8766,7 +8784,7 @@ export class WebhookService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } @@ -8787,7 +8805,7 @@ export class WebhookService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -8808,7 +8826,7 @@ export class WebhookService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -8831,7 +8849,7 @@ export class WebhookService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -8857,7 +8875,7 @@ export class WebhookService { errors: { 400: 'Bad Request', 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource', + 403: 'The authenticated user does not have access to this resource', 404: 'Not Found' } }); @@ -8905,7 +8923,7 @@ export class WebhookService { }, errors: { 401: 'The resource is protected and requires an authentication token', - 403: 'The authenticated user do not have access to this resource' + 403: 'The authenticated user does not have access to this resource' } }); } diff --git a/src/Umbraco.Web.UI.Client/src/external/backend-api/src/types.gen.ts b/src/Umbraco.Web.UI.Client/src/external/backend-api/src/types.gen.ts index 24fb93da0d8f..5e831e2da328 100644 --- a/src/Umbraco.Web.UI.Client/src/external/backend-api/src/types.gen.ts +++ b/src/Umbraco.Web.UI.Client/src/external/backend-api/src/types.gen.ts @@ -2050,7 +2050,6 @@ export type PublishDocumentRequestModel = { export type PublishDocumentWithDescendantsRequestModel = { includeUnpublishedDescendants: boolean; - forceRepublish: boolean; cultures: Array<(string)>; }; @@ -2064,6 +2063,10 @@ export type PublishedDocumentResponseModel = { isTrashed: boolean; }; +export type RebuildStatusModel = { + isRebuilding: boolean; +}; + export enum RedirectStatusModel { ENABLED = 'Enabled', DISABLED = 'Disabled' @@ -3536,6 +3539,7 @@ export type PutDocumentTypeByIdResponse = (string); export type GetDocumentTypeByIdAllowedChildrenData = { id: string; + parentContentKey?: string; skip?: number; take?: number; }; @@ -4141,6 +4145,7 @@ export type PutMediaTypeByIdResponse = (string); export type GetMediaTypeByIdAllowedChildrenData = { id: string; + parentContentKey?: string; skip?: number; take?: number; }; @@ -4614,6 +4619,8 @@ export type GetPropertyTypeIsUsedResponse = (boolean); export type PostPublishedCacheRebuildResponse = (string); +export type GetPublishedCacheRebuildStatusResponse = ((RebuildStatusModel)); + export type PostPublishedCacheReloadResponse = (string); export type GetRedirectManagementData = { diff --git a/src/Umbraco.Web.UI.Client/src/external/monaco-editor/index.ts b/src/Umbraco.Web.UI.Client/src/external/monaco-editor/index.ts index 4397dee0f96c..c4dbacf95f1b 100644 --- a/src/Umbraco.Web.UI.Client/src/external/monaco-editor/index.ts +++ b/src/Umbraco.Web.UI.Client/src/external/monaco-editor/index.ts @@ -6,18 +6,18 @@ import styles from 'monaco-editor/min/vs/editor/editor.main.css?inline'; const initializeWorkers = () => { self.MonacoEnvironment = { getWorker(workerId: string, label: string): Promise<Worker> | Worker { - let url = '/umbraco/backoffice/monaco-editor/esm/vs/editor/editor.worker.js'; + let url = '/umbraco/backoffice/monaco-editor/vs/editor/editor.worker.js'; if (label === 'json') { - url = '/umbraco/backoffice/monaco-editor/esm/vs/language/json/json.worker.js'; + url = '/umbraco/backoffice/monaco-editor/vs/language/json/json.worker.js'; } if (label === 'css' || label === 'scss' || label === 'less') { - url = '/umbraco/backoffice/monaco-editor/esm/vs/language/css/css.worker.js'; + url = '/umbraco/backoffice/monaco-editor/vs/language/css/css.worker.js'; } if (label === 'html' || label === 'handlebars' || label === 'razor') { - url = '/umbraco/backoffice/monaco-editor/esm/vs/language/html/html.worker.js'; + url = '/umbraco/backoffice/monaco-editor/vs/language/html/html.worker.js'; } if (label === 'typescript' || label === 'javascript') { - url = '/umbraco/backoffice/monaco-editor/esm/vs/language/typescript/ts.worker.js'; + url = '/umbraco/backoffice/monaco-editor/vs/language/typescript/ts.worker.js'; } return new Worker(url, { name: workerId, type: 'module' }); }, diff --git a/src/Umbraco.Web.UI.Client/src/external/tinymce/index.ts b/src/Umbraco.Web.UI.Client/src/external/tinymce/index.ts index 3d2e9a2f41b1..3d2a1e8fd61a 100644 --- a/src/Umbraco.Web.UI.Client/src/external/tinymce/index.ts +++ b/src/Umbraco.Web.UI.Client/src/external/tinymce/index.ts @@ -25,6 +25,9 @@ export function renderEditor(userConfig?: RawEditorOptions) { // Declare a global variable to hold the TinyMCE instance declare global { interface Window { + /** + * @TJS-ignore + */ tinymce: TinyMCE; } } diff --git a/src/Umbraco.Web.UI.Client/src/external/tiptap/extensions/tiptap-anchor.extension.ts b/src/Umbraco.Web.UI.Client/src/external/tiptap/extensions/tiptap-anchor.extension.ts new file mode 100644 index 000000000000..0638768517f5 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/external/tiptap/extensions/tiptap-anchor.extension.ts @@ -0,0 +1,54 @@ +import { Node, mergeAttributes } from '@tiptap/core'; + +export const Anchor = Node.create({ + name: 'anchor', + + atom: true, + draggable: true, + inline: true, + group: 'inline', + marks: '', + selectable: true, + + addAttributes() { + return { + id: {}, + }; + }, + + addNodeView() { + return ({ HTMLAttributes }) => { + const dom = document.createElement('span'); + dom.setAttribute('data-umb-anchor', ''); + dom.setAttribute('title', HTMLAttributes.id); + + const icon = document.createElement('uui-icon'); + icon.setAttribute('name', 'icon-anchor'); + + dom.appendChild(icon); + + return { dom }; + }; + }, + + addOptions() { + return { + HTMLAttributes: { + id: 'id', + }, + }; + }, + + parseHTML() { + return [ + { + tag: 'a[id]', + getAttrs: (element) => (element.innerHTML === '' ? {} : false), + }, + ]; + }, + + renderHTML({ HTMLAttributes }) { + return ['a', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes)]; + }, +}); diff --git a/src/Umbraco.Web.UI.Client/src/external/tiptap/index.ts b/src/Umbraco.Web.UI.Client/src/external/tiptap/index.ts index f85780ae9dc7..af5bf8ba0908 100644 --- a/src/Umbraco.Web.UI.Client/src/external/tiptap/index.ts +++ b/src/Umbraco.Web.UI.Client/src/external/tiptap/index.ts @@ -28,6 +28,7 @@ export { TextAlign } from '@tiptap/extension-text-align'; export { Underline } from '@tiptap/extension-underline'; // CUSTOM EXTENSIONS +export * from './extensions/tiptap-anchor.extension.js'; export * from './extensions/tiptap-div.extension.js'; export * from './extensions/tiptap-figcaption.extension.js'; export * from './extensions/tiptap-figure.extension.js'; diff --git a/src/Umbraco.Web.UI.Client/src/json-schema/all-packages.ts b/src/Umbraco.Web.UI.Client/src/json-schema/all-packages.ts deleted file mode 100644 index 2f8a997a00ed..000000000000 --- a/src/Umbraco.Web.UI.Client/src/json-schema/all-packages.ts +++ /dev/null @@ -1,117 +0,0 @@ -import '@umbraco-cms/backoffice/app'; -import '@umbraco-cms/backoffice/class-api'; -import '@umbraco-cms/backoffice/context-api'; -import '@umbraco-cms/backoffice/controller-api'; -import '@umbraco-cms/backoffice/element-api'; -import '@umbraco-cms/backoffice/embedded-media'; -import '@umbraco-cms/backoffice/extension-api'; -import '@umbraco-cms/backoffice/formatting-api'; -import '@umbraco-cms/backoffice/localization-api'; -import '@umbraco-cms/backoffice/observable-api'; -import '@umbraco-cms/backoffice/action'; -import '@umbraco-cms/backoffice/audit-log'; -import '@umbraco-cms/backoffice/auth'; -import '@umbraco-cms/backoffice/block-custom-view'; -import '@umbraco-cms/backoffice/block-grid'; -import '@umbraco-cms/backoffice/block-list'; -import '@umbraco-cms/backoffice/block-rte'; -import '@umbraco-cms/backoffice/block-type'; -import '@umbraco-cms/backoffice/block'; -import '@umbraco-cms/backoffice/code-editor'; -import '@umbraco-cms/backoffice/collection'; -import '@umbraco-cms/backoffice/components'; -import '@umbraco-cms/backoffice/content-type'; -import '@umbraco-cms/backoffice/content'; -import '@umbraco-cms/backoffice/culture'; -import '@umbraco-cms/backoffice/current-user'; -import '@umbraco-cms/backoffice/dashboard'; -import '@umbraco-cms/backoffice/data-type'; -import '@umbraco-cms/backoffice/debug'; -import '@umbraco-cms/backoffice/dictionary'; -import '@umbraco-cms/backoffice/document-blueprint'; -import '@umbraco-cms/backoffice/document-type'; -import '@umbraco-cms/backoffice/document'; -import '@umbraco-cms/backoffice/entity-action'; -import '@umbraco-cms/backoffice/entity-bulk-action'; -import '@umbraco-cms/backoffice/entity-create-option-action'; -import '@umbraco-cms/backoffice/entity'; -import '@umbraco-cms/backoffice/event'; -import '@umbraco-cms/backoffice/extension-registry'; -import '@umbraco-cms/backoffice/health-check'; -import '@umbraco-cms/backoffice/help'; -import '@umbraco-cms/backoffice/icon'; -import '@umbraco-cms/backoffice/id'; -import '@umbraco-cms/backoffice/imaging'; -import '@umbraco-cms/backoffice/language'; -import '@umbraco-cms/backoffice/lit-element'; -import '@umbraco-cms/backoffice/localization'; -import '@umbraco-cms/backoffice/log-viewer'; -import '@umbraco-cms/backoffice/markdown-editor'; -import '@umbraco-cms/backoffice/media-type'; -import '@umbraco-cms/backoffice/media'; -import '@umbraco-cms/backoffice/member-group'; -import '@umbraco-cms/backoffice/member-type'; -import '@umbraco-cms/backoffice/member'; -import '@umbraco-cms/backoffice/menu'; -import '@umbraco-cms/backoffice/modal'; -import '@umbraco-cms/backoffice/multi-url-picker'; -import '@umbraco-cms/backoffice/notification'; -import '@umbraco-cms/backoffice/object-type'; -import '@umbraco-cms/backoffice/package'; -import '@umbraco-cms/backoffice/partial-view'; -import '@umbraco-cms/backoffice/picker-input'; -import '@umbraco-cms/backoffice/picker'; -import '@umbraco-cms/backoffice/property-action'; -import '@umbraco-cms/backoffice/property-editor'; -import '@umbraco-cms/backoffice/property-type'; -import '@umbraco-cms/backoffice/property'; -import '@umbraco-cms/backoffice/recycle-bin'; -import '@umbraco-cms/backoffice/relation-type'; -import '@umbraco-cms/backoffice/relations'; -import '@umbraco-cms/backoffice/repository'; -import '@umbraco-cms/backoffice/resources'; -import '@umbraco-cms/backoffice/router'; -import '@umbraco-cms/backoffice/rte'; -import '@umbraco-cms/backoffice/script'; -import '@umbraco-cms/backoffice/search'; -import '@umbraco-cms/backoffice/section'; -import '@umbraco-cms/backoffice/server-file-system'; -import '@umbraco-cms/backoffice/settings'; -import '@umbraco-cms/backoffice/sorter'; -import '@umbraco-cms/backoffice/static-file'; -import '@umbraco-cms/backoffice/store'; -import '@umbraco-cms/backoffice/style'; -import '@umbraco-cms/backoffice/stylesheet'; -import '@umbraco-cms/backoffice/sysinfo'; -import '@umbraco-cms/backoffice/tags'; -import '@umbraco-cms/backoffice/template'; -import '@umbraco-cms/backoffice/temporary-file'; -import '@umbraco-cms/backoffice/themes'; -import '@umbraco-cms/backoffice/tiny-mce'; -import '@umbraco-cms/backoffice/tiptap'; -import '@umbraco-cms/backoffice/translation'; -import '@umbraco-cms/backoffice/tree'; -import '@umbraco-cms/backoffice/ufm'; -import '@umbraco-cms/backoffice/user-change-password'; -import '@umbraco-cms/backoffice/user-group'; -import '@umbraco-cms/backoffice/user-permission'; -import '@umbraco-cms/backoffice/user'; -import '@umbraco-cms/backoffice/utils'; -import '@umbraco-cms/backoffice/validation'; -import '@umbraco-cms/backoffice/variant'; -import '@umbraco-cms/backoffice/webhook'; -import '@umbraco-cms/backoffice/workspace'; -import '@umbraco-cms/backoffice/external/backend-api'; -import '@umbraco-cms/backoffice/external/base64-js'; -import '@umbraco-cms/backoffice/external/diff'; -import '@umbraco-cms/backoffice/external/dompurify'; -import '@umbraco-cms/backoffice/external/lit'; -import '@umbraco-cms/backoffice/external/marked'; -import '@umbraco-cms/backoffice/external/monaco-editor'; -import '@umbraco-cms/backoffice/external/openid'; -import '@umbraco-cms/backoffice/external/router-slot'; -import '@umbraco-cms/backoffice/external/rxjs'; -import '@umbraco-cms/backoffice/external/tinymce'; -import '@umbraco-cms/backoffice/external/tiptap'; -import '@umbraco-cms/backoffice/external/uui'; -import '@umbraco-cms/backoffice/external/uuid'; diff --git a/src/Umbraco.Web.UI.Client/src/json-schema/umbraco-package-schema.ts b/src/Umbraco.Web.UI.Client/src/json-schema/umbraco-package-schema.ts index 9bd0fa293082..fae9b07e6599 100644 --- a/src/Umbraco.Web.UI.Client/src/json-schema/umbraco-package-schema.ts +++ b/src/Umbraco.Web.UI.Client/src/json-schema/umbraco-package-schema.ts @@ -1,4 +1,4 @@ -import './all-packages.js'; +import '@umbraco-cms/backoffice/extension-registry'; /** * Umbraco package manifest JSON @@ -27,6 +27,13 @@ export interface UmbracoPackage { */ allowTelemetry?: boolean; + /** + * @title Decides if the package sends telemetry data for collection + * @default true + * @deprecated Use allowTelemetry instead + */ + allowPackageTelemetry?: boolean; + /** * @title Decides if the package is allowed to be accessed by the public, e.g. on the login screen * @default false diff --git a/src/Umbraco.Web.UI.Client/src/libs/extension-api/registry/extension.registry.ts b/src/Umbraco.Web.UI.Client/src/libs/extension-api/registry/extension.registry.ts index 15d828a62415..ead86489e7b6 100644 --- a/src/Umbraco.Web.UI.Client/src/libs/extension-api/registry/extension.registry.ts +++ b/src/Umbraco.Web.UI.Client/src/libs/extension-api/registry/extension.registry.ts @@ -11,8 +11,8 @@ import { map, distinctUntilChanged, combineLatest, of, switchMap } from '@umbrac /** * - * @param previousValue {Array<ManifestBase>} - previous value - * @param currentValue {Array<ManifestBase>} - current value + * @param {Array<ManifestBase>} previousValue - previous value + * @param {Array<ManifestBase>} currentValue - current value * @returns {boolean} - true if value is assumed to be the same as previous value. */ function extensionArrayMemoization<T extends Pick<ManifestBase, 'alias'>>( @@ -32,8 +32,8 @@ function extensionArrayMemoization<T extends Pick<ManifestBase, 'alias'>>( /** * - * @param previousValue {Array<ManifestBase>} - previous value - * @param currentValue {Array<ManifestBase>} - current value + * @param {Array<ManifestBase>} previousValue - previous value + * @param {Array<ManifestBase>} currentValue - current value * @returns {boolean} - true if value is assumed to be the same as previous value. */ function extensionAndKindMatchArrayMemoization< @@ -64,8 +64,8 @@ function extensionAndKindMatchArrayMemoization< /** * - * @param previousValue {Array<ManifestBase>} - previous value - * @param currentValue {Array<ManifestBase>} - current value + * @param {Array<ManifestBase>} previousValue - previous value + * @param {Array<ManifestBase>} currentValue - current value * @returns {boolean} - true if value is assumed to be the same as previous value. */ function extensionSingleMemoization<T extends Pick<ManifestBase, 'alias'>>( @@ -80,8 +80,8 @@ function extensionSingleMemoization<T extends Pick<ManifestBase, 'alias'>>( /** * - * @param previousValue {Array<ManifestBase>} - previous value - * @param currentValue {Array<ManifestBase>} - current value + * @param {Array<ManifestBase>} previousValue - previous value + * @param {Array<ManifestBase>} currentValue - current value * @returns {boolean} - true if value is assumed to be the same as previous value. */ function extensionAndKindMatchSingleMemoization< @@ -154,6 +154,11 @@ export class UmbExtensionRegistry< this._kinds.setValue(nextData); } + /** + * Exclude an extension from being available. + * Notice if you are looking to replace, then you can achieve such via the `overwrites` property in the manifest. + * @param {string} alias - The alias of the extension to exclude. + */ exclude(alias: string): void { this.#exclusions.push(alias); this._extensions.setValue(this._extensions.getValue().filter(this.#acceptExtension)); @@ -165,7 +170,7 @@ export class UmbExtensionRegistry< /** * Register an extension. - * @param {(ManifestTypes | ManifestKind<ManifestTypes>)} manifest - The extension to register. + * @param {(ManifestBase | ManifestKind<ManifestBase>)} manifest - The extension to register. * @memberof UmbExtensionRegistry */ register(manifest: ManifestTypes | ManifestKind<ManifestTypes>): void { @@ -192,7 +197,7 @@ export class UmbExtensionRegistry< /** * Get all registered extensions. - * @returns {Array<ManifestTypes>} - All registered extensions. + * @returns {Array<ManifestBase>} - All registered extensions. * @memberof UmbExtensionRegistry */ getAllExtensions(): Array<ManifestTypes> { @@ -201,7 +206,7 @@ export class UmbExtensionRegistry< /** * Register many extensions. - * @param {(Array<ManifestTypes | ManifestKind<ManifestTypes>>)} manifests - The extensions to register. + * @param {(Array<ManifestBase | ManifestKind<ManifestBase>>)} manifests - The extensions to register. * @memberof UmbExtensionRegistry */ registerMany(manifests: Array<ManifestTypes | ManifestKind<ManifestTypes>>): void { @@ -211,6 +216,7 @@ export class UmbExtensionRegistry< /** * Unregister many extensions with the given aliases. + * Notice it is more secure to exclude it, only unregister extensions that you provided. * @param {Array<string>} aliases - The aliases of the extensions to unregister. * @memberof UmbExtensionRegistry */ @@ -220,6 +226,7 @@ export class UmbExtensionRegistry< /** * Unregister an extension with the given alias. + * Notice it is more secure to exclude it, only unregister extensions that you provided. * @param {string} alias - The alias of the extension to unregister. * @memberof UmbExtensionRegistry */ @@ -310,7 +317,6 @@ export class UmbExtensionRegistry< ); } - // TODO: can we get rid of as unknown here #extensionsOfTypes<ExtensionType extends ManifestBase = ManifestBase>( types: Array<ExtensionType['type']>, ): Observable<Array<ExtensionType>> { @@ -362,8 +368,8 @@ export class UmbExtensionRegistry< /** * Get an observable that provides an extension matching the given alias. - * @param alias {string} - The alias of the extension to get. - * @returns {Observable<T | undefined>} - An observable of the extension that matches the alias. + * @param {string} alias - The alias of the extension to get. + * @returns {Observable<ManifestBase | undefined>} - An observable of the extension that matches the alias. */ byAlias<T extends ManifestBase = ManifestBase>(alias: string): Observable<T | undefined> { return this.extensions.pipe( @@ -382,8 +388,8 @@ export class UmbExtensionRegistry< /** * Get an extension that matches the given alias, this will not return an observable, it is a one of retrieval if it exists at the given point in time. - * @param alias {string} - The alias of the extension to get. - * @returns {<T | undefined>} - The extension manifest that matches the alias. + * @param {string} alias - The alias of the extension to get. + * @returns {ManifestBase | undefined} - The extension manifest that matches the alias. */ getByAlias<T extends ManifestBase = ManifestBase>(alias: string): T | undefined { const ext = this._extensions.getValue().find((ext) => ext.alias === alias) as T | undefined; @@ -395,9 +401,9 @@ export class UmbExtensionRegistry< /** * Get an observable that provides extensions matching the given type and alias. - * @param type {string} - The type of the extensions to get. - * @param alias {string} - The alias of the extensions to get. - * @returns {Observable<T | undefined>} - An observable of the extensions that matches the type and alias. + * @param {string} type - The type of the extensions to get. + * @param {string} alias - The alias of the extensions to get. + * @returns {Observable<ManifestBase | undefined>} - An observable of the extensions that matches the type and alias. */ byTypeAndAlias<Key extends string, T extends ManifestBase = SpecificManifestTypeOrManifestBase<ManifestTypes, Key>>( type: Key, @@ -415,6 +421,12 @@ export class UmbExtensionRegistry< ) as Observable<T | undefined>; } + /** + * Get an observable that provides extensions matching the given type and alias. + * @param {string} type - The type of the extensions to get. + * @param {Array<string>} aliases - The aliases of the extensions to get. + * @returns {Observable<ManifestBase | undefined>} - An observable of the extensions that matches the type and one of the aliases. + */ byTypeAndAliases<Key extends string, T extends ManifestBase = SpecificManifestTypeOrManifestBase<ManifestTypes, Key>>( type: Key, aliases: Array<string>, @@ -435,9 +447,9 @@ export class UmbExtensionRegistry< * Get an observable of extensions by type and a given filter method. * This will return the all extensions that matches the type and which filter method returns true. * The filter method will be called for each extension manifest of the given type, and the first argument to it is the extension manifest. - * @param type {string} - The type of the extension to get - * @param filter {(ext: T): void} - The filter method to use to filter the extensions - * @returns {Observable<Array<T>>} - An observable of the extensions that matches the type and filter method + * @param {string} type - The type of the extension to get. + * @param {(ext: ManifestBase) => boolean} filter - The filter method to use to filter the extensions. + * @returns {Observable<Array<ManifestBase>>} - An observable of the extensions that matches the type and filter method. */ byTypeAndFilter<Key extends string, T extends ManifestBase = SpecificManifestTypeOrManifestBase<ManifestTypes, Key>>( type: Key, @@ -455,7 +467,14 @@ export class UmbExtensionRegistry< ) as Observable<Array<T>>; } - // TODO: Write test for this method: + /** + * Get an extensions by type and a given filter method. + * This will return the all extensions that matches the type and which filter method returns true. + * The filter method will be called for each extension manifest of the given type, and the first argument to it is the extension manifest. + * @param {string} type - The type of the extension to get. + * @param {(ext: ManifestBase) => boolean} filter - The filter method to use to filter the extensions. + * @returns {Observable<Array<ManifestBase>>} - An observable of the extensions that matches the type and filter method. + */ getByTypeAndFilter< Key extends string, T extends ManifestBase = SpecificManifestTypeOrManifestBase<ManifestTypes, Key>, @@ -476,9 +495,9 @@ export class UmbExtensionRegistry< * Get an observable of extensions by types and a given filter method. * This will return the all extensions that matches the types and which filter method returns true. * The filter method will be called for each extension manifest of the given types, and the first argument to it is the extension manifest. - * @param types {Array<string>} - The types of the extensions to get. - * @param filter {(ext: T): void} - The filter method to use to filter the extensions - * @returns {Observable<Array<T>>} - An observable of the extensions that matches the type and filter method + * @param {Array<string>} types - The types of the extensions to get. + * @param {(ext: ManifestBase) => boolean} filter - The filter method to use to filter the extensions + * @returns {Observable<Array<ManifestBase>>} - An observable of the extensions that matches the type and filter method */ byTypesAndFilter<ExtensionTypes extends ManifestBase = ManifestBase>( types: string[], @@ -503,8 +522,8 @@ export class UmbExtensionRegistry< /** * Get an observable that provides extensions matching the given type. - * @param type {string} - The type of the extensions to get. - * @returns {Observable<T | undefined>} - An observable of the extensions that matches the type. + * @param {string} type - The type of the extensions to get. + * @returns {Observable<ManifestBase | undefined>} - An observable of the extensions that matches the type. */ byType<Key extends string, T extends ManifestBase = SpecificManifestTypeOrManifestBase<ManifestTypes, Key>>( type: Key, @@ -517,8 +536,8 @@ export class UmbExtensionRegistry< /** * Get an observable that provides extensions matching given types. - * @param types {Array<string>} - The types of the extensions to get. - * @returns {Observable<T | undefined>} - An observable of the extensions that matches the types. + * @param {Array<string>} types - The types of the extensions to get. + * @returns {Observable<ManifestBase | undefined>} - An observable of the extensions that matches the types. */ byTypes<ExtensionTypes extends ManifestBase = ManifestBase>(types: string[]): Observable<Array<ExtensionTypes>> { return combineLatest([this.#extensionsOfTypes<ExtensionTypes>(types), this.#kindsOfTypes(types)]).pipe( diff --git a/src/Umbraco.Web.UI.Client/src/libs/formatting-api/formatting.controller.ts b/src/Umbraco.Web.UI.Client/src/libs/formatting-api/formatting.controller.ts deleted file mode 100644 index 828f9d5712a8..000000000000 --- a/src/Umbraco.Web.UI.Client/src/libs/formatting-api/formatting.controller.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { DOMPurify, type Config } from '@umbraco-cms/backoffice/external/dompurify'; -import { Marked } from '@umbraco-cms/backoffice/external/marked'; -import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api'; -import { UmbLocalizationController } from '@umbraco-cms/backoffice/localization-api'; - -const UmbMarked = new Marked({ gfm: true, breaks: true }); -const UmbDomPurify = DOMPurify(window); -const UmbDomPurifyConfig: Config = { USE_PROFILES: { html: true } }; - -UmbDomPurify.addHook('afterSanitizeAttributes', function (node) { - // set all elements owning target to target=_blank - if ('target' in node && node instanceof HTMLElement) { - node.setAttribute('target', '_blank'); - } -}); - -/** - * @description - Controller for formatting text. - * @deprecated - Use the `<umb-ufm-render>` component instead. This method will be removed in Umbraco 15. - */ -export class UmbFormattingController extends UmbControllerBase { - #localize = new UmbLocalizationController(this._host); - - /** - * A method to localize the string input then transform any markdown to santized HTML. - * @param input - * @deprecated - Use the `<umb-ufm-render>` component instead. This method will be removed in Umbraco 15. - */ - public transform(input?: string): string { - if (!input) return ''; - const translated = this.#localize.string(input); - const markup = UmbMarked.parse(translated) as string; - const sanitized = UmbDomPurify.sanitize(markup, UmbDomPurifyConfig) as string; - return sanitized; - } -} diff --git a/src/Umbraco.Web.UI.Client/src/libs/formatting-api/index.ts b/src/Umbraco.Web.UI.Client/src/libs/formatting-api/index.ts deleted file mode 100644 index a11870de1f82..000000000000 --- a/src/Umbraco.Web.UI.Client/src/libs/formatting-api/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './formatting.controller.js'; -export * from './localizeAndTransform.function.js'; diff --git a/src/Umbraco.Web.UI.Client/src/libs/formatting-api/localizeAndTransform.function.ts b/src/Umbraco.Web.UI.Client/src/libs/formatting-api/localizeAndTransform.function.ts deleted file mode 100644 index 157b6f3e3044..000000000000 --- a/src/Umbraco.Web.UI.Client/src/libs/formatting-api/localizeAndTransform.function.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { UmbFormattingController } from './formatting.controller.js'; -import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; - -/** - * @param host - * @param input - * @deprecated - Use the `<umb-ufm-render>` component instead. This method will be removed in Umbraco 15. - */ -export function localizeAndTransform(host: UmbControllerHost, input: string): string { - return new UmbFormattingController(host).transform(input); -} diff --git a/src/Umbraco.Web.UI.Client/src/mocks/data/data-type/data-type.data.ts b/src/Umbraco.Web.UI.Client/src/mocks/data/data-type/data-type.data.ts index 2b858b9d2339..79ef5e95d987 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/data/data-type/data-type.data.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/data/data-type/data-type.data.ts @@ -1063,14 +1063,19 @@ export const data: Array<UmbMockDataTypeModel> = [ 'Umb.Tiptap.Toolbar.TextAlignCenter', 'Umb.Tiptap.Toolbar.TextAlignRight', ], - ['Umb.Tiptap.Toolbar.TextDirectionRtl', 'Umb.Tiptap.Toolbar.TextDirectionLtr'], + ['Umb.Tiptap.Toolbar.Subscript', 'Umb.Tiptap.Toolbar.Superscript'], + [ + 'Umb.Tiptap.Toolbar.CharacterMap', + 'Umb.Tiptap.Toolbar.TextDirectionRtl', + 'Umb.Tiptap.Toolbar.TextDirectionLtr', + ], [ 'Umb.Tiptap.Toolbar.BulletList', 'Umb.Tiptap.Toolbar.OrderedList', 'Umb.Tiptap.Toolbar.Blockquote', 'Umb.Tiptap.Toolbar.HorizontalRule', ], - ['Umb.Tiptap.Toolbar.Link', 'Umb.Tiptap.Toolbar.Unlink'], + ['Umb.Tiptap.Toolbar.Anchor', 'Umb.Tiptap.Toolbar.Link', 'Umb.Tiptap.Toolbar.Unlink'], ['Umb.Tiptap.Toolbar.Table', 'Umb.Tiptap.Toolbar.MediaPicker', 'Umb.Tiptap.Toolbar.EmbeddedMedia'], ], ], diff --git a/src/Umbraco.Web.UI.Client/src/mocks/data/document/document.data.ts b/src/Umbraco.Web.UI.Client/src/mocks/data/document/document.data.ts index 98ea26928fed..45be5334cf62 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/data/document/document.data.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/data/document/document.data.ts @@ -911,10 +911,10 @@ export const data: Array<UmbMockDocumentModel> = [ value: { blocks: undefined, markup: ` + <p><a id="anchor"></a> Here is a link for <a href="https://gist.github.com/leekelleher/9490718" target="_blank">all HTML tags</a>.</p> <p> <span id="foo">Some</span> value for the RTE with an <a href="https://google.com">external link</a> and an <a type="document" href="/{localLink:c05da24d-7740-447b-9cdc-bd8ce2172e38}">internal link</a>. </p> - <p><a href="https://gist.github.com/leekelleher/9490718" target="_blank">All HTML tags</a></p> <div data-foo-bar="123"> <span>This is a plain old span tag.</span> <span style="color:red;">Hello <span style="color:blue;">world</span>.</span> diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/clipboard/block/paste/block-to-block-grid-paste-translator.test.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/clipboard/block/paste/block-to-block-grid-paste-translator.test.ts index 61445c9ab81e..571720619f55 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/clipboard/block/paste/block-to-block-grid-paste-translator.test.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/clipboard/block/paste/block-to-block-grid-paste-translator.test.ts @@ -5,6 +5,7 @@ import { UMB_BLOCK_GRID_PROPERTY_EDITOR_SCHEMA_ALIAS } from '../../../property-e import type { UmbBlockGridValueModel } from '../../../types.js'; import { UmbBlockToBlockGridClipboardPastePropertyValueTranslator } from './block-to-block-grid-paste-translator.js'; import type { UmbBlockClipboardEntryValueModel } from '@umbraco-cms/backoffice/block'; +import type { UmbBlockGridPropertyEditorConfig } from '../../../property-editors/block-grid-editor/types.js'; @customElement('test-controller-host') class UmbTestControllerHostElement extends UmbControllerHostElementMixin(HTMLElement) {} @@ -55,22 +56,26 @@ describe('UmbBlockToBlockGridClipboardPastePropertyValueTranslator', () => { settingsData: blockGridPropertyValue.settingsData, }; - const config1: Array<{ alias: string; value: [{ contentElementTypeKey: string }] }> = [ + const config1: UmbBlockGridPropertyEditorConfig = [ { alias: 'blocks', value: [ { + allowAtRoot: true, + allowInAreas: true, contentElementTypeKey: 'contentTypeKey', }, ], }, ]; - const config2: Array<{ alias: string; value: [{ contentElementTypeKey: string }] }> = [ + const config2: UmbBlockGridPropertyEditorConfig = [ { alias: 'blocks', value: [ { + allowAtRoot: true, + allowInAreas: true, contentElementTypeKey: 'contentTypeKey2', }, ], @@ -101,12 +106,12 @@ describe('UmbBlockToBlockGridClipboardPastePropertyValueTranslator', () => { describe('isCompatibleValue', () => { it('returns true if the value is compatible', async () => { - const result = await copyTranslator.isCompatibleValue(blockClipboardEntryValue, config1); + const result = await copyTranslator.isCompatibleValue(blockGridPropertyValue, config1); expect(result).to.be.true; }); it('returns false if the value is not compatible', async () => { - const result = await copyTranslator.isCompatibleValue(blockClipboardEntryValue, config2); + const result = await copyTranslator.isCompatibleValue(blockGridPropertyValue, config2); expect(result).to.be.false; }); }); diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/clipboard/block/paste/block-to-block-grid-paste-translator.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/clipboard/block/paste/block-to-block-grid-paste-translator.ts index 1f7cc20ba85b..67081389ce3f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/clipboard/block/paste/block-to-block-grid-paste-translator.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/clipboard/block/paste/block-to-block-grid-paste-translator.ts @@ -1,5 +1,6 @@ import type { UmbBlockGridLayoutModel, UmbBlockGridValueModel } from '../../../types.js'; import { UMB_BLOCK_GRID_PROPERTY_EDITOR_SCHEMA_ALIAS } from '../../../constants.js'; +import type { UmbBlockGridPropertyEditorConfig } from '../../../property-editors/block-grid-editor/types.js'; import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api'; import type { UmbClipboardPastePropertyValueTranslator } from '@umbraco-cms/backoffice/clipboard'; import type { UmbBlockClipboardEntryValueModel, UmbBlockLayoutBaseModel } from '@umbraco-cms/backoffice/block'; @@ -44,19 +45,18 @@ export class UmbBlockToBlockGridClipboardPastePropertyValueTranslator /** * Determines if a block clipboard entry value is compatible with the Block Grid property editor. - * @param {UmbBlockClipboardEntryValueModel} value The block clipboard entry value. - * @param {*} config The Block Grid property editor configuration. + * @param {UmbBlockClipboardEntryValueModel} propertyValue The block clipboard entry value. + * @param {UmbBlockGridPropertyEditorConfig} config The Block Grid property editor configuration. * @returns {Promise<boolean>} A promise that resolves with a boolean indicating if the value is compatible. * @memberof UmbBlockToBlockGridClipboardPastePropertyValueTranslator */ async isCompatibleValue( - value: UmbBlockClipboardEntryValueModel, - // TODO: Replace any with the correct type. - config: Array<{ alias: string; value: [{ contentElementTypeKey: string }] }>, + propertyValue: UmbBlockGridValueModel, + config: UmbBlockGridPropertyEditorConfig, ): Promise<boolean> { const allowedBlockContentTypes = config.find((c) => c.alias === 'blocks')?.value.map((b) => b.contentElementTypeKey) ?? []; - const blockContentTypes = value.contentData.map((c) => c.contentTypeKey); + const blockContentTypes = propertyValue.contentData.map((c) => c.contentTypeKey); return blockContentTypes?.every((b) => allowedBlockContentTypes.includes(b)) ?? false; } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/clipboard/grid-block/paste/grid-block-to-block-grid-paste-translator.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/clipboard/grid-block/paste/grid-block-to-block-grid-paste-translator.ts index 150d71484334..2b3a2a0e089e 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/clipboard/grid-block/paste/grid-block-to-block-grid-paste-translator.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/clipboard/grid-block/paste/grid-block-to-block-grid-paste-translator.ts @@ -1,3 +1,4 @@ +import type { UmbBlockGridPropertyEditorConfig } from '../../../property-editors/block-grid-editor/types.js'; import { UMB_BLOCK_GRID_PROPERTY_EDITOR_SCHEMA_ALIAS } from '../../../property-editors/constants.js'; import type { UmbBlockGridValueModel } from '../../../types.js'; import type { UmbGridBlockClipboardEntryValueModel } from '../../types.js'; @@ -35,20 +36,22 @@ export class UmbGridBlockToBlockGridClipboardPastePropertyValueTranslator /** * Checks if the clipboard entry value is compatible with the config. - * @param {UmbGridBlockClipboardEntryValueModel} value - The grid block clipboard entry value. + * @param {UmbGridBlockClipboardEntryValueModel} propertyValue - The grid block clipboard entry value. * @param {*} config - The Property Editor config. + * @param {(value, config) => Promise<boolean>} filter - The filter function. * @returns {Promise<boolean>} {Promise<boolean>} * @memberof UmbGridBlockToBlockGridClipboardPastePropertyValueTranslator */ async isCompatibleValue( - value: UmbGridBlockClipboardEntryValueModel, - // TODO: Replace any with the correct type. - config: Array<{ alias: string; value: [{ contentElementTypeKey: string }] }>, + propertyValue: UmbBlockGridValueModel, + config: UmbBlockGridPropertyEditorConfig, + filter?: (propertyValue: UmbBlockGridValueModel, config: UmbBlockGridPropertyEditorConfig) => Promise<boolean>, ): Promise<boolean> { - const allowedBlockContentTypes = - config.find((c) => c.alias === 'blocks')?.value.map((b) => b.contentElementTypeKey) ?? []; - const blockContentTypes = value.contentData.map((c) => c.contentTypeKey); - return blockContentTypes?.every((b) => allowedBlockContentTypes.includes(b)) ?? false; + const blocksConfig = config.find((c) => c.alias === 'blocks'); + const allowedBlockContentTypes = blocksConfig?.value.map((b) => b.contentElementTypeKey) ?? []; + const blockContentTypes = propertyValue.contentData.map((c) => c.contentTypeKey); + const allContentTypesAllowed = blockContentTypes?.every((b) => allowedBlockContentTypes.includes(b)) ?? false; + return allContentTypesAllowed && (!filter || (await filter(propertyValue, config))); } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/clipboard/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/clipboard/manifests.ts index 6546d9da1158..7fdabef3752d 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/clipboard/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/clipboard/manifests.ts @@ -1,6 +1,7 @@ import { UMB_BLOCK_GRID_PROPERTY_EDITOR_UI_ALIAS } from '../property-editors/constants.js'; import { manifests as blockManifests } from './block/manifests.js'; import { manifests as gridBlockManifests } from './grid-block/manifests.js'; +import type { UmbExtensionManifestKind } from '@umbraco-cms/backoffice/extension-registry'; import { UMB_PROPERTY_HAS_VALUE_CONDITION_ALIAS, UMB_WRITABLE_PROPERTY_CONDITION_ALIAS, @@ -8,7 +9,7 @@ import { const forPropertyEditorUis = [UMB_BLOCK_GRID_PROPERTY_EDITOR_UI_ALIAS]; -export const manifests: Array<UmbExtensionManifest> = [ +export const manifests: Array<UmbExtensionManifest | UmbExtensionManifestKind> = [ { type: 'propertyContext', kind: 'clipboard', @@ -31,18 +32,6 @@ export const manifests: Array<UmbExtensionManifest> = [ }, ], }, - { - type: 'propertyAction', - kind: 'pasteFromClipboard', - alias: 'Umb.PropertyAction.BlockGrid.Clipboard.Paste', - name: 'Block Grid Paste From Clipboard Property Action', - forPropertyEditorUis, - conditions: [ - { - alias: UMB_WRITABLE_PROPERTY_CONDITION_ALIAS, - }, - ], - }, ...blockManifests, ...gridBlockManifests, ]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entry/block-grid-entry.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entry/block-grid-entry.element.ts index 008acd78ca2d..80807a87b155 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entry/block-grid-entry.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entry/block-grid-entry.element.ts @@ -446,7 +446,7 @@ export class UmbBlockGridEntryElement extends UmbLitElement implements UmbProper }; override render() { - return this.contentKey + return this.contentKey && (this._contentTypeAlias || this._unsupported) ? html` ${this.#renderCreateBeforeInlineButton()} <div class="umb-block-grid__block" part="umb-block-grid__block"> diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/context/block-grid-entries.context.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/context/block-grid-entries.context.ts index bb1fe4cc3702..e410f894d07e 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/context/block-grid-entries.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/context/block-grid-entries.context.ts @@ -14,6 +14,7 @@ import type { UmbBlockGridValueModel, } from '../types.js'; import { forEachBlockLayoutEntryOf } from '../utils/index.js'; +import type { UmbBlockGridPropertyEditorConfig } from '../property-editors/block-grid-editor/types.js'; import { UMB_BLOCK_GRID_MANAGER_CONTEXT } from './block-grid-manager.context-token.js'; import type { UmbBlockGridScalableContainerContext } from './block-grid-scale-manager/block-grid-scale-manager.controller.js'; import { @@ -170,7 +171,7 @@ export class UmbBlockGridEntriesContext // TODO: consider moving some of this logic to the clipboard property context const propertyContext = await this.getContext(UMB_PROPERTY_CONTEXT); - const config = propertyContext.getConfig(); + const config = propertyContext.getConfig() as UmbBlockGridPropertyEditorConfig; const valueResolver = new UmbClipboardPastePropertyValueTranslatorValueResolver(this); return { @@ -198,7 +199,8 @@ export class UmbBlockGridEntriesContext clipboardEntryDetail.values, UMB_BLOCK_GRID_PROPERTY_EDITOR_UI_ALIAS, ); - return pasteTranslator.isCompatibleValue(value, config); + + return pasteTranslator.isCompatibleValue(value, config, (value) => this.#clipboardEntriesFilter(value)); } return true; @@ -269,6 +271,21 @@ export class UmbBlockGridEntriesContext }); } + async #clipboardEntriesFilter(propertyValue: UmbBlockGridValueModel) { + const allowedElementTypeKeys = this.#retrieveAllowedElementTypes().map((x) => x.contentElementTypeKey); + + const rootContentKeys = propertyValue.layout['Umbraco.BlockGrid']?.map((block) => block.contentKey) ?? []; + const rootContentTypeKeys = propertyValue.contentData + .filter((content) => rootContentKeys.includes(content.key)) + .map((content) => content.contentTypeKey); + + const allContentTypesAllowed = rootContentTypeKeys.every((contentKey) => + allowedElementTypeKeys.includes(contentKey), + ); + + return allContentTypesAllowed; + } + protected _gotBlockManager() { if (!this._manager) return; diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/manifests.ts index 209e3eef670e..5b760bd1909b 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/manifests.ts @@ -3,8 +3,9 @@ import { manifests as componentManifests } from './components/manifests.js'; import { manifests as propertyEditorManifests } from './property-editors/manifests.js'; import { manifests as propertyValueClonerManifests } from './property-value-cloner/manifests.js'; import { manifests as workspaceManifests } from './workspace/manifests.js'; +import type { UmbExtensionManifestKind } from '@umbraco-cms/backoffice/extension-registry'; -export const manifests: Array<UmbExtensionManifest> = [ +export const manifests: Array<UmbExtensionManifest | UmbExtensionManifestKind> = [ ...clipboardManifests, ...componentManifests, ...propertyEditorManifests, diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-area-type-permission/block-grid-area-type-permission.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-area-type-permission/block-grid-area-type-permission.element.ts index 1d18d7e3af1f..62c9e7408c7b 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-area-type-permission/block-grid-area-type-permission.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-area-type-permission/block-grid-area-type-permission.element.ts @@ -3,7 +3,6 @@ import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import { html, customElement, property, css, state, repeat, nothing } from '@umbraco-cms/backoffice/external/lit'; import type { UmbPropertyEditorUiElement } from '@umbraco-cms/backoffice/property-editor'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; -import { UmbPropertyValueChangeEvent } from '@umbraco-cms/backoffice/property-editor'; import { UMB_DATA_TYPE_WORKSPACE_CONTEXT } from '@umbraco-cms/backoffice/data-type'; import type { UmbBlockTypeWithGroupKey } from '@umbraco-cms/backoffice/block-type'; import type { UUIComboboxElement, UUIComboboxEvent, UUIInputEvent } from '@umbraco-cms/backoffice/external/uui'; @@ -12,6 +11,7 @@ import { UMB_DOCUMENT_TYPE_ITEM_REPOSITORY_ALIAS, type UmbDocumentTypeItemModel, } from '@umbraco-cms/backoffice/document-type'; +import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; @customElement('umb-property-editor-ui-block-grid-area-type-permission') export class UmbPropertyEditorUIBlockGridAreaTypePermissionElement @@ -78,7 +78,7 @@ export class UmbPropertyEditorUIBlockGridAreaTypePermissionElement #addNewPermission() { this.value = [...this.value, { minAllowed: 0, maxAllowed: undefined }]; - this.dispatchEvent(new UmbPropertyValueChangeEvent()); + this.dispatchEvent(new UmbChangeEvent()); } #setPermissionKey(e: UUIComboboxEvent, index: number) { @@ -95,7 +95,7 @@ export class UmbPropertyEditorUIBlockGridAreaTypePermissionElement : { elementTypeKey: undefined, groupKey: undefined }; this.value = value.map((permission, i) => (i === index ? { ...permission, ...setting } : permission)); - this.dispatchEvent(new UmbPropertyValueChangeEvent()); + this.dispatchEvent(new UmbChangeEvent()); } #setPermissionMinimumRange(e: UUIInputEvent, index: number) { @@ -105,7 +105,7 @@ export class UmbPropertyEditorUIBlockGridAreaTypePermissionElement this.value = value.map((permission, i) => i === index ? { ...permission, minAllowed: parseInt(input) ?? 0 } : permission, ); - this.dispatchEvent(new UmbPropertyValueChangeEvent()); + this.dispatchEvent(new UmbChangeEvent()); } #setPermissionMaximumRange(e: UUIInputEvent, index: number) { const value = [...this.value]; @@ -114,12 +114,12 @@ export class UmbPropertyEditorUIBlockGridAreaTypePermissionElement this.value = value.map((permission, i) => i === index ? { ...permission, maxAllowed: parseInt(input) ?? undefined } : permission, ); - this.dispatchEvent(new UmbPropertyValueChangeEvent()); + this.dispatchEvent(new UmbChangeEvent()); } #remove(index: number) { this.value = [...this.value].filter((_, i) => i !== index); - this.dispatchEvent(new UmbPropertyValueChangeEvent()); + this.dispatchEvent(new UmbChangeEvent()); } override render() { diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-column-span/property-editor-ui-block-grid-column-span.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-column-span/property-editor-ui-block-grid-column-span.element.ts index d604f01593a9..6cc9d810720c 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-column-span/property-editor-ui-block-grid-column-span.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-column-span/property-editor-ui-block-grid-column-span.element.ts @@ -2,11 +2,11 @@ import type { UmbBlockGridTypeColumnSpanOption } from '../../types.js'; import { html, customElement, property, css, state, repeat } from '@umbraco-cms/backoffice/external/lit'; import { type UmbPropertyEditorUiElement, - UmbPropertyValueChangeEvent, type UmbPropertyEditorConfigCollection, } from '@umbraco-cms/backoffice/property-editor'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; +import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; @customElement('umb-property-editor-ui-block-grid-column-span') export class UmbPropertyEditorUIBlockGridColumnSpanElement extends UmbLitElement implements UmbPropertyEditorUiElement { @@ -33,7 +33,7 @@ export class UmbPropertyEditorUIBlockGridColumnSpanElement extends UmbLitElement this.value = [...value, { columnSpan: index }]; } - this.dispatchEvent(new UmbPropertyValueChangeEvent()); + this.dispatchEvent(new UmbChangeEvent()); } override render() { diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-editor/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-editor/manifests.ts index c6cc6a7dcdc4..c575670a4609 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-editor/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-editor/manifests.ts @@ -1,8 +1,10 @@ import { manifest as blockGridSchemaManifest } from './Umbraco.BlockGrid.js'; import { UMB_BLOCK_GRID_PROPERTY_EDITOR_SCHEMA_ALIAS, UMB_BLOCK_GRID_PROPERTY_EDITOR_UI_ALIAS } from './constants.js'; +import { manifests as propertyActionManifests } from './property-actions/manifests.js'; +import type { UmbExtensionManifestKind } from '@umbraco-cms/backoffice/extension-registry'; import { UmbStandardBlockValueResolver } from '@umbraco-cms/backoffice/block'; -export const manifests: Array<UmbExtensionManifest> = [ +export const manifests: Array<UmbExtensionManifest | UmbExtensionManifestKind> = [ { type: 'propertyEditorUi', alias: UMB_BLOCK_GRID_PROPERTY_EDITOR_UI_ALIAS, @@ -66,7 +68,6 @@ export const manifests: Array<UmbExtensionManifest> = [ }, }, }, - blockGridSchemaManifest, { type: 'propertyValueResolver', alias: 'Umb.PropertyValueResolver.BlockGrid', @@ -76,4 +77,6 @@ export const manifests: Array<UmbExtensionManifest> = [ editorAlias: UMB_BLOCK_GRID_PROPERTY_EDITOR_SCHEMA_ALIAS, }, }, + blockGridSchemaManifest, + ...propertyActionManifests, ]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-editor/property-actions/block-grid-paste-from-clipboard.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-editor/property-actions/block-grid-paste-from-clipboard.ts new file mode 100644 index 000000000000..1480e09dcf8d --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-editor/property-actions/block-grid-paste-from-clipboard.ts @@ -0,0 +1,52 @@ +import type { UmbBlockGridValueModel } from '../../../types.js'; +import type { UmbBlockGridPropertyEditorConfig } from '../types.js'; +import { UmbPasteFromClipboardPropertyAction } from '@umbraco-cms/backoffice/clipboard'; + +/** + * The Block Grid Paste From Clipboard Property Action. + * @exports + * @class UmbBlockGridPasteFromClipboardPropertyAction + * @augments UmbPasteFromClipboardPropertyAction + */ +export class UmbBlockGridPasteFromClipboardPropertyAction extends UmbPasteFromClipboardPropertyAction { + /** + * Filters the picker based on the block grid property editor config. + * @param {UmbBlockGridValueModel} propertyValue The property editor value. + * @param {UmbBlockGridPropertyEditorConfig} config The property editor config. + * @override + * @protected + * @memberof UmbBlockGridPasteFromClipboardPropertyAction + */ + protected override async _pickerFilter( + propertyValue: UmbBlockGridValueModel, + config: UmbBlockGridPropertyEditorConfig, + ) { + // The property action always paste in the root of the grid so + // we need to check if the content types are allowed at the root + const blocksConfig = config.find((configValue) => configValue.alias === 'blocks'); + + const allowedRootContentTypeKeys = + blocksConfig?.value + .map((blockConfig) => { + if (blockConfig.allowAtRoot) { + return blockConfig.contentElementTypeKey; + } else { + return undefined; + } + }) + .filter((contentTypeKey) => contentTypeKey !== undefined) ?? []; + + const rootContentKeys = propertyValue.layout['Umbraco.BlockGrid']?.map((block) => block.contentKey) ?? []; + const rootContentTypeKeys = propertyValue.contentData + .filter((content) => rootContentKeys.includes(content.key)) + .map((content) => content.contentTypeKey); + + // ensure all content types in the paste value are allowed in the grid root + const allContentTypesAllowedAtRoot = rootContentTypeKeys.every((contentKey) => + allowedRootContentTypeKeys.includes(contentKey), + ); + + return allContentTypesAllowedAtRoot; + } +} +export { UmbBlockGridPasteFromClipboardPropertyAction as api }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-editor/property-actions/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-editor/property-actions/manifests.ts new file mode 100644 index 000000000000..8b2befb3a1be --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-editor/property-actions/manifests.ts @@ -0,0 +1,20 @@ +import { UMB_BLOCK_GRID_PROPERTY_EDITOR_UI_ALIAS } from '../constants.js'; +import { UMB_WRITABLE_PROPERTY_CONDITION_ALIAS } from '@umbraco-cms/backoffice/property'; +import type { UmbExtensionManifestKind } from '@umbraco-cms/backoffice/extension-registry'; +import { UMB_PROPERTY_ACTION_PASTE_FROM_CLIPBOARD_KIND_MANIFEST } from '@umbraco-cms/backoffice/clipboard'; + +export const manifests: Array<UmbExtensionManifest | UmbExtensionManifestKind> = [ + { + ...UMB_PROPERTY_ACTION_PASTE_FROM_CLIPBOARD_KIND_MANIFEST.manifest, + type: 'propertyAction', + alias: 'Umb.PropertyAction.BlockGrid.Clipboard.Paste', + name: 'Block Grid Paste From Clipboard Property Action', + api: () => import('./block-grid-paste-from-clipboard.js'), + forPropertyEditorUis: [UMB_BLOCK_GRID_PROPERTY_EDITOR_UI_ALIAS], + conditions: [ + { + alias: UMB_WRITABLE_PROPERTY_CONDITION_ALIAS, + }, + ], + }, +]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-editor/types.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-editor/types.ts new file mode 100644 index 000000000000..99e701404c7f --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-editor/types.ts @@ -0,0 +1,9 @@ +// TODO: add the missing fields to the type +export type UmbBlockGridPropertyEditorConfig = Array<{ + alias: 'blocks'; + value: Array<{ + allowAtRoot: boolean; + allowInAreas: boolean; + contentElementTypeKey: string; + }>; +}>; diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-group-configuration/property-editor-ui-block-grid-group-configuration.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-group-configuration/property-editor-ui-block-grid-group-configuration.element.ts index 3dac2a5abff9..bbb80c10718f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-group-configuration/property-editor-ui-block-grid-group-configuration.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-group-configuration/property-editor-ui-block-grid-group-configuration.element.ts @@ -1,13 +1,13 @@ import { html, customElement, property, css } from '@umbraco-cms/backoffice/external/lit'; import { type UmbPropertyEditorUiElement, - UmbPropertyValueChangeEvent, type UmbPropertyEditorConfigCollection, } from '@umbraco-cms/backoffice/property-editor'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import { UmbId } from '@umbraco-cms/backoffice/id'; import type { UmbBlockGridTypeGroupType } from '@umbraco-cms/backoffice/block-grid'; +import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; @customElement('umb-property-editor-ui-block-grid-group-configuration') export class UmbPropertyEditorUIBlockGridGroupConfigurationElement @@ -28,7 +28,7 @@ export class UmbPropertyEditorUIBlockGridGroupConfigurationElement #addGroup() { this.value = [...this._value, { name: 'Unnamed group', key: UmbId.new() }]; - this.dispatchEvent(new UmbPropertyValueChangeEvent()); + this.dispatchEvent(new UmbChangeEvent()); } override render() { diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-layout-stylesheet/property-editor-ui-block-grid-layout-stylesheet.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-layout-stylesheet/property-editor-ui-block-grid-layout-stylesheet.element.ts index f0f5785c6693..28fe0f04344c 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-layout-stylesheet/property-editor-ui-block-grid-layout-stylesheet.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-layout-stylesheet/property-editor-ui-block-grid-layout-stylesheet.element.ts @@ -6,13 +6,13 @@ import '@umbraco-cms/backoffice/static-file'; import { html, customElement, property, state } from '@umbraco-cms/backoffice/external/lit'; import { type UmbPropertyEditorUiElement, - UmbPropertyValueChangeEvent, type UmbPropertyEditorConfigCollection, } from '@umbraco-cms/backoffice/property-editor'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import { UmbServerFilePathUniqueSerializer } from '@umbraco-cms/backoffice/server-file-system'; import type { UmbNumberRangeValueType } from '@umbraco-cms/backoffice/models'; +import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; @customElement('umb-property-editor-ui-block-grid-layout-stylesheet') export class UmbPropertyEditorUIBlockGridLayoutStylesheetElement @@ -66,7 +66,7 @@ export class UmbPropertyEditorUIBlockGridLayoutStylesheetElement } else { this._value = (event.target as UmbInputStaticFileElement).selection; } - this.dispatchEvent(new UmbPropertyValueChangeEvent()); + this.dispatchEvent(new UmbChangeEvent()); } // TODO: Implement mandatory? diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/manifests.ts index 8a797e8068f9..37621422be33 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/manifests.ts @@ -5,8 +5,9 @@ import { manifests as blockGridEditorManifests } from './block-grid-editor/manif import { manifest as blockGridGroupConfiguration } from './block-grid-group-configuration/manifests.js'; import { manifest as blockGridLayoutStylesheet } from './block-grid-layout-stylesheet/manifests.js'; import { manifest as blockGridTypeConfiguration } from './block-grid-type-configuration/manifests.js'; +import type { UmbExtensionManifestKind } from '@umbraco-cms/backoffice/extension-registry'; -export const manifests: Array<UmbExtensionManifest> = [ +export const manifests: Array<UmbExtensionManifest | UmbExtensionManifestKind> = [ blockGridAreaTypePermission, blockGridAreasConfigEditor, blockGridColumnSpan, diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-list/clipboard/block/paste/block-to-block-list-paste-translator.test.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-list/clipboard/block/paste/block-to-block-list-paste-translator.test.ts index a54f811c9c27..56fb74df4221 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-list/clipboard/block/paste/block-to-block-list-paste-translator.test.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-list/clipboard/block/paste/block-to-block-list-paste-translator.test.ts @@ -98,12 +98,12 @@ describe('UmbBlockToBlockListClipboardPastePropertyValueTranslator', () => { describe('isCompatibleValue', () => { it('should return true if the content types are allowed', async () => { - const result = await pasteTranslator.isCompatibleValue(blockClipboardEntryValue, config); + const result = await pasteTranslator.isCompatibleValue(blockListPropertyValue, config); expect(result).to.be.true; }); it('should return false if the content types are not allowed', async () => { - const result = await pasteTranslator.isCompatibleValue(blockClipboardEntryValue, config2); + const result = await pasteTranslator.isCompatibleValue(blockListPropertyValue, config2); expect(result).to.be.false; }); }); diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-list/clipboard/block/paste/block-to-block-list-paste-translator.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-list/clipboard/block/paste/block-to-block-list-paste-translator.ts index 9493c4ef6145..32fe61adef85 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-list/clipboard/block/paste/block-to-block-list-paste-translator.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-list/clipboard/block/paste/block-to-block-list-paste-translator.ts @@ -35,19 +35,19 @@ export class UmbBlockToBlockListClipboardPastePropertyValueTranslator /** * Checks if the clipboard entry value is compatible with the config. - * @param {UmbBlockClipboardEntryValueModel} value - The block clipboard entry value. + * @param {UmbBlockListValueModel} propertyValue - The property value * @param {*} config - The Property Editor config. * @returns {Promise<boolean>} - Whether the clipboard entry value is compatible with the config. * @memberof UmbBlockToBlockListClipboardPastePropertyValueTranslator */ async isCompatibleValue( - value: UmbBlockClipboardEntryValueModel, + propertyValue: UmbBlockListValueModel, // TODO: Replace any with the correct type. config: Array<{ alias: string; value: [{ contentElementTypeKey: string }] }>, ): Promise<boolean> { const allowedBlockContentTypes = config.find((c) => c.alias === 'blocks')?.value.map((b) => b.contentElementTypeKey) ?? []; - const blockContentTypes = value.contentData.map((c) => c.contentTypeKey); + const blockContentTypes = propertyValue.contentData.map((c) => c.contentTypeKey); return blockContentTypes?.every((b) => allowedBlockContentTypes.includes(b)) ?? false; } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-list/components/block-list-entry/block-list-entry.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-list/components/block-list-entry/block-list-entry.element.ts index 48624dad3f3f..af584330efde 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-list/components/block-list-entry/block-list-entry.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-list/components/block-list-entry/block-list-entry.element.ts @@ -395,7 +395,7 @@ export class UmbBlockListEntryElement extends UmbLitElement implements UmbProper } #renderBlock() { - return this.contentKey + return this.contentKey && (this._contentTypeAlias || this._unsupported) ? html` <div class="umb-block-list__block"> <umb-extension-slot diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-list/property-editors/block-list-type-configuration/property-editor-ui-block-list-type-configuration.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-list/property-editors/block-list-type-configuration/property-editor-ui-block-list-type-configuration.element.ts index 054b47d12465..482b509553c7 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-list/property-editors/block-list-type-configuration/property-editor-ui-block-list-type-configuration.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-list/property-editors/block-list-type-configuration/property-editor-ui-block-list-type-configuration.element.ts @@ -3,13 +3,13 @@ import { UMB_BLOCK_LIST_TYPE } from '../../constants.js'; import type { UmbBlockTypeBaseModel, UmbInputBlockTypeElement } from '@umbraco-cms/backoffice/block-type'; import { type UmbPropertyEditorUiElement, - UmbPropertyValueChangeEvent, type UmbPropertyEditorConfigCollection, } from '@umbraco-cms/backoffice/property-editor'; import { html, customElement, property, state } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import { UMB_WORKSPACE_MODAL } from '@umbraco-cms/backoffice/workspace'; import { UmbModalRouteRegistrationController } from '@umbraco-cms/backoffice/router'; +import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; /** * @element umb-property-editor-ui-block-list-type-configuration @@ -58,7 +58,7 @@ export class UmbPropertyEditorUIBlockListBlockConfigurationElement #onChange(e: CustomEvent) { e.stopPropagation(); this.value = (e.target as UmbInputBlockTypeElement).value; - this.dispatchEvent(new UmbPropertyValueChangeEvent()); + this.dispatchEvent(new UmbChangeEvent()); } override render() { diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-list/workspace/views/block-list-type-workspace-view.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-list/workspace/views/block-list-type-workspace-view.element.ts index 989d3d7a57aa..28d824111167 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-list/workspace/views/block-list-type-workspace-view.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-list/workspace/views/block-list-type-workspace-view.element.ts @@ -75,6 +75,7 @@ export class UmbBlockListTypeWorkspaceViewSettingsElement extends UmbLitElement <umb-property label=${this.localize.term('blockEditor_forceHideContentEditor')} alias="forceHideContentEditorInOverlay" + description=${this.localize.term('blockEditor_forceHideContentEditorHelp')} property-editor-ui-alias="Umb.PropertyEditorUi.Toggle"></umb-property> </uui-box> `; diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-rte/components/block-rte-entry/block-rte-entry.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-rte/components/block-rte-entry/block-rte-entry.element.ts index 6155247cf929..4708b444a374 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-rte/components/block-rte-entry/block-rte-entry.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-rte/components/block-rte-entry/block-rte-entry.element.ts @@ -66,7 +66,7 @@ export class UmbBlockRteEntryElement extends UmbLitElement implements UmbPropert _workspaceEditSettingsPath?: string; @state() - _contentElementTypeAlias?: string; + _contentTypeAlias?: string; @state() _contentTypeName?: string; @@ -115,7 +115,7 @@ export class UmbBlockRteEntryElement extends UmbLitElement implements UmbPropert this.observe( this.#context.contentElementTypeAlias, (alias) => { - this._contentElementTypeAlias = alias; + this._contentTypeAlias = alias; }, null, ); @@ -230,7 +230,7 @@ export class UmbBlockRteEntryElement extends UmbLitElement implements UmbPropert } readonly #filterBlockCustomViews = (manifest: ManifestBlockEditorCustomView) => { - const elementTypeAlias = this._contentElementTypeAlias ?? ''; + const elementTypeAlias = this._contentTypeAlias ?? ''; const isForBlockEditor = !manifest.forBlockEditor || stringOrStringArrayContains(manifest.forBlockEditor, UMB_BLOCK_RTE); const isForContentTypeAlias = @@ -256,23 +256,25 @@ export class UmbBlockRteEntryElement extends UmbLitElement implements UmbPropert }; #renderBlock() { - return html` - <div class="uui-text uui-font"> - <umb-extension-slot - type="blockEditorCustomView" - default-element="umb-ref-rte-block" - .renderMethod=${this.#extensionSlotRenderMethod} - .props=${this._blockViewProps} - .filter=${this.#filterBlockCustomViews} - single> - ${this.#renderRefBlock()} - </umb-extension-slot> - <uui-action-bar> ${this.#renderEditAction()} ${this.#renderEditSettingsAction()} </uui-action-bar> - ${!this._showContentEdit && this._contentInvalid - ? html`<uui-badge attention color="danger" label="Invalid content">!</uui-badge>` - : nothing} - </div> - `; + return this.contentKey && this._contentTypeAlias + ? html` + <div class="uui-text uui-font"> + <umb-extension-slot + type="blockEditorCustomView" + default-element="umb-ref-rte-block" + .renderMethod=${this.#extensionSlotRenderMethod} + .props=${this._blockViewProps} + .filter=${this.#filterBlockCustomViews} + single> + ${this.#renderRefBlock()} + </umb-extension-slot> + <uui-action-bar> ${this.#renderEditAction()} ${this.#renderEditSettingsAction()} </uui-action-bar> + ${!this._showContentEdit && this._contentInvalid + ? html`<uui-badge attention color="danger" label="Invalid content">!</uui-badge>` + : nothing} + </div> + ` + : nothing; } #renderRefBlock() { diff --git a/src/Umbraco.Web.UI.Client/src/packages/clipboard/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/clipboard/constants.ts index adfc175c42de..607c1a68dab3 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/clipboard/constants.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/clipboard/constants.ts @@ -1,4 +1,4 @@ export * from './clipboard-entry/constants.js'; export * from './clipboard-root/constants.js'; export * from './collection/constants.js'; -export * from './property/context/constants.js'; +export * from './property/constants.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/clipboard/property/actions/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/clipboard/property/actions/constants.ts new file mode 100644 index 000000000000..93931d504510 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/clipboard/property/actions/constants.ts @@ -0,0 +1 @@ +export * from './paste/constants.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/clipboard/property/actions/index.ts b/src/Umbraco.Web.UI.Client/src/packages/clipboard/property/actions/index.ts new file mode 100644 index 000000000000..50023908eba9 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/clipboard/property/actions/index.ts @@ -0,0 +1 @@ +export * from './paste/index.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/clipboard/property/actions/paste/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/clipboard/property/actions/paste/constants.ts new file mode 100644 index 000000000000..b7fe0395d405 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/clipboard/property/actions/paste/constants.ts @@ -0,0 +1 @@ +export { UMB_PROPERTY_ACTION_PASTE_FROM_CLIPBOARD_KIND_MANIFEST } from './manifests.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/clipboard/property/actions/paste/index.ts b/src/Umbraco.Web.UI.Client/src/packages/clipboard/property/actions/paste/index.ts new file mode 100644 index 000000000000..8150ab49c49c --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/clipboard/property/actions/paste/index.ts @@ -0,0 +1 @@ +export { UmbPasteFromClipboardPropertyAction } from './paste-from-clipboard.property-action.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/clipboard/property/actions/paste/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/clipboard/property/actions/paste/manifests.ts index 3c0ac1d98032..44989826c0d2 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/clipboard/property/actions/paste/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/clipboard/property/actions/paste/manifests.ts @@ -1,22 +1,24 @@ import type { UmbExtensionManifestKind } from '@umbraco-cms/backoffice/extension-registry'; import { UMB_PROPERTY_ACTION_DEFAULT_KIND_MANIFEST } from '@umbraco-cms/backoffice/property-action'; -export const manifests: Array<UmbExtensionManifest | UmbExtensionManifestKind> = [ - { - type: 'kind', - alias: 'Umb.Kind.PropertyAction.pasteFromClipboard', - matchKind: 'pasteFromClipboard', - matchType: 'propertyAction', - manifest: { - ...UMB_PROPERTY_ACTION_DEFAULT_KIND_MANIFEST.manifest, - type: 'propertyAction', - kind: 'pasteFromClipboard', - api: () => import('./paste-from-clipboard.property-action.js'), - weight: 1190, - meta: { - icon: 'icon-clipboard-paste', - label: 'Replace', - }, +export const UMB_PROPERTY_ACTION_PASTE_FROM_CLIPBOARD_KIND_MANIFEST: UmbExtensionManifestKind = { + type: 'kind', + alias: 'Umb.Kind.PropertyAction.pasteFromClipboard', + matchKind: 'pasteFromClipboard', + matchType: 'propertyAction', + manifest: { + ...UMB_PROPERTY_ACTION_DEFAULT_KIND_MANIFEST.manifest, + type: 'propertyAction', + kind: 'pasteFromClipboard', + api: () => import('./paste-from-clipboard.property-action.js'), + weight: 1190, + meta: { + icon: 'icon-clipboard-paste', + label: 'Replace', }, }, +}; + +export const manifests: Array<UmbExtensionManifest | UmbExtensionManifestKind> = [ + UMB_PROPERTY_ACTION_PASTE_FROM_CLIPBOARD_KIND_MANIFEST, ]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/clipboard/property/actions/paste/paste-from-clipboard.property-action.ts b/src/Umbraco.Web.UI.Client/src/packages/clipboard/property/actions/paste/paste-from-clipboard.property-action.ts index 869b3ad05b76..e4c54117e8df 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/clipboard/property/actions/paste/paste-from-clipboard.property-action.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/clipboard/property/actions/paste/paste-from-clipboard.property-action.ts @@ -25,6 +25,11 @@ export class UmbPasteFromClipboardPropertyAction extends UmbPropertyActionBase<M ]); } + // eslint-disable-next-line @typescript-eslint/no-unused-vars + protected async _pickerFilter(value: any, config: any) { + return true; + } + override async execute() { await this.#init; if (!this.#clipboardContext) throw new Error('Clipboard context not found'); @@ -39,6 +44,7 @@ export class UmbPasteFromClipboardPropertyAction extends UmbPropertyActionBase<M const result = await this.#clipboardContext.pick({ propertyEditorUiAlias: propertyEditorManifest.alias, multiple: false, + filter: this._pickerFilter, }); const selectedUnique = result.selection[0]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/clipboard/property/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/clipboard/property/constants.ts new file mode 100644 index 000000000000..dcbcfa43f83d --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/clipboard/property/constants.ts @@ -0,0 +1,2 @@ +export * from './actions/constants.js'; +export * from './context/constants.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/clipboard/property/context/clipboard.property-context.ts b/src/Umbraco.Web.UI.Client/src/packages/clipboard/property/context/clipboard.property-context.ts index ce80e17c23d2..60c4ece70f3a 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/clipboard/property/context/clipboard.property-context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/clipboard/property/context/clipboard.property-context.ts @@ -117,11 +117,13 @@ export class UmbClipboardPropertyContext extends UmbContextBase<UmbClipboardProp * @param args - Arguments for picking a clipboard entry * @param {boolean} args.multiple - Allow multiple clipboard entries to be picked * @param {string} args.propertyEditorUiAlias - The alias of the property editor to match + * @param {() => Promise<boolean>} args.filter - A filter function to filter clipboard entries * @returns { Promise<{ selection: Array<UmbEntityUnique>; propertyValues: Array<any> }> } */ async pick(args: { multiple: boolean; propertyEditorUiAlias: string; + filter?: (value: any, config: any) => Promise<boolean>; }): Promise<{ selection: Array<UmbEntityUnique>; propertyValues: Array<any> }> { await this.#init; @@ -149,8 +151,12 @@ export class UmbClipboardPropertyContext extends UmbContextBase<UmbClipboardProp ); if (pasteTranslator.isCompatibleValue) { - const value = await valueResolver.resolve(clipboardEntryDetail.values, propertyEditorUiManifest.alias); - return pasteTranslator.isCompatibleValue(value, config); + const propertyValue = await valueResolver.resolve( + clipboardEntryDetail.values, + propertyEditorUiManifest.alias, + ); + + return pasteTranslator.isCompatibleValue(propertyValue, config, args.filter); } return true; diff --git a/src/Umbraco.Web.UI.Client/src/packages/clipboard/property/index.ts b/src/Umbraco.Web.UI.Client/src/packages/clipboard/property/index.ts index eb5c3fe3aa54..9caeb48aac4b 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/clipboard/property/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/clipboard/property/index.ts @@ -1 +1,2 @@ +export * from './actions/index.js'; export * from './value-translator/index.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/clipboard/property/value-translator/paste/types.ts b/src/Umbraco.Web.UI.Client/src/packages/clipboard/property/value-translator/paste/types.ts index e4cc3081edaf..8ae7da76a601 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/clipboard/property/value-translator/paste/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/clipboard/property/value-translator/paste/types.ts @@ -4,9 +4,13 @@ export type * from './clipboard-paste-translator.extension.js'; export interface UmbClipboardPastePropertyValueTranslator< ClipboardEntryValueType = any, - PropertyValueModelType = any, + PropertyValueType = any, ConfigType = any, > extends UmbApi { - translate: (value: ClipboardEntryValueType) => Promise<PropertyValueModelType>; - isCompatibleValue?: (value: ClipboardEntryValueType, config: ConfigType) => Promise<boolean>; + translate: (clipboardEntryValue: ClipboardEntryValueType) => Promise<PropertyValueType>; + isCompatibleValue?: ( + propertyValue: PropertyValueType, + config: ConfigType, + filter?: (propertyValue: PropertyValueType, config: ConfigType) => Promise<boolean>, + ) => Promise<boolean>; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/code-editor/index.ts b/src/Umbraco.Web.UI.Client/src/packages/code-editor/index.ts index e3fb6a2f7ce1..313325ddddaf 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/code-editor/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/code-editor/index.ts @@ -3,11 +3,3 @@ export * from './models/index.js'; export * from './code-editor-modal/index.js'; export type { UmbCodeEditorController } from './code-editor.controller.js'; - -/** - * @deprecated Use `import from '@umbraco-cms/backoffice/code-editor';` directly. - * This function will be removed in Umbraco 15. - */ -export function loadCodeEditor() { - return import('@umbraco-cms/backoffice/code-editor'); -} diff --git a/src/Umbraco.Web.UI.Client/src/packages/code-editor/property-editor/property-editor-ui-code-editor.element.ts b/src/Umbraco.Web.UI.Client/src/packages/code-editor/property-editor/property-editor-ui-code-editor.element.ts index dcacb68fce0f..6a5cbc2b6bf1 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/code-editor/property-editor/property-editor-ui-code-editor.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/code-editor/property-editor/property-editor-ui-code-editor.element.ts @@ -1,9 +1,8 @@ import type { CodeEditorLanguage } from '../models/index.js'; import type { UmbCodeEditorElement } from '../components/code-editor.element.js'; import { css, customElement, html, property, state, styleMap } from '@umbraco-cms/backoffice/external/lit'; -import { UmbInputEvent } from '@umbraco-cms/backoffice/event'; +import { UmbChangeEvent, UmbInputEvent } from '@umbraco-cms/backoffice/event'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; -import { UmbPropertyValueChangeEvent } from '@umbraco-cms/backoffice/property-editor'; import type { UmbPropertyEditorConfigCollection, UmbPropertyEditorUiElement, @@ -49,7 +48,7 @@ export class UmbPropertyEditorUICodeEditorElement extends UmbLitElement implemen #onChange(event: UmbInputEvent & { target: UmbCodeEditorElement }) { if (!(event instanceof UmbInputEvent)) return; this.value = event.target.code; - this.dispatchEvent(new UmbPropertyValueChangeEvent()); + this.dispatchEvent(new UmbChangeEvent()); } override render() { diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-dropdown/input-dropdown-list.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-dropdown/input-dropdown-list.element.ts index d9867b079686..36fb0751bd69 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-dropdown/input-dropdown-list.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-dropdown/input-dropdown-list.element.ts @@ -1,13 +1,29 @@ -import { css, html, customElement, property, query } from '@umbraco-cms/backoffice/external/lit'; -import { UUIFormControlMixin } from '@umbraco-cms/backoffice/external/uui'; +import { css, customElement, html, property } from '@umbraco-cms/backoffice/external/lit'; import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; +import { UMB_VALIDATION_EMPTY_LOCALIZATION_KEY, UmbFormControlMixin } from '@umbraco-cms/backoffice/validation'; import type { UUISelectEvent } from '@umbraco-cms/backoffice/external/uui'; @customElement('umb-input-dropdown-list') -export class UmbInputDropdownListElement extends UUIFormControlMixin(UmbLitElement, undefined) { +export class UmbInputDropdownListElement extends UmbFormControlMixin< + string | undefined, + typeof UmbLitElement, + undefined +>(UmbLitElement, undefined) { @property({ type: Array }) - public options?: Array<Option>; + public set options(value: Array<Option> | undefined) { + this.#options = value; + + this.value = + value + ?.filter((option) => option.selected) + .map((option) => option.value) + .join(', ') ?? undefined; + } + public get options(): Array<Option> | undefined { + return this.#options; + } + #options?: Array<Option> | undefined; @property({ type: String }) public placeholder?: string; @@ -16,6 +32,19 @@ export class UmbInputDropdownListElement extends UUIFormControlMixin(UmbLitEleme @property({ type: Boolean }) public multiple?: boolean; + @property({ type: String }) + name?: string = 'Dropdown'; + + /** + * Sets the input to required, meaning validation will fail if the value is empty. + * @type {boolean} + */ + @property({ type: Boolean }) + required?: boolean; + + @property({ type: String }) + requiredMessage?: string; + /** * Sets the input to readonly mode, meaning value cannot be changed but still able to read and select its content. * @type {boolean} @@ -25,26 +54,36 @@ export class UmbInputDropdownListElement extends UUIFormControlMixin(UmbLitEleme @property({ type: Boolean, reflect: true }) readonly = false; - @query('uui-select') - private selectEle!: HTMLInputElement; + constructor() { + super(); + + this.addValidator( + 'valueMissing', + () => this.requiredMessage ?? UMB_VALIDATION_EMPTY_LOCALIZATION_KEY, + () => !this.readonly && !!this.required && (this.value === undefined || this.value === null || this.value === ''), + ); + } - protected override getFormElement() { - return this.selectEle; + protected override firstUpdated() { + this.addFormControlElement(this.shadowRoot!.querySelector('uui-select')!); } #onChange(e: UUISelectEvent) { e.stopPropagation(); - if (e.target.value) this.value = e.target.value; + this.value = e.target.value?.toString() ?? undefined; this.dispatchEvent(new UmbChangeEvent()); } override render() { - return html`<uui-select - label=${this.localize.term('formProviderFieldTypes_dropdownName')} - .placeholder=${this.placeholder ?? ''} - .options=${this.options ?? []} - @change=${this.#onChange} - ?readonly=${this.readonly}></uui-select>`; + return html` + <uui-select + label=${this.localize.term(this.localize.term('general_fieldFor', [this.name]))} + .placeholder=${this.placeholder ?? ''} + .options=${this.options ?? []} + @change=${this.#onChange} + ?readonly=${this.readonly}> + </uui-select> + `; } static override styles = [ diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-radio-button-list/input-radio-button-list.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-radio-button-list/input-radio-button-list.element.ts index 747b5cb4bd02..5840ae9081b3 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-radio-button-list/input-radio-button-list.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-radio-button-list/input-radio-button-list.element.ts @@ -1,15 +1,18 @@ import { css, html, nothing, repeat, customElement, property, classMap } from '@umbraco-cms/backoffice/external/lit'; -import { UUIFormControlMixin, UUIRadioElement } from '@umbraco-cms/backoffice/external/uui'; import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; +import { UUIRadioElement } from '@umbraco-cms/backoffice/external/uui'; +import { UMB_VALIDATION_EMPTY_LOCALIZATION_KEY, UmbFormControlMixin } from '@umbraco-cms/backoffice/validation'; + import type { UUIRadioEvent } from '@umbraco-cms/backoffice/external/uui'; export type UmbRadioButtonItem = { label: string; value: string; invalid?: boolean }; @customElement('umb-input-radio-button-list') -export class UmbInputRadioButtonListElement extends UUIFormControlMixin(UmbLitElement, '') { - #value: string = ''; - +export class UmbInputRadioButtonListElement extends UmbFormControlMixin<string, typeof UmbLitElement, undefined>( + UmbLitElement, + undefined, +) { @property() public override set value(value: string) { this.#value = value; @@ -17,6 +20,7 @@ export class UmbInputRadioButtonListElement extends UUIFormControlMixin(UmbLitEl public override get value(): string { return this.#value; } + #value: string = ''; @property({ type: Array }) public list: Array<UmbRadioButtonItem> = []; @@ -30,8 +34,24 @@ export class UmbInputRadioButtonListElement extends UUIFormControlMixin(UmbLitEl @property({ type: Boolean, reflect: true }) readonly = false; - protected override getFormElement() { - return undefined; + /** + * Sets the input to required, meaning validation will fail if the value is empty. + * @type {boolean} + */ + @property({ type: Boolean }) + required?: boolean; + + @property({ type: String }) + requiredMessage?: string; + + constructor() { + super(); + + this.addValidator( + 'valueMissing', + () => this.requiredMessage ?? UMB_VALIDATION_EMPTY_LOCALIZATION_KEY, + () => !this.readonly && !!this.required && (this.value === undefined || this.value === null || this.value === ''), + ); } #onChange(event: UUIRadioEvent) { @@ -56,11 +76,13 @@ export class UmbInputRadioButtonListElement extends UUIFormControlMixin(UmbLitEl } #renderRadioButton(item: (typeof this.list)[0]) { - return html`<uui-radio - value=${item.value} - class=${classMap({ invalid: !!item.invalid })} - label=${item.label + (item.invalid ? ` (${this.localize.term('validation_legacyOption')})` : '')} - title=${item.invalid ? this.localize.term('validation_legacyOptionDescription') : ''}></uui-radio>`; + return html` + <uui-radio + value=${item.value} + class=${classMap({ invalid: !!item.invalid })} + label=${item.label + (item.invalid ? ` (${this.localize.term('validation_legacyOption')})` : '')} + title=${item.invalid ? this.localize.term('validation_legacyOptionDescription') : ''}></uui-radio> + `; } static override readonly styles = [ diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/content-type/global-components/content-type-workspace-editor-header.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/content-type/global-components/content-type-workspace-editor-header.element.ts new file mode 100644 index 000000000000..fc3180ee1d17 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/content-type/global-components/content-type-workspace-editor-header.element.ts @@ -0,0 +1,156 @@ +import { UMB_CONTENT_TYPE_WORKSPACE_CONTEXT } from '../workspace/content-type-workspace.context-token.js'; +import type { UmbInputWithAliasElement } from '@umbraco-cms/backoffice/components'; +import { umbFocus, UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; +import { css, html, customElement, state, ifDefined } from '@umbraco-cms/backoffice/external/lit'; +import { UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal'; +import { UMB_ICON_PICKER_MODAL } from '@umbraco-cms/backoffice/icon'; +import type { UUITextareaElement } from '@umbraco-cms/backoffice/external/uui'; +import { umbBindToValidation } from '@umbraco-cms/backoffice/validation'; + +@customElement('umb-content-type-workspace-editor-header') +export class UmbContentTypeWorkspaceEditorHeaderElement extends UmbLitElement { + @state() + private _name?: string; + + @state() + private _alias?: string; + + @state() + private _description?: string; + + @state() + private _icon?: string; + + @state() + private _isNew?: boolean; + + #workspaceContext?: typeof UMB_CONTENT_TYPE_WORKSPACE_CONTEXT.TYPE; + + constructor() { + super(); + + this.consumeContext(UMB_CONTENT_TYPE_WORKSPACE_CONTEXT, (instance) => { + this.#workspaceContext = instance; + this.#observeContentType(); + }); + } + + #observeContentType() { + if (!this.#workspaceContext) return; + this.observe(this.#workspaceContext.name, (name) => (this._name = name), '_observeName'); + this.observe(this.#workspaceContext.alias, (alias) => (this._alias = alias), '_observeAlias'); + this.observe( + this.#workspaceContext.description, + (description) => (this._description = description), + '_observeDescription', + ); + this.observe(this.#workspaceContext.icon, (icon) => (this._icon = icon), '_observeIcon'); + this.observe(this.#workspaceContext.isNew, (isNew) => (this._isNew = isNew), '_observeIsNew'); + } + + private async _handleIconClick() { + const [alias, color] = this._icon?.replace('color-', '')?.split(' ') ?? []; + const modalManager = await this.getContext(UMB_MODAL_MANAGER_CONTEXT); + const modalContext = modalManager.open(this, UMB_ICON_PICKER_MODAL, { + value: { + icon: alias, + color: color, + }, + }); + + modalContext?.onSubmit().then((saved) => { + if (saved.icon && saved.color) { + this.#workspaceContext?.setIcon(`${saved.icon} color-${saved.color}`); + } else if (saved.icon) { + this.#workspaceContext?.setIcon(saved.icon); + } + }); + } + + #onNameAndAliasChange(event: InputEvent & { target: UmbInputWithAliasElement }) { + this.#workspaceContext?.setName(event.target.value ?? ''); + this.#workspaceContext?.setAlias(event.target.alias ?? ''); + } + + #onDescriptionChange(event: InputEvent & { target: UUITextareaElement }) { + this.#workspaceContext?.setDescription(event.target.value.toString() ?? ''); + } + + override render() { + return html` + <div id="header"> + <uui-button id="icon" compact label="icon" look="outline" @click=${this._handleIconClick}> + <umb-icon name=${ifDefined(this._icon)}></umb-icon> + </uui-button> + + <div id="editors"> + <umb-input-with-alias + id="name" + label=${this.localize.term('placeholders_entername')} + .value=${this._name} + .alias=${this._alias} + ?auto-generate-alias=${this._isNew} + @change=${this.#onNameAndAliasChange} + required + ${umbBindToValidation(this, '$.name', this._name)} + ${umbFocus()}> + </umb-input-with-alias> + + <uui-input + id="description" + .label=${this.localize.term('placeholders_enterDescription')} + .value=${this._description} + .placeholder=${this.localize.term('placeholders_enterDescription')} + @input=${this.#onDescriptionChange}></uui-input> + </div> + </div> + `; + } + + static override styles = [ + css` + :host { + display: contents; + } + + #header { + display: flex; + flex: 1 1 auto; + gap: var(--uui-size-space-2); + } + + #editors { + display: flex; + flex: 1 1 auto; + flex-direction: column; + gap: var(--uui-size-space-1); + } + + #name { + width: 100%; + } + + #description { + width: 100%; + --uui-input-height: var(--uui-size-8); + --uui-input-border-color: transparent; + } + + #description:hover { + --uui-input-border-color: var(--uui-color-border); + } + + #icon { + font-size: var(--uui-size-8); + height: 60px; + width: 60px; + } + `, + ]; +} + +declare global { + interface HTMLElementTagNameMap { + 'umb-content-type-workspace-editor-header': UmbContentTypeWorkspaceEditorHeaderElement; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/content-type/global-components/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/content-type/global-components/index.ts new file mode 100644 index 000000000000..e9575c6d1b4c --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/content-type/global-components/index.ts @@ -0,0 +1,3 @@ +import './content-type-workspace-editor-header.element.js'; + +export * from './content-type-workspace-editor-header.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/content-type/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/content-type/index.ts index 3bedcf0cc9c9..a43707b91ea3 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/content-type/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/content-type/index.ts @@ -1,4 +1,5 @@ export * from './constants.js'; +export * from './global-components/index.js'; export * from './repository/index.js'; export * from './structure/index.js'; export * from './workspace/index.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/content-type/repository/structure/content-type-structure-data-source.interface.ts b/src/Umbraco.Web.UI.Client/src/packages/core/content-type/repository/structure/content-type-structure-data-source.interface.ts index dde79453a265..49d31fe5be61 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/content-type/repository/structure/content-type-structure-data-source.interface.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/content-type/repository/structure/content-type-structure-data-source.interface.ts @@ -6,5 +6,5 @@ export interface UmbContentTypeStructureDataSourceConstructor<ItemType> { } export interface UmbContentTypeStructureDataSource<ItemType> { - getAllowedChildrenOf(unique: string | null): Promise<UmbDataSourceResponse<UmbPagedModel<ItemType>>>; + getAllowedChildrenOf(unique: string | null, parentContentUnique: string | null): Promise<UmbDataSourceResponse<UmbPagedModel<ItemType>>>; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/content-type/repository/structure/content-type-structure-repository-base.ts b/src/Umbraco.Web.UI.Client/src/packages/core/content-type/repository/structure/content-type-structure-repository-base.ts index 91cddbe518d8..4d51925ed87f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/content-type/repository/structure/content-type-structure-repository-base.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/content-type/repository/structure/content-type-structure-repository-base.ts @@ -23,7 +23,7 @@ export abstract class UmbContentTypeStructureRepositoryBase<ItemType> * @returns {*} * @memberof UmbContentTypeStructureRepositoryBase */ - requestAllowedChildrenOf(unique: string | null) { - return this.#structureSource.getAllowedChildrenOf(unique); + requestAllowedChildrenOf(unique: string | null, parentContentUnique: string | null) { + return this.#structureSource.getAllowedChildrenOf(unique, parentContentUnique); } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/content-type/repository/structure/content-type-structure-repository.interface.ts b/src/Umbraco.Web.UI.Client/src/packages/core/content-type/repository/structure/content-type-structure-repository.interface.ts index 7691800e44e0..1f74549895ee 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/content-type/repository/structure/content-type-structure-repository.interface.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/content-type/repository/structure/content-type-structure-repository.interface.ts @@ -1,5 +1,5 @@ import type { UmbDataSourceResponse, UmbPagedModel } from '@umbraco-cms/backoffice/repository'; export interface UmbContentTypeStructureRepository<ItemType> { - requestAllowedChildrenOf(unique: string): Promise<UmbDataSourceResponse<UmbPagedModel<ItemType>>>; + requestAllowedChildrenOf(unique: string, parentContentUnique: string | null): Promise<UmbDataSourceResponse<UmbPagedModel<ItemType>>>; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/content-type/repository/structure/content-type-structure-server-data-source-base.ts b/src/Umbraco.Web.UI.Client/src/packages/core/content-type/repository/structure/content-type-structure-server-data-source-base.ts index e3294c5283ff..3ce3d6ae676c 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/content-type/repository/structure/content-type-structure-server-data-source-base.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/content-type/repository/structure/content-type-structure-server-data-source-base.ts @@ -16,7 +16,7 @@ export interface UmbContentTypeStructureServerDataSourceBaseArgs< ServerItemType extends AllowedContentTypeBaseModel, ClientItemType extends UmbEntityModel, > { - getAllowedChildrenOf: (unique: string | null) => Promise<UmbPagedModel<ServerItemType>>; + getAllowedChildrenOf: (unique: string | null, parentContentUnique: string | null) => Promise<UmbPagedModel<ServerItemType>>; mapper: (item: ServerItemType) => ClientItemType; } @@ -50,8 +50,8 @@ export abstract class UmbContentTypeStructureServerDataSourceBase< * @returns {*} * @memberof UmbContentTypeStructureServerDataSourceBase */ - async getAllowedChildrenOf(unique: string | null) { - const { data, error } = await tryExecuteAndNotify(this.#host, this.#getAllowedChildrenOf(unique)); + async getAllowedChildrenOf(unique: string | null, parentContentUnique: string | null) { + const { data, error } = await tryExecuteAndNotify(this.#host, this.#getAllowedChildrenOf(unique, parentContentUnique)); if (data) { const items = data.items.map((item) => this.#mapper(item)); diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/content-type/workspace/content-type-workspace-context-base.ts b/src/Umbraco.Web.UI.Client/src/packages/core/content-type/workspace/content-type-workspace-context-base.ts index 74c3f510d1d4..42fd9945506f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/content-type/workspace/content-type-workspace-context-base.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/content-type/workspace/content-type-workspace-context-base.ts @@ -252,6 +252,14 @@ export abstract class UmbContentTypeWorkspaceContextBase< this.structure.updateOwnerContentType({ compositions } as Partial<DetailModelType>); } + /** + * Gets the icon of the content type + * @returns { string | undefined } The icon of the content type + */ + public getIcon(): string | undefined { + return this.structure.getOwnerContentType()?.icon; + } + // TODO: manage setting icon color alias? public setIcon(icon: string) { this.structure.updateOwnerContentType({ icon } as Partial<DetailModelType>); diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/content-type/workspace/content-type-workspace-context.interface.ts b/src/Umbraco.Web.UI.Client/src/packages/core/content-type/workspace/content-type-workspace-context.interface.ts index 1405ff101c2a..dc529f79808a 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/content-type/workspace/content-type-workspace-context.interface.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/content-type/workspace/content-type-workspace-context.interface.ts @@ -8,7 +8,6 @@ export interface UmbContentTypeWorkspaceContext<ContentTypeType extends UmbConte readonly IS_CONTENT_TYPE_WORKSPACE_CONTEXT: true; readonly name: Observable<string | undefined>; - getName(): string | undefined; readonly alias: Observable<string | undefined>; readonly description: Observable<string | undefined>; readonly icon: Observable<string | undefined>; @@ -22,7 +21,18 @@ export interface UmbContentTypeWorkspaceContext<ContentTypeType extends UmbConte readonly structure: UmbContentTypeStructureManager<ContentTypeType>; + getAlias(): string | undefined; setAlias(alias: string): void; + getCompositions(): Array<UmbContentTypeCompositionModel> | undefined; setCompositions(compositions: Array<UmbContentTypeCompositionModel>): void; + + getDescription(): string | undefined; + setDescription(description: string): void; + + getIcon(): string | undefined; + setIcon(icon: string): void; + + getName(): string | undefined; + setName(name: string): void; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/constants.ts index d60c06800697..c6a840d16037 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/constants.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/constants.ts @@ -1 +1,2 @@ export * from './common/constants.js'; +export * from './has-children/constants.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/has-children/condition/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/has-children/condition/constants.ts new file mode 100644 index 000000000000..cdfbe5b0b33b --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/has-children/condition/constants.ts @@ -0,0 +1 @@ +export const UMB_ENTITY_HAS_CHILDREN_CONDITION_ALIAS = 'Umb.Condition.EntityHasChildren'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/has-children/condition/entity-has-children.condition-config.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/has-children/condition/entity-has-children.condition-config.ts new file mode 100644 index 000000000000..2c08118a07eb --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/has-children/condition/entity-has-children.condition-config.ts @@ -0,0 +1,12 @@ +import type { UMB_ENTITY_HAS_CHILDREN_CONDITION_ALIAS } from './constants.js'; +import type { UmbConditionConfigBase } from '@umbraco-cms/backoffice/extension-api'; + +// eslint-disable-next-line @typescript-eslint/no-empty-object-type +export interface UmbEntityHasChildrenConditionConfig + extends UmbConditionConfigBase<typeof UMB_ENTITY_HAS_CHILDREN_CONDITION_ALIAS> {} + +declare global { + interface UmbExtensionConditionConfigMap { + UmbEntityHasChildrenConditionConfig: UmbEntityHasChildrenConditionConfig; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/has-children/condition/entity-has-children.condition.manifest.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/has-children/condition/entity-has-children.condition.manifest.ts new file mode 100644 index 000000000000..ddfde4f2a9a1 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/has-children/condition/entity-has-children.condition.manifest.ts @@ -0,0 +1,9 @@ +import { UMB_ENTITY_HAS_CHILDREN_CONDITION_ALIAS } from './constants.js'; +import type { ManifestCondition } from '@umbraco-cms/backoffice/extension-api'; + +export const manifest: ManifestCondition = { + type: 'condition', + name: 'Entity Has Children Condition', + alias: UMB_ENTITY_HAS_CHILDREN_CONDITION_ALIAS, + api: () => import('./entity-has-children.condition.js'), +}; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/has-children/condition/entity-has-children.condition.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/has-children/condition/entity-has-children.condition.ts new file mode 100644 index 000000000000..cc930c4cf8a9 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/has-children/condition/entity-has-children.condition.ts @@ -0,0 +1,25 @@ +import { UMB_HAS_CHILDREN_ENTITY_CONTEXT } from '../context/has-children.context-token.js'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; +import type { + UmbConditionConfigBase, + UmbConditionControllerArguments, + UmbExtensionCondition, +} from '@umbraco-cms/backoffice/extension-api'; +import { UmbConditionBase } from '@umbraco-cms/backoffice/extension-registry'; + +export class UmbEntityHasChildrenCondition + extends UmbConditionBase<UmbConditionConfigBase> + implements UmbExtensionCondition +{ + constructor(host: UmbControllerHost, args: UmbConditionControllerArguments<UmbConditionConfigBase>) { + super(host, args); + + this.consumeContext(UMB_HAS_CHILDREN_ENTITY_CONTEXT, (context) => { + this.observe(context.hasChildren, (hasChildren) => { + this.permitted = hasChildren === true; + }); + }); + } +} + +export { UmbEntityHasChildrenCondition as api }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/has-children/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/has-children/constants.ts new file mode 100644 index 000000000000..853290d6b288 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/has-children/constants.ts @@ -0,0 +1,2 @@ +export * from './condition/constants.js'; +export * from './context/constants.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/has-children/context/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/has-children/context/constants.ts new file mode 100644 index 000000000000..137e79f8430b --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/has-children/context/constants.ts @@ -0,0 +1 @@ +export * from './has-children.context-token.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/has-children/context/has-children.context-token.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/has-children/context/has-children.context-token.ts new file mode 100644 index 000000000000..a2bb77d5e20d --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/has-children/context/has-children.context-token.ts @@ -0,0 +1,6 @@ +import type { UmbHasChildrenEntityContext } from './has-children.entity-context.js'; +import { UmbContextToken } from '@umbraco-cms/backoffice/context-api'; + +export const UMB_HAS_CHILDREN_ENTITY_CONTEXT = new UmbContextToken<UmbHasChildrenEntityContext>( + 'UmbHasChildrenEntityContext', +); diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/has-children/context/has-children.entity-context.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/has-children/context/has-children.entity-context.ts new file mode 100644 index 000000000000..ed8d1c0de908 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/has-children/context/has-children.entity-context.ts @@ -0,0 +1,31 @@ +import { UMB_HAS_CHILDREN_ENTITY_CONTEXT } from './has-children.context-token.js'; +import { UmbContextBase } from '@umbraco-cms/backoffice/class-api'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; +import { UmbBooleanState } from '@umbraco-cms/backoffice/observable-api'; + +export class UmbHasChildrenEntityContext extends UmbContextBase<UmbHasChildrenEntityContext> { + #hasChildren = new UmbBooleanState(undefined); + public readonly hasChildren = this.#hasChildren.asObservable(); + + constructor(host: UmbControllerHost) { + super(host, UMB_HAS_CHILDREN_ENTITY_CONTEXT); + } + + /** + * Gets the hasChildren state + * @returns {boolean} - The hasChildren state + * @memberof UmbHasChildrenEntityContext + */ + public getHasChildren(): boolean | undefined { + return this.#hasChildren.getValue(); + } + + /** + * Sets the hasChildren state + * @param {boolean} hasChildren - The hasChildren state + * @memberof UmbHasChildrenEntityContext + */ + public setHasChildren(hasChildren: boolean) { + this.#hasChildren.setValue(hasChildren); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/has-children/context/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/has-children/context/index.ts new file mode 100644 index 000000000000..0b119e565ced --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/has-children/context/index.ts @@ -0,0 +1 @@ +export * from './has-children.entity-context.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/has-children/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/has-children/index.ts new file mode 100644 index 000000000000..00c55032bc33 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/has-children/index.ts @@ -0,0 +1 @@ +export * from './context/index.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/has-children/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/has-children/manifests.ts new file mode 100644 index 000000000000..e3b60432b4f5 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/has-children/manifests.ts @@ -0,0 +1,4 @@ +import { manifest as conditionManifest } from './condition/entity-has-children.condition.manifest.js'; +import type { UmbExtensionManifestKind } from '@umbraco-cms/backoffice/extension-registry'; + +export const manifests: Array<UmbExtensionManifest | UmbExtensionManifestKind> = [conditionManifest]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/index.ts index 886f167a4269..2104b5fe36f4 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/index.ts @@ -5,8 +5,10 @@ export * from './constants.js'; export * from './entity-action-base.js'; export * from './entity-action-list.element.js'; export * from './entity-action.event.js'; +export * from './has-children/index.js'; export * from './entity-updated.event.js'; export * from './entity-deleted.event.js'; + export type * from './types.js'; export { UmbRequestReloadStructureForEntityEvent } from './request-reload-structure-for-entity.event.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/manifests.ts index 7d6813e49a3a..66347b90e6a2 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/manifests.ts @@ -2,6 +2,7 @@ import { manifests as createEntityActionManifests } from './common/create/manife import { manifests as defaultEntityActionManifests } from './default/manifests.js'; import { manifests as deleteEntityActionManifests } from './common/delete/manifests.js'; import { manifests as duplicateEntityActionManifests } from './common/duplicate/manifests.js'; +import { manifests as hasChildrenManifests } from './has-children/manifests.js'; import type { UmbExtensionManifestKind } from '@umbraco-cms/backoffice/extension-registry'; @@ -10,4 +11,5 @@ export const manifests: Array<UmbExtensionManifest | UmbExtensionManifestKind> = ...defaultEntityActionManifests, ...deleteEntityActionManifests, ...duplicateEntityActionManifests, + ...hasChildrenManifests, ]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icon-dictionary.json b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icon-dictionary.json index be201e346b48..fec3dac7ac53 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icon-dictionary.json +++ b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icon-dictionary.json @@ -1387,6 +1387,10 @@ "file": "phone.svg", "legacy": true }, + { + "name": "icon-omega", + "file": "omega.svg" + }, { "name": "icon-operator", "file": "user-cog.svg" diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons.ts b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons.ts index ab93598d5fa3..4302b267ac61 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons.ts @@ -1123,6 +1123,9 @@ legacy: true, hidden: true, path: () => import("./icons/icon-old-phone.js"), },{ +name: "icon-omega", +path: () => import("./icons/icon-omega.js"), +},{ name: "icon-operator", path: () => import("./icons/icon-operator.js"), },{ diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-omega.ts b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-omega.ts new file mode 100644 index 000000000000..1579666da668 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-omega.ts @@ -0,0 +1 @@ +export default `<svg xmlns="http://www.w3.org/2000/svg" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.75" class="lucide lucide-omega" viewBox="0 0 24 24"><path d="M3 20h4.5a.5.5 0 0 0 .5-.5v-.282a.52.52 0 0 0-.247-.437 8 8 0 1 1 8.494-.001.52.52 0 0 0-.247.438v.282a.5.5 0 0 0 .5.5H21"/></svg>`; \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/events/property-value-change.event.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/events/property-value-change.event.ts index 7f2e807d763a..2c9a41d3b2e9 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/events/property-value-change.event.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/events/property-value-change.event.ts @@ -1,6 +1,16 @@ -export class UmbPropertyValueChangeEvent extends Event { - public constructor() { - // mimics the native change event - super('change', { bubbles: true, composed: false, cancelable: false }); +import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; +import { UmbDeprecation } from '@umbraco-cms/backoffice/utils'; + +/** + * @deprecated Use UmbChangeEvent instead, this will be removed as of v.18.0.0 + */ +export class UmbPropertyValueChangeEvent extends UmbChangeEvent { + constructor() { + super(); + new UmbDeprecation({ + removeInVersion: '18.0.0', + deprecated: 'UmbPropertyValueChangeEvent', + solution: 'Use UmbChangeEvent instead', + }).warn(); } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/ui-picker-modal/property-editor-ui-picker-modal.token.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/ui-picker-modal/property-editor-ui-picker-modal.token.ts index d6aacdea4bf2..9ffc832a142a 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/ui-picker-modal/property-editor-ui-picker-modal.token.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/ui-picker-modal/property-editor-ui-picker-modal.token.ts @@ -1,9 +1,6 @@ import { UmbModalToken } from '@umbraco-cms/backoffice/modal'; -export interface UmbPropertyEditorUIPickerModalData { - /** @deprecated This property will be removed in Umbraco 15. */ - submitLabel?: string; -} +export type UmbPropertyEditorUIPickerModalData = object; export type UmbPropertyEditorUIPickerModalValue = { selection: Array<string>; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/recycle-bin/tree/tree-item/recycle-bin-tree-item.context.ts b/src/Umbraco.Web.UI.Client/src/packages/core/recycle-bin/tree/tree-item/recycle-bin-tree-item.context.ts index ff1156ed0bbc..c0091158e80a 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/recycle-bin/tree/tree-item/recycle-bin-tree-item.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/recycle-bin/tree/tree-item/recycle-bin-tree-item.context.ts @@ -35,7 +35,7 @@ export class UmbRecycleBinTreeItemContext< const supportedEntityTypes = this.getManifest()?.meta.supportedEntityTypes; if (!supportedEntityTypes) { - throw new Error('Supported entity types are missing from the manifest. (manifest.meta.supportedEntityTypes)'); + throw new Error('Entity types are missing from the manifest (manifest.meta.supportedEntityTypes).'); } if (supportedEntityTypes.includes(entityType)) { diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/section/components/input-section/input-section.context.ts b/src/Umbraco.Web.UI.Client/src/packages/core/section/components/input-section/input-section.context.ts index 269af4d7f45c..adc42d14d7fb 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/section/components/input-section/input-section.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/section/components/input-section/input-section.context.ts @@ -9,6 +9,3 @@ export class UmbSectionPickerInputContext extends UmbPickerInputContext<UmbSecti super(host, UMB_SECTION_ITEM_REPOSITORY_ALIAS, UMB_SECTION_PICKER_MODAL); } } - -/** @deprecated Use `UmbSectionPickerInputContext` instead. This method will be removed in Umbraco 15. */ -export { UmbSectionPickerInputContext as UmbSectionPickerContext }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/tree/entity-actions/move/move-to.action.ts b/src/Umbraco.Web.UI.Client/src/packages/core/tree/entity-actions/move/move-to.action.ts index e6921c7c196d..239ddb10b3b8 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/tree/entity-actions/move/move-to.action.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/tree/entity-actions/move/move-to.action.ts @@ -16,6 +16,7 @@ export class UmbMoveToEntityAction extends UmbEntityActionBase<MetaEntityActionM data: { treeAlias: this.args.meta.treeAlias, foldersOnly: this.args.meta.foldersOnly, + pickableFilter: (treeItem) => treeItem.unique !== this.args.unique, }, }); diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/tree/folder/entity-action/delete-folder/delete-folder.action.ts b/src/Umbraco.Web.UI.Client/src/packages/core/tree/folder/entity-action/delete-folder/delete-folder.action.ts index f076800505e0..4947ccee23d0 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/tree/folder/entity-action/delete-folder/delete-folder.action.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/tree/folder/entity-action/delete-folder/delete-folder.action.ts @@ -40,7 +40,7 @@ export class UmbDeleteFolderEntityAction extends UmbEntityActionBase<MetaEntityA if (folder) { // TODO: maybe we can show something about how many items are part of the folder? await umbConfirmModal(this._host, { - headline: `Delete folder ${folder.name}`, + headline: `Delete ${folder.name}`, content: 'Are you sure you want to delete this folder?', color: 'danger', confirmLabel: 'Delete', diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-item/tree-item-base/tree-item-context-base.ts b/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-item/tree-item-base/tree-item-context-base.ts index cd3e085fa2d8..93844ec277a7 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-item/tree-item-base/tree-item-context-base.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-item/tree-item-base/tree-item-context-base.ts @@ -12,6 +12,7 @@ import { UMB_SECTION_CONTEXT, UMB_SECTION_SIDEBAR_CONTEXT } from '@umbraco-cms/b import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; import { UMB_ACTION_EVENT_CONTEXT } from '@umbraco-cms/backoffice/action'; import { + UmbHasChildrenEntityContext, UmbRequestReloadChildrenOfEntityEvent, UmbRequestReloadStructureForEntityEvent, } from '@umbraco-cms/backoffice/entity-action'; @@ -75,6 +76,8 @@ export abstract class UmbTreeItemContextBase< #sectionSidebarContext?: typeof UMB_SECTION_SIDEBAR_CONTEXT.TYPE; #actionEventContext?: typeof UMB_ACTION_EVENT_CONTEXT.TYPE; + #hasChildrenContext = new UmbHasChildrenEntityContext(this); + // TODO: get this from the tree context #paging = { skip: 0, @@ -128,7 +131,10 @@ export abstract class UmbTreeItemContextBase< if (!treeItem.entityType) throw new Error('Could not create tree item context, tree item type is missing'); this.entityType = treeItem.entityType; - this.#hasChildren.setValue(treeItem.hasChildren || false); + const hasChildren = treeItem.hasChildren || false; + this.#hasChildren.setValue(hasChildren); + this.#hasChildrenContext.setHasChildren(hasChildren); + this._treeItem.setValue(treeItem); // Update observers: @@ -184,7 +190,10 @@ export abstract class UmbTreeItemContextBase< this.#childItems.setValue(data.items); } - this.#hasChildren.setValue(data.total > 0); + const hasChildren = data.total > 0; + this.#hasChildren.setValue(hasChildren); + this.#hasChildrenContext.setHasChildren(hasChildren); + this.pagination.setTotalItems(data.total); } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-picker-modal/tree-picker-modal.token.ts b/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-picker-modal/tree-picker-modal.token.ts index 4d98367e3497..7bb443bad887 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-picker-modal/tree-picker-modal.token.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-picker-modal/tree-picker-modal.token.ts @@ -1,4 +1,4 @@ -import type { UmbTreeStartNode } from '../types.js'; +import type { UmbTreeItemModel, UmbTreeStartNode } from '../types.js'; import { UMB_TREE_PICKER_MODAL_ALIAS } from './constants.js'; import type { UmbPickerModalData, UmbPickerModalValue } from '@umbraco-cms/backoffice/modal'; import type { UmbWorkspaceModalData } from '@umbraco-cms/backoffice/workspace'; @@ -14,7 +14,7 @@ export interface UmbTreePickerModalCreateActionData<PathPatternParamsType extend } export interface UmbTreePickerModalData< - TreeItemType, + TreeItemType = UmbTreeItemModel, PathPatternParamsType extends UmbPathPatternParamsType = UmbPathPatternParamsType, > extends UmbPickerModalData<TreeItemType> { hideTreeRoot?: boolean; @@ -28,7 +28,7 @@ export interface UmbTreePickerModalData< // eslint-disable-next-line @typescript-eslint/no-empty-object-type export interface UmbTreePickerModalValue extends UmbPickerModalValue {} -export const UMB_TREE_PICKER_MODAL = new UmbModalToken<UmbTreePickerModalData<unknown>, UmbTreePickerModalValue>( +export const UMB_TREE_PICKER_MODAL = new UmbModalToken<UmbTreePickerModalData, UmbTreePickerModalValue>( UMB_TREE_PICKER_MODAL_ALIAS, { modal: { diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-action-menu/workspace-action-menu.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-action-menu/workspace-action-menu.element.ts index 9f1e456f55fa..5085ef7a3c41 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-action-menu/workspace-action-menu.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-action-menu/workspace-action-menu.element.ts @@ -27,34 +27,32 @@ export class UmbWorkspaceActionMenuElement extends UmbLitElement { } override render() { - return this.items?.length - ? html` - <uui-button - id="popover-trigger" - popovertarget="workspace-action-popover" - look="${this.look}" - color="${this.color}" - label=${this.localize.term('visuallyHiddenTexts_tabExpand')} - compact> - <uui-symbol-expand id="expand-symbol" .open=${this._popoverOpen}></uui-symbol-expand> - </uui-button> - <uui-popover-container - id="workspace-action-popover" - margin="6" - placement="top-end" - @toggle=${this.#onPopoverToggle}> - <umb-popover-layout> - <uui-scroll-container> - ${repeat( - this.items, - (ext) => ext.alias, - (ext) => ext.component, - )} - </uui-scroll-container> - </umb-popover-layout> - </uui-popover-container> - ` - : nothing; + if (!this.items?.length) return nothing; + + return html`<uui-button + id="popover-trigger" + popovertarget="workspace-action-popover" + look="${this.look}" + color="${this.color}" + label=${this.localize.term('visuallyHiddenTexts_tabExpand')} + compact> + <uui-symbol-expand id="expand-symbol" .open=${this._popoverOpen}></uui-symbol-expand> + </uui-button> + <uui-popover-container + id="workspace-action-popover" + margin="6" + placement="top-end" + @toggle=${this.#onPopoverToggle}> + <umb-popover-layout> + <uui-scroll-container> + ${repeat( + this.items, + (ext) => ext.alias, + (ext) => ext.component, + )} + </uui-scroll-container> + </umb-popover-layout> + </uui-popover-container>`; } static override styles = [ diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-action/default/workspace-action-default-kind.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-action/default/workspace-action-default-kind.element.ts index 166a57e22f20..54b4a164dda3 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-action/default/workspace-action-default-kind.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-action/default/workspace-action-default-kind.element.ts @@ -13,6 +13,7 @@ import { type UmbExtensionElementAndApiInitializer, UmbExtensionsElementAndApiInitializer, } from '@umbraco-cms/backoffice/extension-api'; +import { stringOrStringArrayIntersects } from '@umbraco-cms/backoffice/utils'; import '../../workspace-action-menu/index.js'; @@ -38,7 +39,6 @@ export class UmbWorkspaceActionElement< this._href = value?.meta.href; this._additionalOptions = value?.meta.additionalOptions; this.#createAliases(); - this.requestUpdate('manifest', oldValue); } } public get manifest() { @@ -90,7 +90,10 @@ export class UmbWorkspaceActionElement< // TODO: This works on one level for now, which will be enough for the current use case. However, you can overwrite the overwrites, so we need to make this recursive. Perhaps we could move this to the extensions initializer. // Add overwrites so that we can show any previously registered actions on the original workspace action if (this.#manifest.overwrites) { - for (const alias of this.#manifest.overwrites) { + const overwrites = Array.isArray(this.#manifest.overwrites) + ? this.#manifest.overwrites + : [this.#manifest.overwrites]; + for (const alias of overwrites) { aliases.add(alias); } } @@ -145,11 +148,7 @@ export class UmbWorkspaceActionElement< umbExtensionsRegistry, 'workspaceActionMenuItem', ExtensionApiArgsMethod, - (action) => { - return Array.isArray(action.forWorkspaceActions) - ? action.forWorkspaceActions.some((alias) => aliases.includes(alias)) - : aliases.includes(action.forWorkspaceActions); - }, + (action) => stringOrStringArrayIntersects(action.forWorkspaceActions, aliases), (extensionControllers) => { this._items = extensionControllers; }, diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/conditions/const.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/conditions/const.ts index b52165146a63..6aa130abce38 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/conditions/const.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/conditions/const.ts @@ -3,23 +3,11 @@ */ export const UMB_WORKSPACE_HAS_COLLECTION_CONDITION_ALIAS = 'Umb.Condition.WorkspaceHasCollection'; -/** - * [Deprecated] Workspace has collection condition alias - * @deprecated Use {UMB_WORKSPACE_HAS_COLLECTION_CONDITION_ALIAS} instead. This will be removed in Umbraco 16. - */ -export const UMB_WORKSPACE_HAS_COLLECTION_CONDITION = UMB_WORKSPACE_HAS_COLLECTION_CONDITION_ALIAS; - /** * Workspace entity is new condition alias */ export const UMB_WORKSPACE_ENTITY_IS_NEW_CONDITION_ALIAS = 'Umb.Condition.WorkspaceEntityIsNew'; -/** - * [Deprecated] Workspace entity is new condition alias - * @deprecated Use {UMB_WORKSPACE_ENTITY_IS_NEW_CONDITION_ALIAS} instead. This will be removed in Umbraco 16. - */ -export const UMB_WORKSPACE_ENTITY_IS_NEW_CONDITION = UMB_WORKSPACE_ENTITY_IS_NEW_CONDITION_ALIAS; - /** * Workspace alias condition alias */ diff --git a/src/Umbraco.Web.UI.Client/src/packages/data-type/components/data-type-input/data-type-input.context.ts b/src/Umbraco.Web.UI.Client/src/packages/data-type/components/data-type-input/data-type-input.context.ts index b84dc0a56762..cab4a5a0017b 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/data-type/components/data-type-input/data-type-input.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/data-type/components/data-type-input/data-type-input.context.ts @@ -16,6 +16,3 @@ export class UmbDataTypePickerInputContext extends UmbPickerInputContext< super(host, UMB_DATA_TYPE_ITEM_REPOSITORY_ALIAS, UMB_DATA_TYPE_PICKER_MODAL); } } - -/** @deprecated Use `UmbDataTypePickerInputContext` instead. This method will be removed in Umbraco 15. */ -export { UmbDataTypePickerInputContext as UmbDataTypePickerContext }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/data-type/modals/data-type-picker-flow/data-type-picker-flow-modal.token.ts b/src/Umbraco.Web.UI.Client/src/packages/data-type/modals/data-type-picker-flow/data-type-picker-flow-modal.token.ts index 55a1643d9028..cc757c7263b7 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/data-type/modals/data-type-picker-flow/data-type-picker-flow-modal.token.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/data-type/modals/data-type-picker-flow/data-type-picker-flow-modal.token.ts @@ -1,9 +1,6 @@ import { UmbModalToken } from '@umbraco-cms/backoffice/modal'; -export interface UmbDataTypePickerFlowModalData { - /** @deprecated This property will be removed in Umbraco 15. */ - submitLabel?: string; -} +export type UmbDataTypePickerFlowModalData = object; export type UmbDataTypePickerFlowModalValue = { selection: Array<string>; diff --git a/src/Umbraco.Web.UI.Client/src/packages/dictionary/components/input-dictionary/input-dictionary.context.ts b/src/Umbraco.Web.UI.Client/src/packages/dictionary/components/input-dictionary/input-dictionary.context.ts index 0dfd2bf9d1bf..23653d4142d1 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/dictionary/components/input-dictionary/input-dictionary.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/dictionary/components/input-dictionary/input-dictionary.context.ts @@ -19,6 +19,3 @@ export class UmbDictionaryPickerInputContext extends UmbPickerInputContext< super(host, UMB_DICTIONARY_ITEM_REPOSITORY_ALIAS, UMB_DICTIONARY_PICKER_MODAL); } } - -/** @deprecated Use `UmbDictionaryPickerInputContext` instead. This method will be removed in Umbraco 15. */ -export { UmbDictionaryPickerInputContext as UmbDictionaryPickerContext }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/dictionary/entity-action/import/import-dictionary-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/dictionary/entity-action/import/import-dictionary-modal.element.ts index 9e4f850bb3ce..cb8b9f118668 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/dictionary/entity-action/import/import-dictionary-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/dictionary/entity-action/import/import-dictionary-modal.element.ts @@ -175,14 +175,14 @@ export class UmbImportDictionaryModalLayout extends UmbModalBaseElement< <uui-form> <form id="form" name="form" @submit=${this.#onUpload}> <uui-form-layout-item> - <uui-label for="file" slot="label" required>${this.localize.term('formFileUpload_pickFile')}</uui-label> + <uui-label for="file" slot="label" required>${this.localize.term('dictionary_pickFile')}</uui-label> <uui-input-file accept=".udt" name="file" id="file" @input=${this.#onFileInput} required - required-message=${this.localize.term('formFileUpload_pickFile')}></uui-input-file> + required-message=${this.localize.term('dictionary_pickFileRequired')}></uui-input-file> </uui-form-layout-item> </form> </uui-form>`; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/components/input-document-type/input-document-type.context.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/components/input-document-type/input-document-type.context.ts index 0d6b3ec2daf8..48d5473bce10 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/components/input-document-type/input-document-type.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/components/input-document-type/input-document-type.context.ts @@ -16,6 +16,3 @@ export class UmbDocumentTypePickerInputContext extends UmbPickerInputContext< super(host, UMB_DOCUMENT_TYPE_ITEM_REPOSITORY_ALIAS, UMB_DOCUMENT_TYPE_PICKER_MODAL); } } - -/** @deprecated Use `UmbDocumentTypePickerInputContext` instead. This method will be removed in Umbraco 15. */ -export { UmbDocumentTypePickerInputContext as UmbDocumentTypePickerContext }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/property-editors/document-type-picker/property-editor-ui-document-type-picker.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/property-editors/document-type-picker/property-editor-ui-document-type-picker.element.ts index 22a637de146f..0edf6c84821f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/property-editors/document-type-picker/property-editor-ui-document-type-picker.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/property-editors/document-type-picker/property-editor-ui-document-type-picker.element.ts @@ -1,7 +1,7 @@ +import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; import type { UmbInputDocumentTypeElement } from '../../components/input-document-type/input-document-type.element.js'; import { html, customElement, property, state } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; -import { UmbPropertyValueChangeEvent } from '@umbraco-cms/backoffice/property-editor'; import type { UmbNumberRangeValueType } from '@umbraco-cms/backoffice/models'; import type { UmbPropertyEditorConfigCollection, @@ -38,7 +38,7 @@ export class UmbPropertyEditorUIDocumentTypePickerElement extends UmbLitElement #onChange(event: CustomEvent & { target: UmbInputDocumentTypeElement }) { this.value = event.target.value; - this.dispatchEvent(new UmbPropertyValueChangeEvent()); + this.dispatchEvent(new UmbChangeEvent()); } override render() { diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/structure/document-type-structure.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/structure/document-type-structure.server.data-source.ts index 808fd08c6bb3..7fe548819555 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/structure/document-type-structure.server.data-source.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/structure/document-type-structure.server.data-source.ts @@ -7,7 +7,7 @@ import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; /** * - + * @class UmbDocumentTypeStructureServerDataSource * @augments {UmbContentTypeStructureServerDataSourceBase} */ @@ -20,10 +20,10 @@ export class UmbDocumentTypeStructureServerDataSource extends UmbContentTypeStru } } -const getAllowedChildrenOf = (unique: string | null) => { +const getAllowedChildrenOf = (unique: string | null, parentContentUnique: string | null) => { if (unique) { // eslint-disable-next-line local-rules/no-direct-api-import - return DocumentTypeService.getDocumentTypeByIdAllowedChildren({ id: unique }); + return DocumentTypeService.getDocumentTypeByIdAllowedChildren({ id: unique, parentContentKey: parentContentUnique ?? undefined }); } else { // eslint-disable-next-line local-rules/no-direct-api-import return DocumentTypeService.getDocumentTypeAllowedAtRoot({}); diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/workspace/document-type/document-type-workspace-editor.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/workspace/document-type/document-type-workspace-editor.element.ts index be615b022711..64fa61eb296a 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/workspace/document-type/document-type-workspace-editor.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/workspace/document-type/document-type-workspace-editor.element.ts @@ -1,110 +1,12 @@ -import { UMB_DOCUMENT_TYPE_WORKSPACE_CONTEXT } from './document-type-workspace.context-token.js'; -import type { UmbInputWithAliasElement } from '@umbraco-cms/backoffice/components'; -import { umbFocus, UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; -import { css, html, customElement, state, ifDefined } from '@umbraco-cms/backoffice/external/lit'; -import { UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal'; -import { UMB_ICON_PICKER_MODAL } from '@umbraco-cms/backoffice/icon'; -import type { UUITextareaElement } from '@umbraco-cms/backoffice/external/uui'; -import { umbBindToValidation } from '@umbraco-cms/backoffice/validation'; +import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; +import { css, html, customElement } from '@umbraco-cms/backoffice/external/lit'; @customElement('umb-document-type-workspace-editor') export class UmbDocumentTypeWorkspaceEditorElement extends UmbLitElement { - @state() - private _name?: string; - - @state() - private _alias?: string; - - @state() - private _description?: string; - - @state() - private _icon?: string; - - @state() - private _isNew?: boolean; - - #workspaceContext?: typeof UMB_DOCUMENT_TYPE_WORKSPACE_CONTEXT.TYPE; - - constructor() { - super(); - - this.consumeContext(UMB_DOCUMENT_TYPE_WORKSPACE_CONTEXT, (instance) => { - this.#workspaceContext = instance; - this.#observeDocumentType(); - }); - } - - #observeDocumentType() { - if (!this.#workspaceContext) return; - this.observe(this.#workspaceContext.name, (name) => (this._name = name), '_observeName'); - this.observe(this.#workspaceContext.alias, (alias) => (this._alias = alias), '_observeAlias'); - this.observe( - this.#workspaceContext.description, - (description) => (this._description = description), - '_observeDescription', - ); - this.observe(this.#workspaceContext.icon, (icon) => (this._icon = icon), '_observeIcon'); - this.observe(this.#workspaceContext.isNew, (isNew) => (this._isNew = isNew), '_observeIsNew'); - } - - private async _handleIconClick() { - const [alias, color] = this._icon?.replace('color-', '')?.split(' ') ?? []; - const modalManager = await this.getContext(UMB_MODAL_MANAGER_CONTEXT); - const modalContext = modalManager.open(this, UMB_ICON_PICKER_MODAL, { - value: { - icon: alias, - color: color, - }, - }); - - modalContext?.onSubmit().then((saved) => { - if (saved.icon && saved.color) { - this.#workspaceContext?.setIcon(`${saved.icon} color-${saved.color}`); - } else if (saved.icon) { - this.#workspaceContext?.setIcon(saved.icon); - } - }); - } - - #onNameAndAliasChange(event: InputEvent & { target: UmbInputWithAliasElement }) { - this.#workspaceContext?.setName(event.target.value ?? ''); - this.#workspaceContext?.setAlias(event.target.alias ?? ''); - } - - #onDescriptionChange(event: InputEvent & { target: UUITextareaElement }) { - this.#workspaceContext?.setDescription(event.target.value.toString() ?? ''); - } - override render() { return html` <umb-entity-detail-workspace-editor> - <div id="header" slot="header"> - <uui-button id="icon" compact label="icon" look="outline" @click=${this._handleIconClick}> - <umb-icon name=${ifDefined(this._icon)}></umb-icon> - </uui-button> - - <div id="editors"> - <umb-input-with-alias - id="name" - label=${this.localize.term('placeholders_entername')} - .value=${this._name} - .alias=${this._alias} - ?auto-generate-alias=${this._isNew} - @change=${this.#onNameAndAliasChange} - required - ${umbBindToValidation(this, '$.name', this._name)} - ${umbFocus()}> - </umb-input-with-alias> - - <uui-input - id="description" - .label=${this.localize.term('placeholders_enterDescription')} - .value=${this._description} - .placeholder=${this.localize.term('placeholders_enterDescription')} - @input=${this.#onDescriptionChange}></uui-input> - </div> - </div> + <umb-content-type-workspace-editor-header slot="header"></umb-content-type-workspace-editor-header> </umb-entity-detail-workspace-editor> `; } @@ -116,39 +18,6 @@ export class UmbDocumentTypeWorkspaceEditorElement extends UmbLitElement { width: 100%; height: 100%; } - - #header { - display: flex; - flex: 1 1 auto; - gap: var(--uui-size-space-2); - } - - #editors { - display: flex; - flex: 1 1 auto; - flex-direction: column; - gap: var(--uui-size-space-1); - } - - #name { - width: 100%; - } - - #description { - width: 100%; - --uui-input-height: var(--uui-size-8); - --uui-input-border-color: transparent; - } - - #description:hover { - --uui-input-border-color: var(--uui-color-border); - } - - #icon { - font-size: var(--uui-size-8); - height: 60px; - width: 60px; - } `, ]; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/action/create-document-collection-action.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/action/create-document-collection-action.element.ts index 25b50fd7d89a..b3cf273c8fd3 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/action/create-document-collection-action.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/action/create-document-collection-action.element.ts @@ -56,12 +56,12 @@ export class UmbCreateDocumentCollectionActionElement extends UmbLitElement { override async firstUpdated() { if (this._documentTypeUnique) { - this.#retrieveAllowedDocumentTypesOf(this._documentTypeUnique); + this.#retrieveAllowedDocumentTypesOf(this._documentTypeUnique, this._documentUnique || null); } } - async #retrieveAllowedDocumentTypesOf(unique: string | null) { - const { data } = await this.#documentTypeStructureRepository.requestAllowedChildrenOf(unique); + async #retrieveAllowedDocumentTypesOf(unique: string | null, parentContentUnique: string | null) { + const { data } = await this.#documentTypeStructureRepository.requestAllowedChildrenOf(unique, parentContentUnique); if (data && data.items) { this._allowedDocumentTypes = data.items; @@ -69,7 +69,7 @@ export class UmbCreateDocumentCollectionActionElement extends UmbLitElement { } #onPopoverToggle(event: ToggleEvent) { - // TODO: This ignorer is just neede for JSON SCHEMA TO WORK, As its not updated with latest TS jet. + // TODO: This ignorer is just needed for JSON SCHEMA TO WORK, As its not updated with latest TS jet. // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore this._popoverOpen = event.newState === 'open'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/input-document/input-document.context.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/input-document/input-document.context.ts index 6d9f07bc551a..dd2238e3dc1b 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/input-document/input-document.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/input-document/input-document.context.ts @@ -61,6 +61,3 @@ export class UmbDocumentPickerInputContext extends UmbPickerInputContext< return true; }; } - -/** @deprecated Use `UmbDocumentPickerInputContext` instead. This method will be removed in Umbraco 15. */ -export { UmbDocumentPickerInputContext as UmbDocumentPickerContext }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/create/document-create-options-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/create/document-create-options-modal.element.ts index 1dbf684c995d..7418d23371fe 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/create/document-create-options-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/create/document-create-options-modal.element.ts @@ -47,15 +47,15 @@ export class UmbDocumentCreateOptionsModalElement extends UmbModalBaseElement< const parentUnique = this.data?.parent.unique; const documentTypeUnique = this.data?.documentType?.unique || null; - this.#retrieveAllowedDocumentTypesOf(documentTypeUnique); + this.#retrieveAllowedDocumentTypesOf(documentTypeUnique, parentUnique || null); if (parentUnique) { this.#retrieveHeadline(parentUnique); } } - async #retrieveAllowedDocumentTypesOf(unique: string | null) { - const { data } = await this.#documentTypeStructureRepository.requestAllowedChildrenOf(unique); + async #retrieveAllowedDocumentTypesOf(unique: string | null, parentContentUnique: string | null) { + const { data } = await this.#documentTypeStructureRepository.requestAllowedChildrenOf(unique, parentContentUnique); if (data) { // TODO: implement pagination, or get 1000? diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/property-editors/document-picker/property-editor-ui-document-picker.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/property-editors/document-picker/property-editor-ui-document-picker.element.ts index 07ac7c3c77c8..8afb3dc8ba4f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/property-editors/document-picker/property-editor-ui-document-picker.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/property-editors/document-picker/property-editor-ui-document-picker.element.ts @@ -1,8 +1,8 @@ +import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; import type { UmbInputDocumentElement } from '../../components/input-document/input-document.element.js'; import { UMB_DOCUMENT_ENTITY_TYPE } from '../../entity.js'; import { html, customElement, property, state } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; -import { UmbPropertyValueChangeEvent } from '@umbraco-cms/backoffice/property-editor'; import type { UmbNumberRangeValueType } from '@umbraco-cms/backoffice/models'; import type { UmbPropertyEditorConfigCollection, @@ -53,7 +53,7 @@ export class UmbPropertyEditorUIDocumentPickerElement extends UmbLitElement impl #onChange(event: CustomEvent & { target: UmbInputDocumentElement }) { this.value = event.target.value; - this.dispatchEvent(new UmbPropertyValueChangeEvent()); + this.dispatchEvent(new UmbChangeEvent()); } override render() { diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish-with-descendants/modal/document-publish-with-descendants-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish-with-descendants/modal/document-publish-with-descendants-modal.element.ts index 0705b49b74ce..f5f84f3290f5 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish-with-descendants/modal/document-publish-with-descendants-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish-with-descendants/modal/document-publish-with-descendants-modal.element.ts @@ -5,7 +5,7 @@ import type { UmbDocumentPublishWithDescendantsModalValue, } from './document-publish-with-descendants-modal.token.js'; import { css, customElement, html, state } from '@umbraco-cms/backoffice/external/lit'; -import { umbConfirmModal, UmbModalBaseElement } from '@umbraco-cms/backoffice/modal'; +import { UmbModalBaseElement } from '@umbraco-cms/backoffice/modal'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import { UmbSelectionManager } from '@umbraco-cms/backoffice/utils'; @@ -18,7 +18,6 @@ export class UmbDocumentPublishWithDescendantsModalElement extends UmbModalBaseE > { #selectionManager = new UmbSelectionManager<string>(this); #includeUnpublishedDescendants = false; - #forceRepublish = false; @state() _options: Array<UmbDocumentVariantOptionModel> = []; @@ -84,25 +83,11 @@ export class UmbDocumentPublishWithDescendantsModalElement extends UmbModalBaseE this.#includeUnpublishedDescendants = !this.#includeUnpublishedDescendants; } - async #onForceRepublishChange() { - this.#forceRepublish = !this.#forceRepublish; - } - async #submit() { - if (this.#forceRepublish) { - await umbConfirmModal(this, { - headline: this.localize.term('content_forceRepublishWarning'), - content: this.localize.term('content_forceRepublishAdvisory'), - color: 'warning', - confirmLabel: this.localize.term('actions_publish'), - }); - } - this.value = { selection: this.#selectionManager.getSelection(), includeUnpublishedDescendants: this.#includeUnpublishedDescendants, - forceRepublish: this.#forceRepublish, }; this.modalContext?.submit(); } @@ -143,14 +128,6 @@ export class UmbDocumentPublishWithDescendantsModalElement extends UmbModalBaseE @change=${this.#onIncludeUnpublishedDescendantsChange}></uui-toggle> </uui-form-layout-item> - <uui-form-layout-item> - <uui-toggle - id="forceRepublish" - label=${this.localize.term('content_forceRepublish')} - ?checked=${this.value?.forceRepublish} - @change=${this.#onForceRepublishChange}></uui-toggle> - </uui-form-layout-item> - <div slot="actions"> <uui-button label=${this.localize.term('general_close')} @click=${this.#close}></uui-button> <uui-button diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish-with-descendants/modal/document-publish-with-descendants-modal.token.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish-with-descendants/modal/document-publish-with-descendants-modal.token.ts index 5a24079ea707..3e25770093df 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish-with-descendants/modal/document-publish-with-descendants-modal.token.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish-with-descendants/modal/document-publish-with-descendants-modal.token.ts @@ -8,7 +8,6 @@ export interface UmbDocumentPublishWithDescendantsModalData extends UmbDocumentV export interface UmbDocumentPublishWithDescendantsModalValue extends UmbDocumentVariantPickerValue { includeUnpublishedDescendants?: boolean; - forceRepublish?: boolean; } export const UMB_DOCUMENT_PUBLISH_WITH_DESCENDANTS_MODAL = new UmbModalToken< diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/repository/document-publishing.repository.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/repository/document-publishing.repository.ts index 2ed98a2e05ce..7599d8a763f2 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/repository/document-publishing.repository.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/repository/document-publishing.repository.ts @@ -71,14 +71,12 @@ export class UmbDocumentPublishingRepository extends UmbRepositoryBase { * @param id * @param variantIds * @param includeUnpublishedDescendants - * @param forceRepublish * @memberof UmbDocumentPublishingRepository */ async publishWithDescendants( id: string, variantIds: Array<UmbVariantId>, includeUnpublishedDescendants: boolean, - forceRepublish: boolean, ) { if (!id) throw new Error('id is missing'); if (!variantIds) throw new Error('variant IDs are missing'); @@ -88,7 +86,6 @@ export class UmbDocumentPublishingRepository extends UmbRepositoryBase { id, variantIds, includeUnpublishedDescendants, - forceRepublish, ); if (!error) { diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/repository/document-publishing.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/repository/document-publishing.server.data-source.ts index dcb0c72d76ab..d4a12593df2f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/repository/document-publishing.server.data-source.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/repository/document-publishing.server.data-source.ts @@ -92,21 +92,18 @@ export class UmbDocumentPublishingServerDataSource { * @param unique * @param variantIds * @param includeUnpublishedDescendants - * @param forceRepublish * @memberof UmbDocumentPublishingServerDataSource */ async publishWithDescendants( unique: string, variantIds: Array<UmbVariantId>, includeUnpublishedDescendants: boolean, - forceRepublish: boolean, ) { if (!unique) throw new Error('Id is missing'); const requestBody: PublishDocumentWithDescendantsRequestModel = { cultures: variantIds.map((variant) => variant.toCultureString()), includeUnpublishedDescendants, - forceRepublish, }; return tryExecuteAndNotify( diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/workspace-context/document-publishing.workspace-context.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/workspace-context/document-publishing.workspace-context.ts index 2d2c9940bfb7..c2d59220e30b 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/workspace-context/document-publishing.workspace-context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/workspace-context/document-publishing.workspace-context.ts @@ -229,7 +229,6 @@ export class UmbDocumentPublishingWorkspaceContext extends UmbContextBase<UmbDoc unique, variantIds, result.includeUnpublishedDescendants ?? false, - result.forceRepublish ?? false, ); if (!error) { diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/recycle-bin/entity-action/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/recycle-bin/entity-action/manifests.ts index 6c4cf7b4b217..cc11edeb2255 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/recycle-bin/entity-action/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/recycle-bin/entity-action/manifests.ts @@ -12,6 +12,7 @@ import { UMB_ENTITY_IS_NOT_TRASHED_CONDITION_ALIAS, UMB_ENTITY_IS_TRASHED_CONDITION_ALIAS, } from '@umbraco-cms/backoffice/recycle-bin'; +import { UMB_ENTITY_HAS_CHILDREN_CONDITION_ALIAS } from '@umbraco-cms/backoffice/entity-action'; export const manifests: Array<UmbExtensionManifest> = [ { @@ -70,6 +71,9 @@ export const manifests: Array<UmbExtensionManifest> = [ alias: 'Umb.Condition.UserPermission.Document', allOf: [UMB_USER_PERMISSION_DOCUMENT_DELETE], }, + { + alias: UMB_ENTITY_HAS_CHILDREN_CONDITION_ALIAS, + }, ], }, ...bulkTrashManifests, diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/rollback/entity-action/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/rollback/entity-action/manifests.ts index 2f736381e681..24b0612e073e 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/rollback/entity-action/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/rollback/entity-action/manifests.ts @@ -13,6 +13,7 @@ export const manifests: Array<UmbExtensionManifest> = [ meta: { icon: 'icon-history', label: '#actions_rollback', + additionalOptions: true, }, conditions: [ { diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts index 547247c7b746..2134817942f8 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts @@ -35,7 +35,7 @@ import { import type { UmbDocumentTypeDetailModel } from '@umbraco-cms/backoffice/document-type'; import { UmbIsTrashedEntityContext } from '@umbraco-cms/backoffice/recycle-bin'; import { UMB_APP_CONTEXT } from '@umbraco-cms/backoffice/app'; -import { UmbDeprecation } from '@umbraco-cms/backoffice/utils'; +import { ensurePathEndsWithSlash, UmbDeprecation } from '@umbraco-cms/backoffice/utils'; import { createExtensionApiByAlias } from '@umbraco-cms/backoffice/extension-registry'; type ContentModel = UmbDocumentDetailModel; @@ -289,7 +289,8 @@ export class UmbDocumentWorkspaceContext const appContext = await this.getContext(UMB_APP_CONTEXT); - const previewUrl = new URL(appContext.getBackofficePath() + '/preview', appContext.getServerUrl()); + const backofficePath = appContext.getBackofficePath(); + const previewUrl = new URL(ensurePathEndsWithSlash(backofficePath) + 'preview', appContext.getServerUrl()); previewUrl.searchParams.set('id', unique); if (culture && culture !== UMB_INVARIANT_CULTURE) { diff --git a/src/Umbraco.Web.UI.Client/src/packages/language/components/input-language/input-language.context.ts b/src/Umbraco.Web.UI.Client/src/packages/language/components/input-language/input-language.context.ts index 5ec78941c01a..caf52fdb90b5 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/language/components/input-language/input-language.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/language/components/input-language/input-language.context.ts @@ -9,6 +9,3 @@ export class UmbLanguagePickerInputContext extends UmbPickerInputContext<UmbLang super(host, UMB_LANGUAGE_ITEM_REPOSITORY_ALIAS, UMB_LANGUAGE_PICKER_MODAL); } } - -/** @deprecated Use `UmbLanguagePickerInputContext` instead. This method will be removed in Umbraco 15. */ -export { UmbLanguagePickerInputContext as UmbLanguagePickerContext }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/log-viewer/workspace/views/search/components/log-viewer-level-tag.element.ts b/src/Umbraco.Web.UI.Client/src/packages/log-viewer/workspace/views/search/components/log-viewer-level-tag.element.ts index c360fa7b2eaa..8b84e93388bf 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/log-viewer/workspace/views/search/components/log-viewer-level-tag.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/log-viewer/workspace/views/search/components/log-viewer-level-tag.element.ts @@ -24,7 +24,7 @@ export class UmbLogViewerLevelTagElement extends LitElement { Error: { look: 'primary', color: 'danger' }, Fatal: { look: 'primary', - style: 'background-color: var(--umb-log-viewer-fatal-color); color: var(--uui-color-surface)', + style: 'background-color: var(--umb-log-viewer-fatal-color)', }, }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/markdown-editor/property-editors/markdown-editor/property-editor-ui-markdown-editor.element.ts b/src/Umbraco.Web.UI.Client/src/packages/markdown-editor/property-editors/markdown-editor/property-editor-ui-markdown-editor.element.ts index 3c54910cd6fb..2307159e453c 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/markdown-editor/property-editors/markdown-editor/property-editor-ui-markdown-editor.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/markdown-editor/property-editors/markdown-editor/property-editor-ui-markdown-editor.element.ts @@ -1,7 +1,6 @@ import type { UmbInputMarkdownElement } from '../../components/input-markdown-editor/index.js'; import { html, customElement, property, state } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; -import { UmbPropertyValueChangeEvent } from '@umbraco-cms/backoffice/property-editor'; import type { UmbPropertyEditorConfigCollection, UmbPropertyEditorUiElement, @@ -9,6 +8,7 @@ import type { import type { UUIModalSidebarSize } from '@umbraco-cms/backoffice/external/uui'; import '../../components/input-markdown-editor/index.js'; +import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; /** * @element umb-property-editor-ui-markdown-editor @@ -42,7 +42,7 @@ export class UmbPropertyEditorUIMarkdownEditorElement extends UmbLitElement impl #onChange(event: Event & { target: UmbInputMarkdownElement }) { this.value = event.target.value as string; - this.dispatchEvent(new UmbPropertyValueChangeEvent()); + this.dispatchEvent(new UmbChangeEvent()); } override render() { diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/components/input-media-type/input-media-type.context.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/components/input-media-type/input-media-type.context.ts index 213ff9ab76bd..608e115fc620 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/components/input-media-type/input-media-type.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/components/input-media-type/input-media-type.context.ts @@ -19,6 +19,3 @@ export class UmbMediaTypePickerInputContext extends UmbPickerInputContext< super(host, UMB_MEDIA_TYPE_ITEM_REPOSITORY_ALIAS, UMB_MEDIA_TYPE_PICKER_MODAL); } } - -/** @deprecated Use `UmbMediaTypePickerInputContext` instead. This method will be removed in Umbraco 15. */ -export { UmbMediaTypePickerInputContext as UmbMediaTypePickerContext }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/property-editors/media-type-picker/property-editor-ui-media-type-picker.element.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/property-editors/media-type-picker/property-editor-ui-media-type-picker.element.ts index 9d327ba17e8c..63b9d3a84762 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/property-editors/media-type-picker/property-editor-ui-media-type-picker.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/property-editors/media-type-picker/property-editor-ui-media-type-picker.element.ts @@ -1,7 +1,7 @@ +import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; import type { UmbInputMediaTypeElement } from '../../components/index.js'; import { html, customElement, property, state } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; -import { UmbPropertyValueChangeEvent } from '@umbraco-cms/backoffice/property-editor'; import type { UmbNumberRangeValueType } from '@umbraco-cms/backoffice/models'; import type { UmbPropertyEditorConfigCollection, @@ -29,7 +29,7 @@ export class UmbPropertyEditorUIMediaTypePickerElement extends UmbLitElement imp #onChange(event: CustomEvent & { target: UmbInputMediaTypeElement }) { this.value = event.target.value; - this.dispatchEvent(new UmbPropertyValueChangeEvent()); + this.dispatchEvent(new UmbChangeEvent()); } override render() { diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/structure/media-type-structure.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/structure/media-type-structure.server.data-source.ts index c3c2f927f6a5..d2384cdde334 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/structure/media-type-structure.server.data-source.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/structure/media-type-structure.server.data-source.ts @@ -26,10 +26,10 @@ export class UmbMediaTypeStructureServerDataSource extends UmbContentTypeStructu } } -const getAllowedChildrenOf = (unique: string | null) => { +const getAllowedChildrenOf = (unique: string | null, parentContentUnique: string | null) => { if (unique) { // eslint-disable-next-line local-rules/no-direct-api-import - return MediaTypeService.getMediaTypeByIdAllowedChildren({ id: unique }); + return MediaTypeService.getMediaTypeByIdAllowedChildren({ id: unique, parentContentKey: parentContentUnique ?? undefined }); } else { // eslint-disable-next-line local-rules/no-direct-api-import return MediaTypeService.getMediaTypeAllowedAtRoot({}); diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/media-type-workspace-editor.element.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/media-type-workspace-editor.element.ts index 10c68fbe8431..8a9fe04b185c 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/media-type-workspace-editor.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/media-type-workspace-editor.element.ts @@ -1,112 +1,12 @@ -import type { UmbMediaTypeWorkspaceContext } from './media-type-workspace.context.js'; -import { UMB_MEDIA_TYPE_WORKSPACE_CONTEXT } from './media-type-workspace.context-token.js'; -import { css, html, customElement, state, ifDefined } from '@umbraco-cms/backoffice/external/lit'; -import { UmbLitElement, umbFocus } from '@umbraco-cms/backoffice/lit-element'; -import { UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal'; -import { UMB_ICON_PICKER_MODAL } from '@umbraco-cms/backoffice/icon'; -import type { UmbInputWithAliasElement } from '@umbraco-cms/backoffice/components'; -import type { UUITextareaElement } from '@umbraco-cms/backoffice/external/uui'; +import { css, html, customElement } from '@umbraco-cms/backoffice/external/lit'; +import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; @customElement('umb-media-type-workspace-editor') export class UmbMediaTypeWorkspaceEditorElement extends UmbLitElement { - @state() - private _name?: string; - - @state() - private _description?: string; - - @state() - private _alias?: string; - - @state() - private _aliasLocked = true; - - @state() - private _icon?: string; - - @state() - private _isNew?: boolean; - - #workspaceContext?: UmbMediaTypeWorkspaceContext; - - constructor() { - super(); - - this.consumeContext(UMB_MEDIA_TYPE_WORKSPACE_CONTEXT, (context) => { - this.#workspaceContext = context; - this.#observeMediaType(); - }); - } - - #observeMediaType() { - if (!this.#workspaceContext) return; - this.observe(this.#workspaceContext.name, (name) => (this._name = name), '_observeName'); - this.observe( - this.#workspaceContext.description, - (description) => (this._description = description), - '_observeDescription', - ); - this.observe(this.#workspaceContext.alias, (alias) => (this._alias = alias), '_observeAlias'); - this.observe(this.#workspaceContext.icon, (icon) => (this._icon = icon), '_observeIcon'); - this.observe(this.#workspaceContext.isNew, (isNew) => (this._isNew = isNew), '_observeIsNew'); - } - - private async _handleIconClick() { - const [alias, color] = this._icon?.replace('color-', '')?.split(' ') ?? []; - - const modalManager = await this.getContext(UMB_MODAL_MANAGER_CONTEXT); - const modalContext = modalManager.open(this, UMB_ICON_PICKER_MODAL, { - value: { - icon: alias, - color: color, - }, - }); - - modalContext?.onSubmit().then((saved) => { - if (saved.icon && saved.color) { - this.#workspaceContext?.setIcon(`${saved.icon} color-${saved.color}`); - } else if (saved.icon) { - this.#workspaceContext?.setIcon(saved.icon); - } - }); - } - - #onNameAndAliasChange(event: InputEvent & { target: UmbInputWithAliasElement }) { - this.#workspaceContext?.setName(event.target.value ?? ''); - this.#workspaceContext?.setAlias(event.target.alias ?? ''); - } - - #onDescriptionChange(event: InputEvent & { target: UUITextareaElement }) { - this.#workspaceContext?.setDescription(event.target.value.toString() ?? ''); - } - override render() { return html` <umb-entity-detail-workspace-editor> - <div id="header" slot="header"> - <uui-button id="icon" compact label="icon" look="outline" @click=${this._handleIconClick}> - <umb-icon name=${ifDefined(this._icon)}></umb-icon> - </uui-button> - - <div id="editors"> - <umb-input-with-alias - id="name" - label=${this.localize.term('placeholders_entername')} - value=${this._name} - alias=${this._alias} - ?auto-generate-alias=${this._isNew} - @change=${this.#onNameAndAliasChange} - ${umbFocus()}> - </umb-input-with-alias> - - <uui-input - id="description" - .label=${this.localize.term('placeholders_enterDescription')} - .value=${this._description} - .placeholder=${this.localize.term('placeholders_enterDescription')} - @input=${this.#onDescriptionChange}></uui-input> - </div> - </div> + <umb-content-type-workspace-editor-header slot="header"></umb-content-type-workspace-editor-header> </umb-entity-detail-workspace-editor> `; } @@ -118,39 +18,6 @@ export class UmbMediaTypeWorkspaceEditorElement extends UmbLitElement { width: 100%; height: 100%; } - - #header { - display: flex; - flex: 1 1 auto; - gap: var(--uui-size-space-2); - } - - #editors { - display: flex; - flex: 1 1 auto; - flex-direction: column; - gap: var(--uui-size-space-1); - } - - #name { - width: 100%; - } - - #description { - width: 100%; - --uui-input-height: var(--uui-size-8); - --uui-input-border-color: transparent; - } - - #description:hover { - --uui-input-border-color: var(--uui-color-border); - } - - #icon { - font-size: var(--uui-size-8); - height: 60px; - width: 60px; - } `, ]; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/action/create-media-collection-action.element.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/action/create-media-collection-action.element.ts index 1272eb50e132..0e05d732fca7 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/action/create-media-collection-action.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/action/create-media-collection-action.element.ts @@ -53,11 +53,11 @@ export class UmbCreateMediaCollectionActionElement extends UmbLitElement { } override async firstUpdated() { - this.#retrieveAllowedMediaTypesOf(this._mediaTypeUnique ?? ''); + this.#retrieveAllowedMediaTypesOf(this._mediaTypeUnique ?? '', this._mediaUnique || null); } - async #retrieveAllowedMediaTypesOf(unique: string | null) { - const { data } = await this.#mediaTypeStructureRepository.requestAllowedChildrenOf(unique); + async #retrieveAllowedMediaTypesOf(unique: string | null, parentContentUnique: string | null) { + const { data } = await this.#mediaTypeStructureRepository.requestAllowedChildrenOf(unique, parentContentUnique); if (data && data.items) { this._allowedMediaTypes = data.items; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/dropzone/dropzone-manager.class.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/dropzone/dropzone-manager.class.ts index 4a1bc9e3c610..b58069b537bc 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/dropzone/dropzone-manager.class.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/dropzone/dropzone-manager.class.ts @@ -226,7 +226,7 @@ export class UmbDropzoneManager extends UmbControllerBase { async #getMediaTypeOptions(item: UmbUploadableItem): Promise<Array<UmbAllowedMediaTypeModel>> { // Check the parent which children media types are allowed const parent = item.parentUnique ? await this.#mediaDetailRepository.requestByUnique(item.parentUnique) : null; - const allowedChildren = await this.#getAllowedChildrenOf(parent?.data?.mediaType.unique ?? null); + const allowedChildren = await this.#getAllowedChildrenOf(parent?.data?.mediaType.unique ?? null, item.parentUnique); const extension = item.temporaryFile?.file.name.split('.').pop() ?? null; @@ -255,7 +255,7 @@ export class UmbDropzoneManager extends UmbControllerBase { return availableMediaTypes; } - async #getAllowedChildrenOf(mediaTypeUnique: string | null) { + async #getAllowedChildrenOf(mediaTypeUnique: string | null, parentUnique: string | null) { //Check if we already got information on this media type. const allowed = this.#allowedChildrenOf .getValue() @@ -263,7 +263,7 @@ export class UmbDropzoneManager extends UmbControllerBase { if (allowed) return allowed; // Request information on this media type. - const { data } = await this.#mediaTypeStructure.requestAllowedChildrenOf(mediaTypeUnique); + const { data } = await this.#mediaTypeStructure.requestAllowedChildrenOf(mediaTypeUnique, parentUnique); if (!data) throw new Error('Parent media type does not exists'); this.#allowedChildrenOf.appendOne({ mediaTypeUnique, allowedChildren: data.items }); diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/entity-actions/create/media-create-options-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/entity-actions/create/media-create-options-modal.element.ts index f25c1b5759b8..a7e3fd544fe9 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/entity-actions/create/media-create-options-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/entity-actions/create/media-create-options-modal.element.ts @@ -26,15 +26,15 @@ export class UmbMediaCreateOptionsModalElement extends UmbModalBaseElement< const mediaUnique = this.data?.parent.unique; const mediaTypeUnique = this.data?.mediaType?.unique || null; - this.#retrieveAllowedMediaTypesOf(mediaTypeUnique); + this.#retrieveAllowedMediaTypesOf(mediaTypeUnique, mediaUnique || null); if (mediaUnique) { this.#retrieveHeadline(mediaUnique); } } - async #retrieveAllowedMediaTypesOf(unique: string | null) { - const { data } = await this.#mediaTypeStructureRepository.requestAllowedChildrenOf(unique); + async #retrieveAllowedMediaTypesOf(unique: string | null, parentContentUnique: string | null) { + const { data } = await this.#mediaTypeStructureRepository.requestAllowedChildrenOf(unique, parentContentUnique); if (data) { // TODO: implement pagination, or get 1000? diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/modals/media-picker/components/media-picker-create-item.element.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/modals/media-picker/components/media-picker-create-item.element.ts index 0b45165f4c1a..4c5d7cce3be2 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/modals/media-picker/components/media-picker-create-item.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/modals/media-picker/components/media-picker-create-item.element.ts @@ -36,7 +36,7 @@ export class UmbMediaPickerCreateItemElement extends UmbLitElement { async #getAllowedMediaTypes() { const mediaType = await this.#getNodeMediaType(); - const { data: allowedMediaTypes } = await this.#mediaTypeStructure.requestAllowedChildrenOf(mediaType); + const { data: allowedMediaTypes } = await this.#mediaTypeStructure.requestAllowedChildrenOf(mediaType, this._node); this._allowedMediaTypes = allowedMediaTypes?.items ?? []; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/property-editors/image-cropper/property-editor-ui-image-cropper.element.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/property-editors/image-cropper/property-editor-ui-image-cropper.element.ts index dc2eed16e82d..e4a213b940a2 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/property-editors/image-cropper/property-editor-ui-image-cropper.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/property-editors/image-cropper/property-editor-ui-image-cropper.element.ts @@ -2,13 +2,13 @@ import type { UmbImageCropperPropertyEditorValue, UmbInputImageCropperElement } import { html, customElement, property, state } from '@umbraco-cms/backoffice/external/lit'; import { type UmbPropertyEditorUiElement, - UmbPropertyValueChangeEvent, type UmbPropertyEditorConfigCollection, } from '@umbraco-cms/backoffice/property-editor'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import { UMB_VALIDATION_EMPTY_LOCALIZATION_KEY, UmbFormControlMixin } from '@umbraco-cms/backoffice/validation'; import '../../components/input-image-cropper/input-image-cropper.element.js'; +import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; /** * @element umb-property-editor-ui-image-cropper @@ -47,7 +47,7 @@ export class UmbPropertyEditorUIImageCropperElement #onChange(e: Event) { this.value = (e.target as UmbInputImageCropperElement).value; - this.dispatchEvent(new UmbPropertyValueChangeEvent()); + this.dispatchEvent(new UmbChangeEvent()); } override render() { diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/property-editors/image-crops/property-editor-ui-image-crops.element.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/property-editors/image-crops/property-editor-ui-image-crops.element.ts index 01a9842e2c36..269b52c78ffd 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/property-editors/image-crops/property-editor-ui-image-crops.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/property-editors/image-crops/property-editor-ui-image-crops.element.ts @@ -2,9 +2,9 @@ import { html, customElement, property, css, repeat, state, query } from '@umbra import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import type { UmbPropertyEditorUiElement } from '@umbraco-cms/backoffice/property-editor'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; -import { UmbPropertyValueChangeEvent } from '@umbraco-cms/backoffice/property-editor'; import { generateAlias } from '@umbraco-cms/backoffice/utils'; import { UmbSorterController } from '@umbraco-cms/backoffice/sorter'; +import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; export type UmbCrop = { label: string; @@ -53,13 +53,13 @@ export class UmbPropertyEditorUIImageCropsElement extends UmbLitElement implemen const oldValue = this._value; this._value = model; this.requestUpdate('_value', oldValue); - this.dispatchEvent(new UmbPropertyValueChangeEvent()); + this.dispatchEvent(new UmbChangeEvent()); }, }); #onRemove(alias: string) { this.value = [...this.value.filter((item) => item.alias !== alias)]; - this.dispatchEvent(new UmbPropertyValueChangeEvent()); + this.dispatchEvent(new UmbChangeEvent()); } #onEdit(crop: UmbCrop) { @@ -120,7 +120,7 @@ export class UmbPropertyEditorUIImageCropsElement extends UmbLitElement implemen } else { this.value = [...this.value, newCrop]; } - this.dispatchEvent(new UmbPropertyValueChangeEvent()); + this.dispatchEvent(new UmbChangeEvent()); form.reset(); this._labelInput.focus(); diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/property-editors/media-entity-picker/property-editor-ui-media-entity-picker.element.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/property-editors/media-entity-picker/property-editor-ui-media-entity-picker.element.ts index 224b4cbdbc96..6b052f1293d0 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/property-editors/media-entity-picker/property-editor-ui-media-entity-picker.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/property-editors/media-entity-picker/property-editor-ui-media-entity-picker.element.ts @@ -1,7 +1,7 @@ +import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; import type { UmbInputMediaElement } from '../../components/index.js'; import { html, customElement, property, state } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; -import { UmbPropertyValueChangeEvent } from '@umbraco-cms/backoffice/property-editor'; import type { UmbNumberRangeValueType } from '@umbraco-cms/backoffice/models'; import type { UmbPropertyEditorConfigCollection, @@ -38,7 +38,7 @@ export class UmbPropertyEditorUIMediaEntityPickerElement extends UmbLitElement i #onChange(event: CustomEvent & { target: UmbInputMediaElement }) { this.value = event.target.value; - this.dispatchEvent(new UmbPropertyValueChangeEvent()); + this.dispatchEvent(new UmbChangeEvent()); } override render() { diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/property-editors/media-picker/property-editor-ui-media-picker.element.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/property-editors/media-picker/property-editor-ui-media-picker.element.ts index 2ff636281a2b..be0ffd1bb9a9 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/property-editors/media-picker/property-editor-ui-media-picker.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/property-editors/media-picker/property-editor-ui-media-picker.element.ts @@ -3,7 +3,6 @@ import type { UmbCropModel, UmbMediaPickerValueModel } from '../types.js'; import { UMB_MEDIA_ENTITY_TYPE } from '../../entity.js'; import { customElement, html, property, state } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; -import { UmbPropertyValueChangeEvent } from '@umbraco-cms/backoffice/property-editor'; import { UMB_PROPERTY_CONTEXT } from '@umbraco-cms/backoffice/property'; import type { UmbNumberRangeValueType } from '@umbraco-cms/backoffice/models'; import type { @@ -14,6 +13,7 @@ import type { UmbTreeStartNode } from '@umbraco-cms/backoffice/tree'; import { UMB_VALIDATION_EMPTY_LOCALIZATION_KEY, UmbFormControlMixin } from '@umbraco-cms/backoffice/validation'; import '../../components/input-rich-media/input-rich-media.element.js'; +import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; const elementName = 'umb-property-editor-ui-media-picker'; @@ -107,7 +107,7 @@ export class UmbPropertyEditorUIMediaPickerElement #onChange(event: CustomEvent & { target: UmbInputRichMediaElement }) { const isEmpty = event.target.value?.length === 0; this.value = isEmpty ? undefined : event.target.value; - this.dispatchEvent(new UmbPropertyValueChangeEvent()); + this.dispatchEvent(new UmbChangeEvent()); } override render() { diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/property-editors/upload-field/property-editor-ui-upload-field.element.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/property-editors/upload-field/property-editor-ui-upload-field.element.ts index 2ca719db655f..a506793085e2 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/property-editors/upload-field/property-editor-ui-upload-field.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/property-editors/upload-field/property-editor-ui-upload-field.element.ts @@ -3,10 +3,10 @@ import type { MediaValueType } from './types.js'; import { html, customElement, property, state } from '@umbraco-cms/backoffice/external/lit'; import { type UmbPropertyEditorUiElement, - UmbPropertyValueChangeEvent, type UmbPropertyEditorConfigCollection, } from '@umbraco-cms/backoffice/property-editor'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; +import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; /** * @element umb-property-editor-ui-upload-field @@ -26,7 +26,7 @@ export class UmbPropertyEditorUIUploadFieldElement extends UmbLitElement impleme #onChange(event: CustomEvent) { this.value = (event.target as UmbInputUploadFieldElement).value; - this.dispatchEvent(new UmbPropertyValueChangeEvent()); + this.dispatchEvent(new UmbChangeEvent()); } override render() { diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/recycle-bin/entity-action/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/recycle-bin/entity-action/manifests.ts index 65b3693975d2..144684b99cd4 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/recycle-bin/entity-action/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/recycle-bin/entity-action/manifests.ts @@ -6,6 +6,7 @@ import { import { UMB_MEDIA_RECYCLE_BIN_ROOT_ENTITY_TYPE, UMB_MEDIA_RECYCLE_BIN_REPOSITORY_ALIAS } from '../constants.js'; import { UMB_MEDIA_REFERENCE_REPOSITORY_ALIAS } from '../../reference/constants.js'; import { manifests as bulkTrashManifests } from './bulk-trash/manifests.js'; +import { UMB_ENTITY_HAS_CHILDREN_CONDITION_ALIAS } from '@umbraco-cms/backoffice/entity-action'; import { UMB_ENTITY_IS_NOT_TRASHED_CONDITION_ALIAS, UMB_ENTITY_IS_TRASHED_CONDITION_ALIAS, @@ -55,6 +56,11 @@ export const manifests: Array<UmbExtensionManifest> = [ meta: { recycleBinRepositoryAlias: UMB_MEDIA_RECYCLE_BIN_REPOSITORY_ALIAS, }, + conditions: [ + { + alias: UMB_ENTITY_HAS_CHILDREN_CONDITION_ALIAS, + }, + ], }, ...bulkTrashManifests, ]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/member-group/components/input-member-group/input-member-group.context.ts b/src/Umbraco.Web.UI.Client/src/packages/members/member-group/components/input-member-group/input-member-group.context.ts index 7168a2375ed0..4f5682854411 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/members/member-group/components/input-member-group/input-member-group.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/members/member-group/components/input-member-group/input-member-group.context.ts @@ -9,6 +9,3 @@ export class UmbMemberGroupPickerInputContext extends UmbPickerInputContext<UmbM super(host, UMB_MEMBER_GROUP_ITEM_REPOSITORY_ALIAS, UMB_MEMBER_GROUP_PICKER_MODAL); } } - -/** @deprecated Use `UmbMemberGroupPickerInputContext` instead. This method will be removed in Umbraco 15. */ -export { UmbMemberGroupPickerInputContext as UmbMemberPickerContext }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/member-group/property-editor/member-group-picker/property-editor-ui-member-group-picker.element.ts b/src/Umbraco.Web.UI.Client/src/packages/members/member-group/property-editor/member-group-picker/property-editor-ui-member-group-picker.element.ts index 82ebf470f488..6f4f8a836ad4 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/members/member-group/property-editor/member-group-picker/property-editor-ui-member-group-picker.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/members/member-group/property-editor/member-group-picker/property-editor-ui-member-group-picker.element.ts @@ -1,12 +1,12 @@ import { html, customElement, property, state } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; -import { UmbPropertyValueChangeEvent } from '@umbraco-cms/backoffice/property-editor'; import type { UmbNumberRangeValueType } from '@umbraco-cms/backoffice/models'; import type { UmbInputMemberGroupElement } from '@umbraco-cms/backoffice/member-group'; import type { UmbPropertyEditorConfigCollection, UmbPropertyEditorUiElement, } from '@umbraco-cms/backoffice/property-editor'; +import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; /** * @element umb-property-editor-ui-member-group-picker @@ -41,7 +41,7 @@ export class UmbPropertyEditorUIMemberGroupPickerElement extends UmbLitElement i #onChange(event: CustomEvent & { target: UmbInputMemberGroupElement }) { this.value = event.target.value; - this.dispatchEvent(new UmbPropertyValueChangeEvent()); + this.dispatchEvent(new UmbChangeEvent()); } override render() { diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/member-type/components/input-member-type/input-member-type.context.ts b/src/Umbraco.Web.UI.Client/src/packages/members/member-type/components/input-member-type/input-member-type.context.ts index 765a9ac577a2..b8fa221c7485 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/members/member-type/components/input-member-type/input-member-type.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/members/member-type/components/input-member-type/input-member-type.context.ts @@ -19,6 +19,3 @@ export class UmbMemberTypePickerInputContext extends UmbPickerInputContext< super(host, UMB_MEMBER_TYPE_ITEM_REPOSITORY_ALIAS, UMB_MEMBER_TYPE_PICKER_MODAL); } } - -/** @deprecated Use `UmbMemberTypePickerInputContext` instead. This method will be removed in Umbraco 15. */ -export { UmbMemberTypePickerInputContext as UmbMemberTypePickerContext }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/member-type/workspace/member-type-workspace-editor.element.ts b/src/Umbraco.Web.UI.Client/src/packages/members/member-type/workspace/member-type-workspace-editor.element.ts index 521a3a54c5f2..af1dc269e92a 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/members/member-type/workspace/member-type-workspace-editor.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/members/member-type/workspace/member-type-workspace-editor.element.ts @@ -1,107 +1,12 @@ -import { UMB_MEMBER_TYPE_WORKSPACE_CONTEXT } from './member-type-workspace.context-token.js'; -import type { UmbInputWithAliasElement } from '@umbraco-cms/backoffice/components'; -import { css, html, customElement, state, ifDefined } from '@umbraco-cms/backoffice/external/lit'; -import type { UUITextareaElement } from '@umbraco-cms/backoffice/external/uui'; -import { UmbLitElement, umbFocus } from '@umbraco-cms/backoffice/lit-element'; -import { UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal'; -import { UMB_ICON_PICKER_MODAL } from '@umbraco-cms/backoffice/icon'; +import { css, html, customElement } from '@umbraco-cms/backoffice/external/lit'; +import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; @customElement('umb-member-type-workspace-editor') export class UmbMemberTypeWorkspaceEditorElement extends UmbLitElement { - @state() - private _name?: string; - - @state() - private _description?: string; - - @state() - private _alias?: string; - - @state() - private _icon?: string; - - @state() - private _isNew?: boolean; - - #workspaceContext?: typeof UMB_MEMBER_TYPE_WORKSPACE_CONTEXT.TYPE; - - constructor() { - super(); - - this.consumeContext(UMB_MEMBER_TYPE_WORKSPACE_CONTEXT, (instance) => { - this.#workspaceContext = instance; - this.#observeMemberType(); - }); - } - - #observeMemberType() { - if (!this.#workspaceContext) return; - this.observe(this.#workspaceContext.name, (name) => (this._name = name), '_observeName'); - this.observe( - this.#workspaceContext.description, - (description) => (this._description = description), - '_observeDescription', - ); - this.observe(this.#workspaceContext.alias, (alias) => (this._alias = alias), '_observeAlias'); - this.observe(this.#workspaceContext.icon, (icon) => (this._icon = icon), '_observeIcon'); - this.observe(this.#workspaceContext.isNew, (isNew) => (this._isNew = isNew), '_observeIsNew'); - } - - private async _handleIconClick() { - const [alias, color] = this._icon?.replace('color-', '')?.split(' ') ?? []; - const modalManager = await this.getContext(UMB_MODAL_MANAGER_CONTEXT); - const modalContext = modalManager.open(this, UMB_ICON_PICKER_MODAL, { - value: { - icon: alias, - color: color, - }, - }); - - modalContext?.onSubmit().then((saved) => { - if (saved.icon && saved.color) { - this.#workspaceContext?.set('icon', `${saved.icon} color-${saved.color}`); - } else if (saved.icon) { - this.#workspaceContext?.set('icon', saved.icon); - } - }); - } - - #onNameAndAliasChange(event: InputEvent & { target: UmbInputWithAliasElement }) { - this.#workspaceContext?.setName(event.target.value ?? ''); - this.#workspaceContext?.setAlias(event.target.alias ?? ''); - } - - #onDescriptionChange(event: InputEvent & { target: UUITextareaElement }) { - this.#workspaceContext?.setDescription(event.target.value.toString() ?? ''); - } - override render() { return html` <umb-entity-detail-workspace-editor> - <div id="header" slot="header"> - <uui-button id="icon" compact label="icon" look="outline" @click=${this._handleIconClick}> - <umb-icon name=${ifDefined(this._icon)}></umb-icon> - </uui-button> - - <div id="editors"> - <umb-input-with-alias - id="name" - label=${this.localize.term('placeholders_entername')} - value=${this._name} - alias=${this._alias} - ?auto-generate-alias=${this._isNew} - @change=${this.#onNameAndAliasChange} - ${umbFocus()}> - </umb-input-with-alias> - - <uui-input - id="description" - .label=${this.localize.term('placeholders_enterDescription')} - .value=${this._description} - .placeholder=${this.localize.term('placeholders_enterDescription')} - @input=${this.#onDescriptionChange}></uui-input> - </div> - </div> + <umb-content-type-workspace-editor-header slot="header"></umb-content-type-workspace-editor-header> </umb-entity-detail-workspace-editor> `; } @@ -113,51 +18,6 @@ export class UmbMemberTypeWorkspaceEditorElement extends UmbLitElement { width: 100%; height: 100%; } - - #header { - display: flex; - flex: 1 1 auto; - gap: var(--uui-size-space-2); - } - - #editors { - display: flex; - flex: 1 1 auto; - flex-direction: column; - gap: var(--uui-size-space-1); - } - - #name { - width: 100%; - flex: 1 1 auto; - align-items: center; - } - - #description { - width: 100%; - --uui-input-height: var(--uui-size-8); - --uui-input-border-color: transparent; - } - - #description:hover { - --uui-input-border-color: var(--uui-color-border); - } - - #alias-lock { - display: flex; - align-items: center; - justify-content: center; - cursor: pointer; - } - #alias-lock uui-icon { - margin-bottom: 2px; - } - - #icon { - font-size: var(--uui-size-8); - height: 60px; - width: 60px; - } `, ]; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/member/components/input-member/input-member.context.ts b/src/Umbraco.Web.UI.Client/src/packages/members/member/components/input-member/input-member.context.ts index 350e9b1ae4aa..0b40c9cf7016 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/members/member/components/input-member/input-member.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/members/member/components/input-member/input-member.context.ts @@ -63,6 +63,3 @@ export class UmbMemberPickerInputContext extends UmbPickerInputContext< return true; }; } - -/** @deprecated Use `UmbMemberPickerInputContext` instead. This method will be removed in Umbraco 15. */ -export { UmbMemberPickerInputContext as UmbMemberPickerContext }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/member/property-editor/member-picker/property-editor-ui-member-picker.element.ts b/src/Umbraco.Web.UI.Client/src/packages/members/member/property-editor/member-picker/property-editor-ui-member-picker.element.ts index 8e3d7aae8243..b1bf7cd68649 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/members/member/property-editor/member-picker/property-editor-ui-member-picker.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/members/member/property-editor/member-picker/property-editor-ui-member-picker.element.ts @@ -1,11 +1,11 @@ import { html, customElement, property } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; -import { UmbPropertyValueChangeEvent } from '@umbraco-cms/backoffice/property-editor'; import type { UmbPropertyEditorConfigCollection, UmbPropertyEditorUiElement, } from '@umbraco-cms/backoffice/property-editor'; import type { UmbInputMemberElement } from '@umbraco-cms/backoffice/member'; +import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; /** * @element umb-property-editor-ui-member-picker @@ -29,7 +29,7 @@ export class UmbPropertyEditorUIMemberPickerElement extends UmbLitElement implem #onChange(event: CustomEvent & { target: UmbInputMemberElement }) { this.value = event.target.value; - this.dispatchEvent(new UmbPropertyValueChangeEvent()); + this.dispatchEvent(new UmbChangeEvent()); } override render() { diff --git a/src/Umbraco.Web.UI.Client/src/packages/multi-url-picker/link-picker-modal/link-picker-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/multi-url-picker/link-picker-modal/link-picker-modal.element.ts index 2ef18e11f1f3..f47fe571ae32 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/multi-url-picker/link-picker-modal/link-picker-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/multi-url-picker/link-picker-modal/link-picker-modal.element.ts @@ -69,7 +69,7 @@ export class UmbLinkPickerModalElement extends UmbModalBaseElement<UmbLinkPicker async #getMediaTypes() { // Get all the media types, excluding the folders, so that files are selectable media items. const mediaTypeStructureRepository = new UmbMediaTypeStructureRepository(this); - const { data: mediaTypes } = await mediaTypeStructureRepository.requestAllowedChildrenOf(null); + const { data: mediaTypes } = await mediaTypeStructureRepository.requestAllowedChildrenOf(null, null); this._allowedMediaTypeUniques = (mediaTypes?.items.map((x) => x.unique).filter((x) => x && !isUmbracoFolder(x)) as Array<string>) ?? []; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/multi-url-picker/property-editor/property-editor-ui-multi-url-picker.element.ts b/src/Umbraco.Web.UI.Client/src/packages/multi-url-picker/property-editor/property-editor-ui-multi-url-picker.element.ts index 74e253e53b01..85e98551da08 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/multi-url-picker/property-editor/property-editor-ui-multi-url-picker.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/multi-url-picker/property-editor/property-editor-ui-multi-url-picker.element.ts @@ -2,7 +2,6 @@ import type { UmbLinkPickerLink } from '../link-picker-modal/types.js'; import type { UmbInputMultiUrlElement } from '../components/input-multi-url/index.js'; import { customElement, html, property, state } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; -import { UmbPropertyValueChangeEvent } from '@umbraco-cms/backoffice/property-editor'; import { UMB_PROPERTY_CONTEXT } from '@umbraco-cms/backoffice/property'; import type { UmbPropertyEditorConfigCollection, @@ -11,6 +10,7 @@ import type { import type { UUIModalSidebarSize } from '@umbraco-cms/backoffice/external/uui'; import '../components/input-multi-url/index.js'; +import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; /** * @element umb-property-editor-ui-multi-url-picker @@ -85,7 +85,7 @@ export class UmbPropertyEditorUIMultiUrlPickerElement extends UmbLitElement impl #onChange(event: CustomEvent & { target: UmbInputMultiUrlElement }) { this.value = event.target.urls; - this.dispatchEvent(new UmbPropertyValueChangeEvent()); + this.dispatchEvent(new UmbChangeEvent()); } override render() { diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/checkbox-list/components/input-checkbox-list/input-checkbox-list.element.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/checkbox-list/components/input-checkbox-list/input-checkbox-list.element.ts index 405039641d9f..996f65d44cdc 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/property-editors/checkbox-list/components/input-checkbox-list/input-checkbox-list.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/checkbox-list/components/input-checkbox-list/input-checkbox-list.element.ts @@ -1,13 +1,17 @@ -import { css, html, nothing, repeat, customElement, property, classMap } from '@umbraco-cms/backoffice/external/lit'; -import { UUIFormControlMixin } from '@umbraco-cms/backoffice/external/uui'; +import { classMap, css, customElement, html, nothing, property, repeat } from '@umbraco-cms/backoffice/external/lit'; import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; +import { UMB_VALIDATION_EMPTY_LOCALIZATION_KEY, UmbFormControlMixin } from '@umbraco-cms/backoffice/validation'; import type { UUIBooleanInputEvent } from '@umbraco-cms/backoffice/external/uui'; export type UmbCheckboxListItem = { label: string; value: string; checked: boolean; invalid?: boolean }; @customElement('umb-input-checkbox-list') -export class UmbInputCheckboxListElement extends UUIFormControlMixin(UmbLitElement, '') { +export class UmbInputCheckboxListElement extends UmbFormControlMixin< + string | undefined, + typeof UmbLitElement, + undefined +>(UmbLitElement, undefined) { @property({ attribute: false }) public list: Array<UmbCheckboxListItem> = []; @@ -38,8 +42,24 @@ export class UmbInputCheckboxListElement extends UUIFormControlMixin(UmbLitEleme @property({ type: Boolean, reflect: true }) readonly = false; - protected override getFormElement() { - return undefined; + /** + * Sets the input to required, meaning validation will fail if the value is empty. + * @type {boolean} + */ + @property({ type: Boolean }) + required?: boolean; + + @property({ type: String }) + requiredMessage?: string; + + constructor() { + super(); + + this.addValidator( + 'valueMissing', + () => this.requiredMessage ?? UMB_VALIDATION_EMPTY_LOCALIZATION_KEY, + () => !this.readonly && !!this.required && (this.value === undefined || this.value === null || this.value === ''), + ); } #onChange(event: UUIBooleanInputEvent) { @@ -73,13 +93,15 @@ export class UmbInputCheckboxListElement extends UUIFormControlMixin(UmbLitEleme } #renderCheckbox(item: (typeof this.list)[0]) { - return html`<uui-checkbox - class=${classMap({ invalid: !!item.invalid })} - ?checked=${item.checked} - label=${item.label + (item.invalid ? ` (${this.localize.term('validation_legacyOption')})` : '')} - title=${item.invalid ? this.localize.term('validation_legacyOptionDescription') : ''} - value=${item.value} - ?readonly=${this.readonly}></uui-checkbox>`; + return html` + <uui-checkbox + class=${classMap({ invalid: !!item.invalid })} + label=${item.label + (item.invalid ? ` (${this.localize.term('validation_legacyOption')})` : '')} + title=${item.invalid ? this.localize.term('validation_legacyOptionDescription') : ''} + value=${item.value} + ?checked=${item.checked} + ?readonly=${this.readonly}></uui-checkbox> + `; } static override readonly styles = [ diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/checkbox-list/property-editor-ui-checkbox-list.element.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/checkbox-list/property-editor-ui-checkbox-list.element.ts index 2b9e8f7790ad..a9cc829e446b 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/property-editors/checkbox-list/property-editor-ui-checkbox-list.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/checkbox-list/property-editor-ui-checkbox-list.element.ts @@ -2,9 +2,10 @@ import type { UmbCheckboxListItem, UmbInputCheckboxListElement, } from './components/input-checkbox-list/input-checkbox-list.element.js'; -import { html, customElement, property, state } from '@umbraco-cms/backoffice/external/lit'; +import { customElement, html, property, state } from '@umbraco-cms/backoffice/external/lit'; +import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; -import { UmbPropertyValueChangeEvent } from '@umbraco-cms/backoffice/property-editor'; +import { UMB_VALIDATION_EMPTY_LOCALIZATION_KEY, UmbFormControlMixin } from '@umbraco-cms/backoffice/validation'; import type { UmbPropertyEditorConfigCollection, UmbPropertyEditorUiElement, @@ -16,14 +17,20 @@ import './components/input-checkbox-list/input-checkbox-list.element.js'; * @element umb-property-editor-ui-checkbox-list */ @customElement('umb-property-editor-ui-checkbox-list') -export class UmbPropertyEditorUICheckboxListElement extends UmbLitElement implements UmbPropertyEditorUiElement { +export class UmbPropertyEditorUICheckboxListElement + extends UmbFormControlMixin<Array<string> | string | undefined, typeof UmbLitElement, undefined>( + UmbLitElement, + undefined, + ) + implements UmbPropertyEditorUiElement +{ #selection: Array<string> = []; @property({ type: Array }) - public set value(value: Array<string> | string | undefined) { + public override set value(value: Array<string> | string | undefined) { this.#selection = Array.isArray(value) ? value : value ? [value] : []; } - public get value(): Array<string> | undefined { + public override get value(): Array<string> | undefined { return this.#selection; } @@ -60,21 +67,38 @@ export class UmbPropertyEditorUICheckboxListElement extends UmbLitElement implem @property({ type: Boolean, reflect: true }) readonly = false; + /** + * Sets the input to mandatory, meaning validation will fail if the value is empty. + * @type {boolean} + */ + @property({ type: Boolean }) + mandatory?: boolean; + + @property({ type: String }) + mandatoryMessage = UMB_VALIDATION_EMPTY_LOCALIZATION_KEY; + @state() private _list: Array<UmbCheckboxListItem> = []; + protected override firstUpdated() { + this.addFormControlElement(this.shadowRoot!.querySelector('umb-input-checkbox-list')!); + } + #onChange(event: CustomEvent & { target: UmbInputCheckboxListElement }) { this.value = event.target.selection; - this.dispatchEvent(new UmbPropertyValueChangeEvent()); + this.dispatchEvent(new UmbChangeEvent()); } override render() { return html` <umb-input-checkbox-list .list=${this._list} + .required=${this.mandatory} + .requiredMessage=${this.mandatoryMessage} .selection=${this.#selection} ?readonly=${this.readonly} - @change=${this.#onChange}></umb-input-checkbox-list> + @change=${this.#onChange}> + </umb-input-checkbox-list> `; } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/collection/config/bulk-action-permissions/permissions.element.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/collection/config/bulk-action-permissions/permissions.element.ts index d08b04b423a3..468616c15021 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/property-editors/collection/config/bulk-action-permissions/permissions.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/collection/config/bulk-action-permissions/permissions.element.ts @@ -5,9 +5,9 @@ import type { } from '@umbraco-cms/backoffice/property-editor'; import { html, customElement, property, css } from '@umbraco-cms/backoffice/external/lit'; import type { UUIBooleanInputEvent } from '@umbraco-cms/backoffice/external/uui'; -import { UmbPropertyValueChangeEvent } from '@umbraco-cms/backoffice/property-editor'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; +import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; type BulkActionPermissionType = | 'allowBulkCopy' @@ -64,7 +64,7 @@ export class UmbPropertyEditorUICollectionPermissionsElement break; } - this.dispatchEvent(new UmbPropertyValueChangeEvent()); + this.dispatchEvent(new UmbChangeEvent()); } protected override firstUpdated() { diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/collection/config/column/column-configuration.element.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/collection/config/column/column-configuration.element.ts index 604b3ed46d2c..85f3977af164 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/property-editors/collection/config/column/column-configuration.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/collection/config/column/column-configuration.element.ts @@ -2,7 +2,6 @@ import type { UmbInputCollectionContentTypePropertyElement } from './components/ import type { UmbCollectionColumnConfiguration } from '@umbraco-cms/backoffice/collection'; import { css, customElement, html, nothing, property, repeat, state } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; -import { UmbPropertyValueChangeEvent } from '@umbraco-cms/backoffice/property-editor'; import { UmbSorterController } from '@umbraco-cms/backoffice/sorter'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import type { @@ -13,6 +12,7 @@ import type { UUIInputEvent } from '@umbraco-cms/backoffice/external/uui'; // import of local components import './components/index.js'; +import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; /** * @element umb-property-editor-ui-collection-column-configuration @@ -29,7 +29,7 @@ export class UmbPropertyEditorUICollectionColumnConfigurationElement containerSelector: '#layout-wrapper', onChange: ({ model }) => { this.value = model; - this.dispatchEvent(new UmbPropertyValueChangeEvent()); + this.dispatchEvent(new UmbChangeEvent()); }, }); @@ -72,7 +72,7 @@ export class UmbPropertyEditorUICollectionColumnConfigurationElement this.value = [...(this.value ?? []), config]; - this.dispatchEvent(new UmbPropertyValueChangeEvent()); + this.dispatchEvent(new UmbChangeEvent()); } #onChangeLabel(e: UUIInputEvent, configuration: UmbCollectionColumnConfiguration) { @@ -81,7 +81,7 @@ export class UmbPropertyEditorUICollectionColumnConfigurationElement config.alias === configuration.alias ? { ...config, header: e.target.value as string } : config, ); - this.dispatchEvent(new UmbPropertyValueChangeEvent()); + this.dispatchEvent(new UmbChangeEvent()); } #onChangeNameTemplate(e: UUIInputEvent, configuration: UmbCollectionColumnConfiguration) { @@ -90,7 +90,7 @@ export class UmbPropertyEditorUICollectionColumnConfigurationElement config.alias === configuration.alias ? { ...config, nameTemplate: e.target.value as string } : config, ); - this.dispatchEvent(new UmbPropertyValueChangeEvent()); + this.dispatchEvent(new UmbChangeEvent()); } #onRemove(unique: string) { @@ -101,7 +101,7 @@ export class UmbPropertyEditorUICollectionColumnConfigurationElement }); this.value = newValue; - this.dispatchEvent(new UmbPropertyValueChangeEvent()); + this.dispatchEvent(new UmbChangeEvent()); } override render() { diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/collection/config/layout/layout-configuration.element.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/collection/config/layout/layout-configuration.element.ts index 7408f7cf268b..21a01929b158 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/property-editors/collection/config/layout/layout-configuration.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/collection/config/layout/layout-configuration.element.ts @@ -11,8 +11,8 @@ import { import { extractUmbColorVariable } from '@umbraco-cms/backoffice/resources'; import { simpleHashCode } from '@umbraco-cms/backoffice/observable-api'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; -import { UmbPropertyValueChangeEvent } from '@umbraco-cms/backoffice/property-editor'; import { UmbSorterController } from '@umbraco-cms/backoffice/sorter'; +import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import { UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal'; import type { UmbInputManifestElement } from '@umbraco-cms/backoffice/components'; @@ -50,7 +50,7 @@ export class UmbPropertyEditorUICollectionLayoutConfigurationElement containerSelector: '#layout-wrapper', onChange: ({ model }) => { this.value = model; - this.dispatchEvent(new UmbPropertyValueChangeEvent()); + this.dispatchEvent(new UmbChangeEvent()); }, }); @@ -93,7 +93,7 @@ export class UmbPropertyEditorUICollectionLayoutConfigurationElement }, ]; - this.dispatchEvent(new UmbPropertyValueChangeEvent()); + this.dispatchEvent(new UmbChangeEvent()); this.#focusNewItem(); } @@ -102,14 +102,14 @@ export class UmbPropertyEditorUICollectionLayoutConfigurationElement const values = [...(this.value ?? [])]; values[index] = { ...values[index], name: e.target.value as string }; this.value = values; - this.dispatchEvent(new UmbPropertyValueChangeEvent()); + this.dispatchEvent(new UmbChangeEvent()); } #onRemove(unique: number) { const values = [...(this.value ?? [])]; values.splice(unique, 1); this.value = values; - this.dispatchEvent(new UmbPropertyValueChangeEvent()); + this.dispatchEvent(new UmbChangeEvent()); } async #onIconChange(icon: typeof UMB_ICON_PICKER_MODAL.VALUE, index: number) { @@ -121,7 +121,7 @@ export class UmbPropertyEditorUICollectionLayoutConfigurationElement const values = [...(this.value ?? [])]; values[index] = { ...values[index], icon: `${picked.icon} color-${picked.color}` }; this.value = values; - this.dispatchEvent(new UmbPropertyValueChangeEvent()); + this.dispatchEvent(new UmbChangeEvent()); } #parseIcon(iconString: string | undefined): typeof UMB_ICON_PICKER_MODAL.VALUE { diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/collection/config/order-by/order-by.element.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/collection/config/order-by/order-by.element.ts index 4b48b1e48a1c..0aa003204144 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/property-editors/collection/config/order-by/order-by.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/collection/config/order-by/order-by.element.ts @@ -1,13 +1,13 @@ import type { UmbCollectionColumnConfiguration } from '@umbraco-cms/backoffice/collection'; import { customElement, html, property, state } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; -import { UmbPropertyValueChangeEvent } from '@umbraco-cms/backoffice/property-editor'; import { UMB_PROPERTY_DATASET_CONTEXT } from '@umbraco-cms/backoffice/property'; import type { UmbPropertyEditorConfigCollection, UmbPropertyEditorUiElement, } from '@umbraco-cms/backoffice/property-editor'; import type { UUISelectEvent } from '@umbraco-cms/backoffice/external/uui'; +import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; /** * @element umb-property-editor-ui-collection-order-by @@ -48,7 +48,7 @@ export class UmbPropertyEditorUICollectionOrderByElement extends UmbLitElement i #onChange(e: UUISelectEvent) { this.value = e.target.value as string; - this.dispatchEvent(new UmbPropertyValueChangeEvent()); + this.dispatchEvent(new UmbChangeEvent()); } override render() { diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/color-picker/property-editor-ui-color-picker.element.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/color-picker/property-editor-ui-color-picker.element.ts index cbb8ad632df9..340886565716 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/property-editors/color-picker/property-editor-ui-color-picker.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/color-picker/property-editor-ui-color-picker.element.ts @@ -1,12 +1,12 @@ import { html, customElement, property, state } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; -import { UmbPropertyValueChangeEvent } from '@umbraco-cms/backoffice/property-editor'; import type { UmbPropertyEditorConfigCollection, UmbPropertyEditorUiElement, } from '@umbraco-cms/backoffice/property-editor'; import type { UmbSwatchDetails } from '@umbraco-cms/backoffice/models'; import type { UUIColorSwatchesEvent } from '@umbraco-cms/backoffice/external/uui'; +import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; /** * @element umb-property-editor-ui-color-picker @@ -60,7 +60,7 @@ export class UmbPropertyEditorUIColorPickerElement extends UmbLitElement impleme #onChange(event: UUIColorSwatchesEvent) { const value = event.target.value; this.value = this._swatches.find((swatch) => swatch.value === value); - this.dispatchEvent(new UmbPropertyValueChangeEvent()); + this.dispatchEvent(new UmbChangeEvent()); } override render() { diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/color-swatches-editor/property-editor-ui-color-swatches-editor.element.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/color-swatches-editor/property-editor-ui-color-swatches-editor.element.ts index cb8bc90f12f0..cdf530f39920 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/property-editors/color-swatches-editor/property-editor-ui-color-swatches-editor.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/color-swatches-editor/property-editor-ui-color-swatches-editor.element.ts @@ -1,12 +1,12 @@ import { html, customElement, property, state } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; -import { UmbPropertyValueChangeEvent } from '@umbraco-cms/backoffice/property-editor'; import type { UmbMultipleColorPickerInputElement } from '@umbraco-cms/backoffice/components'; import type { UmbPropertyEditorConfigCollection, UmbPropertyEditorUiElement, } from '@umbraco-cms/backoffice/property-editor'; import type { UmbSwatchDetails } from '@umbraco-cms/backoffice/models'; +import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; /** * @element umb-property-editor-ui-color-swatches-editor @@ -27,7 +27,7 @@ export class UmbPropertyEditorUIColorSwatchesEditorElement extends UmbLitElement #onChange(event: CustomEvent & { target: UmbMultipleColorPickerInputElement }) { this.value = event.target.items; - this.dispatchEvent(new UmbPropertyValueChangeEvent()); + this.dispatchEvent(new UmbChangeEvent()); } override render() { diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/content-picker/components/input-content/input-content.element.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/content-picker/components/input-content/input-content.element.ts index 3d1485052e7d..cef367e98678 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/property-editors/content-picker/components/input-content/input-content.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/content-picker/components/input-content/input-content.element.ts @@ -60,16 +60,6 @@ export class UmbInputContentElement extends UmbFormControlMixin<string | undefin return this.#selection.map((id) => ({ type: this.#entityTypeLookup[this.#type], unique: id })); } - /** @deprecated Please use `selection` instead. This property will be removed in Umbraco 15. */ - @property({ type: Array }) - public set items(items: Array<UmbReferenceByUniqueAndType>) { - this.selection = items; - } - /** @deprecated Please use `selection` instead. This property will be removed in Umbraco 15. */ - public get items(): Array<UmbReferenceByUniqueAndType> { - return this.selection; - } - @property({ type: String }) public override set value(selectionString: string | undefined) { this.#selection = splitStringToArray(selectionString); diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/content-picker/config/source-content/property-editor-ui-content-picker-source.element.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/content-picker/config/source-content/property-editor-ui-content-picker-source.element.ts index aa302b1ea1f4..e5f347edfa62 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/property-editors/content-picker/config/source-content/property-editor-ui-content-picker-source.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/content-picker/config/source-content/property-editor-ui-content-picker-source.element.ts @@ -3,13 +3,13 @@ import type { UmbInputContentPickerSourceElement } from './input-content-picker- import { type UmbPropertyEditorUiElement, type UmbPropertyEditorConfigCollection, - UmbPropertyValueChangeEvent, } from '@umbraco-cms/backoffice/property-editor'; import { html, customElement, property } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; // import of local component import './input-content-picker-source.element.js'; +import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; /** * @element umb-property-editor-ui-content-picker-source @@ -31,7 +31,7 @@ export class UmbPropertyEditorUIContentPickerSourceElement extends UmbLitElement dynamicRoot: target.dynamicRoot, }; - this.dispatchEvent(new UmbPropertyValueChangeEvent()); + this.dispatchEvent(new UmbChangeEvent()); } override render() { diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/content-picker/config/source-type/property-editor-ui-content-picker-source-type.element.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/content-picker/config/source-type/property-editor-ui-content-picker-source-type.element.ts index 5c896eb110f8..f20639cff211 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/property-editors/content-picker/config/source-type/property-editor-ui-content-picker-source-type.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/content-picker/config/source-type/property-editor-ui-content-picker-source-type.element.ts @@ -6,7 +6,7 @@ import type { UmbPropertyEditorUiElement } from '@umbraco-cms/backoffice/propert import { customElement, html, property, state } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import { UMB_PROPERTY_DATASET_CONTEXT } from '@umbraco-cms/backoffice/property'; -import { UmbPropertyValueChangeEvent } from '@umbraco-cms/backoffice/property-editor'; +import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; /** * @element umb-property-editor-ui-content-picker-source-type @@ -90,7 +90,7 @@ export class UmbPropertyEditorUIContentPickerSourceTypeElement #setValue(value: string[]) { this.value = value.join(','); - this.dispatchEvent(new UmbPropertyValueChangeEvent()); + this.dispatchEvent(new UmbChangeEvent()); } override render() { diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/content-picker/property-editor-ui-content-picker.element.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/content-picker/property-editor-ui-content-picker.element.ts index 20eafce70572..d763d635319c 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/property-editors/content-picker/property-editor-ui-content-picker.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/content-picker/property-editor-ui-content-picker.element.ts @@ -4,7 +4,6 @@ import type { UmbContentPickerSource, UmbContentPickerSourceType } from './types import { html, customElement, property, state } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import { UmbFormControlMixin } from '@umbraco-cms/backoffice/validation'; -import { UmbPropertyValueChangeEvent } from '@umbraco-cms/backoffice/property-editor'; import { UMB_DOCUMENT_ENTITY_TYPE } from '@umbraco-cms/backoffice/document'; import { UMB_MEDIA_ENTITY_TYPE } from '@umbraco-cms/backoffice/media'; import { UMB_MEMBER_ENTITY_TYPE } from '@umbraco-cms/backoffice/member'; @@ -16,6 +15,7 @@ import type { UmbTreeStartNode } from '@umbraco-cms/backoffice/tree'; // import of local component import './components/input-content/index.js'; +import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; type UmbContentPickerValueType = UmbInputContentElement['selection']; @@ -149,7 +149,7 @@ export class UmbPropertyEditorUIContentPickerElement #onChange(event: CustomEvent & { target: UmbInputContentElement }) { this.value = event.target.selection; - this.dispatchEvent(new UmbPropertyValueChangeEvent()); + this.dispatchEvent(new UmbChangeEvent()); } override render() { diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/date-picker/property-editor-ui-date-picker.element.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/date-picker/property-editor-ui-date-picker.element.ts index 3fa3e5616a71..df32f6f115b8 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/property-editors/date-picker/property-editor-ui-date-picker.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/date-picker/property-editor-ui-date-picker.element.ts @@ -1,4 +1,3 @@ -import { UmbPropertyValueChangeEvent } from '@umbraco-cms/backoffice/property-editor'; import type { UmbPropertyEditorConfigCollection, UmbPropertyEditorUiElement, @@ -6,6 +5,7 @@ import type { import { html, customElement, property, state } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import type { UmbInputDateElement } from '@umbraco-cms/backoffice/components'; +import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; /** * This property editor allows the user to pick a date, datetime-local, or time. @@ -140,7 +140,7 @@ export class UmbPropertyEditorUIDatePickerElement extends UmbLitElement implemen const valueHasChanged = this.value !== value; if (valueHasChanged) { this.value = value; - this.dispatchEvent(new UmbPropertyValueChangeEvent()); + this.dispatchEvent(new UmbChangeEvent()); } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/dropdown/property-editor-ui-dropdown.element.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/dropdown/property-editor-ui-dropdown.element.ts index 3d18338a4ff4..9be8531819d7 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/property-editors/dropdown/property-editor-ui-dropdown.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/dropdown/property-editor-ui-dropdown.element.ts @@ -1,25 +1,38 @@ -import { css, customElement, html, map, nothing, property, state } from '@umbraco-cms/backoffice/external/lit'; +import { css, customElement, html, map, nothing, property, state, when } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; -import { UmbPropertyValueChangeEvent } from '@umbraco-cms/backoffice/property-editor'; +import { UMB_VALIDATION_EMPTY_LOCALIZATION_KEY, UmbFormControlMixin } from '@umbraco-cms/backoffice/validation'; import { UUISelectElement } from '@umbraco-cms/backoffice/external/uui'; import type { UmbPropertyEditorConfigCollection, UmbPropertyEditorUiElement, } from '@umbraco-cms/backoffice/property-editor'; import type { UUISelectEvent } from '@umbraco-cms/backoffice/external/uui'; +import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; /** * @element umb-property-editor-ui-dropdown */ @customElement('umb-property-editor-ui-dropdown') -export class UmbPropertyEditorUIDropdownElement extends UmbLitElement implements UmbPropertyEditorUiElement { +export class UmbPropertyEditorUIDropdownElement + extends UmbFormControlMixin<Array<string> | string | undefined, typeof UmbLitElement, undefined>( + UmbLitElement, + undefined, + ) + implements UmbPropertyEditorUiElement +{ #selection: Array<string> = []; + @state() + private _multiple: boolean = false; + + @state() + private _options: Array<Option & { invalid?: boolean }> = []; + @property({ type: Array }) - public set value(value: Array<string> | string | undefined) { + public override set value(value: Array<string> | string | undefined) { this.#selection = Array.isArray(value) ? value : value ? [value] : []; } - public get value(): Array<string> | undefined { + public override get value(): Array<string> | undefined { return this.#selection; } @@ -32,6 +45,19 @@ export class UmbPropertyEditorUIDropdownElement extends UmbLitElement implements @property({ type: Boolean, reflect: true }) readonly = false; + /** + * Sets the input to mandatory, meaning validation will fail if the value is empty. + * @type {boolean} + */ + @property({ type: Boolean }) + mandatory?: boolean; + + @property({ type: String }) + mandatoryMessage = UMB_VALIDATION_EMPTY_LOCALIZATION_KEY; + + @property({ type: String }) + name?: string; + public set config(config: UmbPropertyEditorConfigCollection | undefined) { if (!config) return; @@ -63,11 +89,13 @@ export class UmbPropertyEditorUIDropdownElement extends UmbLitElement implements this._multiple = config.getValueByAlias<boolean>('multiple') ?? false; } - @state() - private _multiple: boolean = false; - - @state() - private _options: Array<Option & { invalid?: boolean }> = []; + protected override firstUpdated() { + if (this._multiple) { + this.addFormControlElement(this.shadowRoot!.querySelector('select')!); + } else { + this.addFormControlElement(this.shadowRoot!.querySelector('umb-input-dropdown-list')!); + } + } #onChange(event: UUISelectEvent) { const value = event.target.value as string; @@ -83,11 +111,18 @@ export class UmbPropertyEditorUIDropdownElement extends UmbLitElement implements #setValue(value: Array<string> | string | null | undefined) { if (!value) return; this.value = value; - this.dispatchEvent(new UmbPropertyValueChangeEvent()); + this.dispatchEvent(new UmbChangeEvent()); } override render() { - return this._multiple ? this.#renderDropdownMultiple() : this.#renderDropdownSingle(); + return html` + ${when( + this._multiple, + () => this.#renderDropdownMultiple(), + () => this.#renderDropdownSingle(), + )} + ${this.#renderDropdownValidation()} + `; } #renderDropdownMultiple() { @@ -96,23 +131,25 @@ export class UmbPropertyEditorUIDropdownElement extends UmbLitElement implements } return html` - <select id="native" multiple @change=${this.#onChangeMulitple}> + <select id="native" multiple ?required=${this.mandatory} @change=${this.#onChangeMulitple}> ${map( this._options, (item) => html`<option value=${item.value} ?selected=${item.selected}>${item.name}</option>`, )} </select> - ${this.#renderDropdownValidation()} `; } #renderDropdownSingle() { return html` <umb-input-dropdown-list + .name=${this.name} .options=${this._options} - @change=${this.#onChange} - ?readonly=${this.readonly}></umb-input-dropdown-list> - ${this.#renderDropdownValidation()} + .required=${this.mandatory} + .requiredMessage=${this.mandatoryMessage} + ?readonly=${this.readonly} + @change=${this.#onChange}> + </umb-input-dropdown-list> `; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/eye-dropper/property-editor-ui-eye-dropper.element.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/eye-dropper/property-editor-ui-eye-dropper.element.ts index 310e53b1f313..6f1f271f10f8 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/property-editors/eye-dropper/property-editor-ui-eye-dropper.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/eye-dropper/property-editor-ui-eye-dropper.element.ts @@ -1,11 +1,11 @@ import { html, customElement, property, state } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; -import { UmbPropertyValueChangeEvent } from '@umbraco-cms/backoffice/property-editor'; import type { UmbPropertyEditorConfigCollection, UmbPropertyEditorUiElement, } from '@umbraco-cms/backoffice/property-editor'; import type { UUIColorPickerChangeEvent } from '@umbraco-cms/backoffice/external/uui'; +import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; /** * @element umb-property-editor-ui-eye-dropper @@ -30,7 +30,7 @@ export class UmbPropertyEditorUIEyeDropperElement extends UmbLitElement implemen #onChange(event: UUIColorPickerChangeEvent) { this.value = event.target.value; - this.dispatchEvent(new UmbPropertyValueChangeEvent()); + this.dispatchEvent(new UmbChangeEvent()); } override render() { diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/icon-picker/property-editor-ui-icon-picker.element.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/icon-picker/property-editor-ui-icon-picker.element.ts index 0f36ea0fcc9b..69086922a5cb 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/property-editors/icon-picker/property-editor-ui-icon-picker.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/icon-picker/property-editor-ui-icon-picker.element.ts @@ -4,7 +4,7 @@ import { UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal'; import { UMB_ICON_PICKER_MODAL } from '@umbraco-cms/backoffice/icon'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import { extractUmbColorVariable } from '@umbraco-cms/backoffice/resources'; -import { UmbPropertyValueChangeEvent } from '@umbraco-cms/backoffice/property-editor'; +import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; /** * @element umb-property-editor-ui-icon-picker @@ -48,7 +48,7 @@ export class UmbPropertyEditorUIIconPickerElement extends UmbLitElement implemen this.value = data.icon as string; } - this.dispatchEvent(new UmbPropertyValueChangeEvent()); + this.dispatchEvent(new UmbChangeEvent()); } override render() { diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/multiple-text-string/property-editor-ui-multiple-text-string.element.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/multiple-text-string/property-editor-ui-multiple-text-string.element.ts index 269630d09c05..a4aa168ece1f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/property-editors/multiple-text-string/property-editor-ui-multiple-text-string.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/multiple-text-string/property-editor-ui-multiple-text-string.element.ts @@ -1,13 +1,12 @@ import { customElement, html, property, query, state } from '@umbraco-cms/backoffice/external/lit'; import { umbBindToValidation, UmbValidationContext } from '@umbraco-cms/backoffice/validation'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; -import { UmbPropertyValueChangeEvent } from '@umbraco-cms/backoffice/property-editor'; import { UMB_PROPERTY_CONTEXT } from '@umbraco-cms/backoffice/property'; import { UMB_SUBMITTABLE_WORKSPACE_CONTEXT, UmbSubmittableWorkspaceContextBase, } from '@umbraco-cms/backoffice/workspace'; -import type { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; +import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; import type { UmbInputMultipleTextStringElement } from '@umbraco-cms/backoffice/components'; import type { UmbPropertyEditorConfigCollection, @@ -97,7 +96,7 @@ export class UmbPropertyEditorUIMultipleTextStringElement extends UmbLitElement event.stopPropagation(); const target = event.currentTarget as UmbInputMultipleTextStringElement; this.value = target.items; - this.dispatchEvent(new UmbPropertyValueChangeEvent()); + this.dispatchEvent(new UmbChangeEvent()); } // Prevent valid events from bubbling outside the message element diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/number-range/property-editor-ui-number-range.element.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/number-range/property-editor-ui-number-range.element.ts index 10d1060a58c4..aa14e097510e 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/property-editors/number-range/property-editor-ui-number-range.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/number-range/property-editor-ui-number-range.element.ts @@ -1,8 +1,8 @@ import type { UmbInputNumberRangeElement } from '@umbraco-cms/backoffice/components'; import { customElement, html, property, state } from '@umbraco-cms/backoffice/external/lit'; import { UmbFormControlMixin } from '@umbraco-cms/backoffice/validation'; +import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; -import { UmbPropertyValueChangeEvent } from '@umbraco-cms/backoffice/property-editor'; import type { UmbNumberRangeValueType } from '@umbraco-cms/backoffice/models'; import type { UmbPropertyEditorConfigCollection, @@ -44,7 +44,7 @@ export class UmbPropertyEditorUINumberRangeElement #onChange(event: CustomEvent & { target: UmbInputNumberRangeElement }) { this.value = { min: event.target.minValue, max: event.target.maxValue }; - this.dispatchEvent(new UmbPropertyValueChangeEvent()); + this.dispatchEvent(new UmbChangeEvent()); } override firstUpdated() { diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/number/property-editor-ui-number.element.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/number/property-editor-ui-number.element.ts index b423c6c6c7d9..eedd195b0053 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/property-editors/number/property-editor-ui-number.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/number/property-editor-ui-number.element.ts @@ -1,12 +1,12 @@ import { css, customElement, html, ifDefined, property, state } from '@umbraco-cms/backoffice/external/lit'; import { UmbFormControlMixin } from '@umbraco-cms/backoffice/validation'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; -import { UmbPropertyValueChangeEvent } from '@umbraco-cms/backoffice/property-editor'; import { UMB_PROPERTY_CONTEXT } from '@umbraco-cms/backoffice/property'; import type { UmbPropertyEditorConfigCollection, UmbPropertyEditorUiElement, } from '@umbraco-cms/backoffice/property-editor'; +import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; @customElement('umb-property-editor-ui-number') export class UmbPropertyEditorUINumberElement @@ -89,7 +89,7 @@ export class UmbPropertyEditorUINumberElement const newValue = event.target.value === '' ? undefined : this.#parseNumber(event.target.value); if (newValue === this.value) return; this.value = newValue; - this.dispatchEvent(new UmbPropertyValueChangeEvent()); + this.dispatchEvent(new UmbChangeEvent()); } override render() { diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/order-direction/property-editor-ui-order-direction.element.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/order-direction/property-editor-ui-order-direction.element.ts index b3fe162c4e42..6794431748d7 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/property-editors/order-direction/property-editor-ui-order-direction.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/order-direction/property-editor-ui-order-direction.element.ts @@ -5,8 +5,8 @@ import type { UmbPropertyEditorConfigCollection, } from '@umbraco-cms/backoffice/property-editor'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; -import { UmbPropertyValueChangeEvent } from '@umbraco-cms/backoffice/property-editor'; import type { UUIBooleanInputEvent } from '@umbraco-cms/backoffice/external/uui'; +import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; /** * @element umb-property-editor-ui-order-direction @@ -21,7 +21,7 @@ export class UmbPropertyEditorUIOrderDirectionElement extends UmbLitElement impl #onInput(e: UUIBooleanInputEvent) { this.value = e.target.value; - this.dispatchEvent(new UmbPropertyValueChangeEvent()); + this.dispatchEvent(new UmbChangeEvent()); } override render() { diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/overlay-size/property-editor-ui-overlay-size.element.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/overlay-size/property-editor-ui-overlay-size.element.ts index cce59c656358..7063e242063c 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/property-editors/overlay-size/property-editor-ui-overlay-size.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/overlay-size/property-editor-ui-overlay-size.element.ts @@ -1,11 +1,11 @@ import { html, customElement, property, state } from '@umbraco-cms/backoffice/external/lit'; import { type UmbPropertyEditorUiElement, - UmbPropertyValueChangeEvent, type UmbPropertyEditorConfigCollection, } from '@umbraco-cms/backoffice/property-editor'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import type { UUIModalSidebarSize, UUISelectEvent } from '@umbraco-cms/backoffice/external/uui'; +import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; /** * @element umb-property-editor-ui-overlay-size @@ -38,7 +38,7 @@ export class UmbPropertyEditorUIOverlaySizeElement extends UmbLitElement impleme #onChange(event: UUISelectEvent) { this.value = event.target.value as string; - this.dispatchEvent(new UmbPropertyValueChangeEvent()); + this.dispatchEvent(new UmbChangeEvent()); } override render() { diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/radio-button-list/property-editor-ui-radio-button-list.element.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/radio-button-list/property-editor-ui-radio-button-list.element.ts index 875411a640c6..30697df23b6b 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/property-editors/radio-button-list/property-editor-ui-radio-button-list.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/radio-button-list/property-editor-ui-radio-button-list.element.ts @@ -1,7 +1,8 @@ import type { UmbInputRadioButtonListElement, UmbRadioButtonItem } from '@umbraco-cms/backoffice/components'; +import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; import { html, customElement, property, state } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; -import { UmbPropertyValueChangeEvent } from '@umbraco-cms/backoffice/property-editor'; +import { UMB_VALIDATION_EMPTY_LOCALIZATION_KEY, UmbFormControlMixin } from '@umbraco-cms/backoffice/validation'; import type { UmbPropertyEditorConfigCollection, UmbPropertyEditorUiElement, @@ -11,9 +12,12 @@ import type { * @element umb-property-editor-ui-radio-button-list */ @customElement('umb-property-editor-ui-radio-button-list') -export class UmbPropertyEditorUIRadioButtonListElement extends UmbLitElement implements UmbPropertyEditorUiElement { - @property() - value?: string = ''; +export class UmbPropertyEditorUIRadioButtonListElement + extends UmbFormControlMixin<string | undefined, typeof UmbLitElement, undefined>(UmbLitElement) + implements UmbPropertyEditorUiElement +{ + @state() + private _list: Array<UmbRadioButtonItem> = []; /** * Sets the input to readonly mode, meaning value cannot be changed but still able to read and select its content. @@ -24,6 +28,16 @@ export class UmbPropertyEditorUIRadioButtonListElement extends UmbLitElement imp @property({ type: Boolean, reflect: true }) readonly = false; + /** + * Sets the input to mandatory, meaning validation will fail if the value is empty. + * @type {boolean} + */ + @property({ type: Boolean }) + mandatory?: boolean; + + @property({ type: String }) + mandatoryMessage = UMB_VALIDATION_EMPTY_LOCALIZATION_KEY; + public set config(config: UmbPropertyEditorConfigCollection | undefined) { if (!config) return; @@ -42,21 +56,25 @@ export class UmbPropertyEditorUIRadioButtonListElement extends UmbLitElement imp } } - @state() - private _list: Array<UmbRadioButtonItem> = []; + protected override firstUpdated() { + this.addFormControlElement(this.shadowRoot!.querySelector('umb-input-radio-button-list')!); + } #onChange(event: CustomEvent & { target: UmbInputRadioButtonListElement }) { this.value = event.target.value; - this.dispatchEvent(new UmbPropertyValueChangeEvent()); + this.dispatchEvent(new UmbChangeEvent()); } override render() { return html` <umb-input-radio-button-list .list=${this._list} + .required=${this.mandatory} + .requiredMessage=${this.mandatoryMessage} .value=${this.value ?? ''} - @change=${this.#onChange} - ?readonly=${this.readonly}></umb-input-radio-button-list> + ?readonly=${this.readonly} + @change=${this.#onChange}> + </umb-input-radio-button-list> `; } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/select/property-editor-ui-select.element.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/select/property-editor-ui-select.element.ts index 53bd5c0f6c0d..3345b9606b6d 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/property-editors/select/property-editor-ui-select.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/select/property-editor-ui-select.element.ts @@ -1,11 +1,11 @@ import { customElement, html, property, state } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; -import { UmbPropertyValueChangeEvent } from '@umbraco-cms/backoffice/property-editor'; import type { UmbPropertyEditorConfigCollection, UmbPropertyEditorUiElement, } from '@umbraco-cms/backoffice/property-editor'; import type { UUISelectEvent } from '@umbraco-cms/backoffice/external/uui'; +import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; /** * @element umb-property-editor-ui-select @@ -33,7 +33,7 @@ export class UmbPropertyEditorUISelectElement extends UmbLitElement implements U #onChange(event: UUISelectEvent) { this.value = event.target.value as string; - this.dispatchEvent(new UmbPropertyValueChangeEvent()); + this.dispatchEvent(new UmbChangeEvent()); } override render() { diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/slider/property-editor-ui-slider.element.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/slider/property-editor-ui-slider.element.ts index f61e4e83b624..1179bbae9da2 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/property-editors/slider/property-editor-ui-slider.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/slider/property-editor-ui-slider.element.ts @@ -1,9 +1,9 @@ +import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; import type { UmbSliderPropertyEditorUiValue } from './types.js'; import type { UmbInputSliderElement } from '@umbraco-cms/backoffice/components'; import { customElement, html, property, state } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import { UMB_PROPERTY_CONTEXT } from '@umbraco-cms/backoffice/property'; -import { UmbPropertyValueChangeEvent } from '@umbraco-cms/backoffice/property-editor'; import type { UmbPropertyEditorConfigCollection, UmbPropertyEditorUiElement, @@ -102,7 +102,7 @@ export class UmbPropertyEditorUISliderElement extends UmbLitElement implements U #onChange(event: CustomEvent & { target: UmbInputSliderElement }) { this.value = this.#getValueObject(event.target.value as string); - this.dispatchEvent(new UmbPropertyValueChangeEvent()); + this.dispatchEvent(new UmbChangeEvent()); } override render() { diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/text-box/property-editor-ui-text-box.element.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/text-box/property-editor-ui-text-box.element.ts index 7913e1a50c4a..191669cc55fe 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/property-editors/text-box/property-editor-ui-text-box.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/text-box/property-editor-ui-text-box.element.ts @@ -2,12 +2,12 @@ import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import { css, html, customElement, state, ifDefined, property } from '@umbraco-cms/backoffice/external/lit'; import { type UmbPropertyEditorUiElement, - UmbPropertyValueChangeEvent, type UmbPropertyEditorConfigCollection, } from '@umbraco-cms/backoffice/property-editor'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import type { UUIInputElement } from '@umbraco-cms/backoffice/external/uui'; import { UMB_VALIDATION_EMPTY_LOCALIZATION_KEY, UmbFormControlMixin } from '@umbraco-cms/backoffice/validation'; +import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; type UuiInputTypeType = typeof UUIInputElement.prototype.type; @@ -74,7 +74,7 @@ export class UmbPropertyEditorUITextBoxElement const newValue = (e.target as HTMLInputElement).value; if (newValue === this.value) return; this.value = newValue; - this.dispatchEvent(new UmbPropertyValueChangeEvent()); + this.dispatchEvent(new UmbChangeEvent()); } override render() { diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/textarea/property-editor-ui-textarea.element.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/textarea/property-editor-ui-textarea.element.ts index 7da58bc2f0c6..e88d6be269a3 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/property-editors/textarea/property-editor-ui-textarea.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/textarea/property-editor-ui-textarea.element.ts @@ -1,6 +1,5 @@ import { css, customElement, html, ifDefined, property, state, styleMap } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; -import { UmbPropertyValueChangeEvent } from '@umbraco-cms/backoffice/property-editor'; import type { StyleInfo } from '@umbraco-cms/backoffice/external/lit'; import type { UmbPropertyEditorConfigCollection, @@ -9,6 +8,7 @@ import type { import { UMB_VALIDATION_EMPTY_LOCALIZATION_KEY, UmbFormControlMixin } from '@umbraco-cms/backoffice/validation'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import type { UUITextareaElement } from '@umbraco-cms/backoffice/external/uui'; +import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; @customElement('umb-property-editor-ui-textarea') export class UmbPropertyEditorUITextareaElement @@ -86,7 +86,7 @@ export class UmbPropertyEditorUITextareaElement const newValue = (event.target as HTMLTextAreaElement).value; if (newValue === this.value) return; this.value = newValue; - this.dispatchEvent(new UmbPropertyValueChangeEvent()); + this.dispatchEvent(new UmbChangeEvent()); } override render() { diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/toggle/property-editor-ui-toggle.element.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/toggle/property-editor-ui-toggle.element.ts index f0e34bb06522..1206c1f92f64 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/property-editors/toggle/property-editor-ui-toggle.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/toggle/property-editor-ui-toggle.element.ts @@ -1,8 +1,8 @@ +import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; import type { UmbTogglePropertyEditorUiValue } from './types.js'; import type { UmbInputToggleElement } from '@umbraco-cms/backoffice/components'; import { customElement, html, property, state } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; -import { UmbPropertyValueChangeEvent } from '@umbraco-cms/backoffice/property-editor'; import type { UmbPropertyEditorConfigCollection, UmbPropertyEditorUiElement, @@ -61,7 +61,7 @@ export class UmbPropertyEditorUIToggleElement #onChange(event: CustomEvent & { target: UmbInputToggleElement }) { const checked = event.target.checked; this.value = this.mandatory ? (checked ?? null) : checked; - this.dispatchEvent(new UmbPropertyValueChangeEvent()); + this.dispatchEvent(new UmbChangeEvent()); } override render() { diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/value-type/property-editor-ui-value-type.element.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/value-type/property-editor-ui-value-type.element.ts index 3bc8309539de..4886542e13e3 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/property-editors/value-type/property-editor-ui-value-type.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/value-type/property-editor-ui-value-type.element.ts @@ -2,10 +2,10 @@ import { html, customElement, property, state, query } from '@umbraco-cms/backof import type { UUISelectElement, UUISelectEvent } from '@umbraco-cms/backoffice/external/uui'; import { type UmbPropertyEditorUiElement, - UmbPropertyValueChangeEvent, type UmbPropertyEditorConfigCollection, } from '@umbraco-cms/backoffice/property-editor'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; +import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; /** * @element umb-property-editor-ui-value-type @@ -49,7 +49,7 @@ export class UmbPropertyEditorUIValueTypeElement extends UmbLitElement implement #onChange(e: UUISelectEvent) { this.value = e.target.value as string; - this.dispatchEvent(new UmbPropertyValueChangeEvent()); + this.dispatchEvent(new UmbChangeEvent()); } override render() { diff --git a/src/Umbraco.Web.UI.Client/src/packages/publish-cache/dashboard-published-status.element.ts b/src/Umbraco.Web.UI.Client/src/packages/publish-cache/dashboard-published-status.element.ts index eeead01e785d..f05a6bf48831 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/publish-cache/dashboard-published-status.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/publish-cache/dashboard-published-status.element.ts @@ -14,6 +14,8 @@ export class UmbDashboardPublishedStatusElement extends UmbLitElement { @state() private _buttonStateRebuild: UUIButtonState = undefined; + #isFirstRebuildStatusPoll: boolean = true; + //Reload private async _reloadMemoryCache() { this._buttonStateReload = 'waiting'; @@ -37,12 +39,31 @@ export class UmbDashboardPublishedStatusElement extends UmbLitElement { // Rebuild private async _rebuildDatabaseCache() { + this._buttonStateRebuild = 'waiting'; const { error } = await tryExecuteAndNotify(this, PublishedCacheService.postPublishedCacheRebuild()); if (error) { this._buttonStateRebuild = 'failed'; } else { - this._buttonStateRebuild = 'success'; + this.#isFirstRebuildStatusPoll = true; + this._pollForRebuildDatabaseCacheStatus(); + } + } + + private async _pollForRebuildDatabaseCacheStatus() { + //Checking the server after 1 second and then every 5 seconds to see if the database cache is still rebuilding. + while (this._buttonStateRebuild === 'waiting') { + await new Promise((resolve) => setTimeout(resolve, this.#isFirstRebuildStatusPoll ? 1000 : 5000)); + this.#isFirstRebuildStatusPoll = false; + const { data, error } = await tryExecuteAndNotify(this, PublishedCacheService.getPublishedCacheRebuildStatus()); + if (error || !data) { + this._buttonStateRebuild = 'failed'; + return; + } + + if (!data.isRebuilding) { + this._buttonStateRebuild = 'success'; + } } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/rte/components/rte-base.element.ts b/src/Umbraco.Web.UI.Client/src/packages/rte/components/rte-base.element.ts index e217f72f460d..a66d5843197e 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/rte/components/rte-base.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/rte/components/rte-base.element.ts @@ -4,7 +4,6 @@ import { property, state } from '@umbraco-cms/backoffice/external/lit'; import { observeMultiple } from '@umbraco-cms/backoffice/observable-api'; import { UmbBlockRteEntriesContext, UmbBlockRteManagerContext } from '@umbraco-cms/backoffice/block-rte'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; -import { UmbPropertyValueChangeEvent } from '@umbraco-cms/backoffice/property-editor'; import { UMB_PROPERTY_CONTEXT, UMB_PROPERTY_DATASET_CONTEXT } from '@umbraco-cms/backoffice/property'; import type { UmbBlockRteTypeModel } from '@umbraco-cms/backoffice/block-rte'; import type { @@ -17,6 +16,7 @@ import { UmbValidationContext, } from '@umbraco-cms/backoffice/validation'; import { UmbBlockElementDataValidationPathTranslator } from '@umbraco-cms/backoffice/block'; +import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; export abstract class UmbPropertyEditorUiRteElementBase extends UmbFormControlMixin<UmbPropertyEditorRteValueType | undefined, typeof UmbLitElement, undefined>(UmbLitElement) @@ -228,6 +228,6 @@ export abstract class UmbPropertyEditorUiRteElementBase } protected _fireChangeEvent() { - this.dispatchEvent(new UmbPropertyValueChangeEvent()); + this.dispatchEvent(new UmbChangeEvent()); } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/static-file/components/input-static-file/input-static-file.context.ts b/src/Umbraco.Web.UI.Client/src/packages/static-file/components/input-static-file/input-static-file.context.ts index ddc063b13656..7a6bc3826a84 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/static-file/components/input-static-file/input-static-file.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/static-file/components/input-static-file/input-static-file.context.ts @@ -15,6 +15,3 @@ export class UmbStaticFilePickerInputContext extends UmbPickerInputContext< super(host, UMB_STATIC_FILE_ITEM_REPOSITORY_ALIAS, UMB_STATIC_FILE_PICKER_MODAL); } } - -/** @deprecated Use `UmbStaticFilePickerInputContext` instead. This method will be removed in Umbraco 15. */ -export { UmbStaticFilePickerInputContext as UmbStaticFilePickerContext }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/static-file/property-editors/static-file-picker/property-editor-ui-static-file-picker.element.ts b/src/Umbraco.Web.UI.Client/src/packages/static-file/property-editors/static-file-picker/property-editor-ui-static-file-picker.element.ts index c4d4e36dd5fd..ab402df21dbf 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/static-file/property-editors/static-file-picker/property-editor-ui-static-file-picker.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/static-file/property-editors/static-file-picker/property-editor-ui-static-file-picker.element.ts @@ -1,7 +1,6 @@ import type { UmbInputStaticFileElement } from '../../components/index.js'; import { type UmbPropertyEditorUiElement, - UmbPropertyValueChangeEvent, type UmbPropertyEditorConfigCollection, } from '@umbraco-cms/backoffice/property-editor'; import { customElement, html, property, state } from '@umbraco-cms/backoffice/external/lit'; @@ -9,6 +8,7 @@ import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import type { UmbNumberRangeValueType } from '@umbraco-cms/backoffice/models'; import '../../components/input-static-file/index.js'; import { UmbServerFilePathUniqueSerializer } from '@umbraco-cms/backoffice/server-file-system'; +import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; @customElement('umb-property-editor-ui-static-file-picker') export class UmbPropertyEditorUIStaticFilePickerElement extends UmbLitElement implements UmbPropertyEditorUiElement { @@ -58,7 +58,7 @@ export class UmbPropertyEditorUIStaticFilePickerElement extends UmbLitElement im } else { this._value = (event.target as UmbInputStaticFileElement).selection; } - this.dispatchEvent(new UmbPropertyValueChangeEvent()); + this.dispatchEvent(new UmbChangeEvent()); } // TODO: Implement mandatory? diff --git a/src/Umbraco.Web.UI.Client/src/packages/tags/property-editors/tags/Umbraco.Tags.ts b/src/Umbraco.Web.UI.Client/src/packages/tags/property-editors/tags/Umbraco.Tags.ts index 5726481026ff..0da099b29a9e 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/tags/property-editors/tags/Umbraco.Tags.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/tags/property-editors/tags/Umbraco.Tags.ts @@ -10,14 +10,15 @@ export const manifest: ManifestPropertyEditorSchema = { properties: [ { alias: 'group', - label: 'Define a tag group', + label: 'Tag group', description: '', propertyEditorUiAlias: 'Umb.PropertyEditorUi.TextBox', }, { alias: 'storageType', label: 'Storage Type', - description: '', + description: + 'Select whether to store the tags in cache as JSON (default) or CSV format. Notice that CSV does not support commas in the tag value.', propertyEditorUiAlias: 'Umb.PropertyEditorUi.Select', config: [ { diff --git a/src/Umbraco.Web.UI.Client/src/packages/tags/property-editors/tags/property-editor-ui-tags.element.ts b/src/Umbraco.Web.UI.Client/src/packages/tags/property-editors/tags/property-editor-ui-tags.element.ts index f8e30b459683..2deede67d561 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/tags/property-editors/tags/property-editor-ui-tags.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/tags/property-editors/tags/property-editor-ui-tags.element.ts @@ -3,12 +3,12 @@ import { UMB_PROPERTY_CONTEXT } from '@umbraco-cms/backoffice/property'; import { html, customElement, property, state, ifDefined } from '@umbraco-cms/backoffice/external/lit'; import { type UmbPropertyEditorUiElement, - UmbPropertyValueChangeEvent, type UmbPropertyEditorConfigCollection, } from '@umbraco-cms/backoffice/property-editor'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import '../../components/tags-input/tags-input.element.js'; +import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; /** * @element umb-property-editor-ui-tags @@ -63,7 +63,7 @@ export class UmbPropertyEditorUITagsElement extends UmbLitElement implements Umb #onChange(event: CustomEvent) { this.value = ((event.target as UmbTagsInputElement).value as string).split(','); - this.dispatchEvent(new UmbPropertyValueChangeEvent()); + this.dispatchEvent(new UmbChangeEvent()); } override render() { diff --git a/src/Umbraco.Web.UI.Client/src/packages/templating/partial-views/global-components/input-partial-view/input-partial-view.context.ts b/src/Umbraco.Web.UI.Client/src/packages/templating/partial-views/global-components/input-partial-view/input-partial-view.context.ts index 651225cede30..37ca2c9b3434 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/templating/partial-views/global-components/input-partial-view/input-partial-view.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/templating/partial-views/global-components/input-partial-view/input-partial-view.context.ts @@ -13,6 +13,3 @@ export class UmbPartialViewPickerInputContext extends UmbPickerInputContext< super(host, UMB_PARTIAL_VIEW_ITEM_REPOSITORY_ALIAS, UMB_PARTIAL_VIEW_PICKER_MODAL); } } - -/** @deprecated Use `UmbPartialViewPickerInputContext` instead. This method will be removed in Umbraco 15. */ -export { UmbPartialViewPickerInputContext as UmbPartialViewPickerContext }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/templating/scripts/global-components/input-script/input-script.context.ts b/src/Umbraco.Web.UI.Client/src/packages/templating/scripts/global-components/input-script/input-script.context.ts index c5029b9845ed..a0890e606522 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/templating/scripts/global-components/input-script/input-script.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/templating/scripts/global-components/input-script/input-script.context.ts @@ -9,6 +9,3 @@ export class UmbScriptPickerInputContext extends UmbPickerInputContext<UmbScript super(host, UMB_SCRIPT_ITEM_REPOSITORY_ALIAS, UMB_SCRIPT_PICKER_MODAL); } } - -/** @deprecated Use `UmbScriptPickerInputContext` instead. This method will be removed in Umbraco 15. */ -export { UmbScriptPickerInputContext as UmbScriptPickerContext }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/templating/stylesheets/global-components/stylesheet-input/stylesheet-input.context.ts b/src/Umbraco.Web.UI.Client/src/packages/templating/stylesheets/global-components/stylesheet-input/stylesheet-input.context.ts index abc9f0743fa6..9ef3ef4b6f44 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/templating/stylesheets/global-components/stylesheet-input/stylesheet-input.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/templating/stylesheets/global-components/stylesheet-input/stylesheet-input.context.ts @@ -12,6 +12,3 @@ export class UmbStylesheetPickerInputContext extends UmbPickerInputContext<UmbSt super(host, UMB_STYLESHEET_ITEM_REPOSITORY_ALIAS, UMB_STYLESHEET_PICKER_MODAL); } } - -/** @deprecated Use `UmbStylesheetPickerInputContext` instead. This method will be removed in Umbraco 15. */ -export { UmbStylesheetPickerInputContext as UmbStylesheetPickerContext }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/templating/stylesheets/property-editors/stylesheet-picker/property-editor-ui-stylesheet-picker.element.ts b/src/Umbraco.Web.UI.Client/src/packages/templating/stylesheets/property-editors/stylesheet-picker/property-editor-ui-stylesheet-picker.element.ts index af764134c698..f2fb34562b23 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/templating/stylesheets/property-editors/stylesheet-picker/property-editor-ui-stylesheet-picker.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/templating/stylesheets/property-editors/stylesheet-picker/property-editor-ui-stylesheet-picker.element.ts @@ -1,12 +1,12 @@ import { customElement, html, property } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; -import { UmbPropertyValueChangeEvent } from '@umbraco-cms/backoffice/property-editor'; import { UmbServerFilePathUniqueSerializer } from '@umbraco-cms/backoffice/server-file-system'; import type { UmbPropertyEditorConfigCollection, UmbPropertyEditorUiElement, } from '@umbraco-cms/backoffice/property-editor'; import type { UmbStylesheetInputElement } from '@umbraco-cms/backoffice/stylesheet'; +import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; @customElement('umb-property-editor-ui-stylesheet-picker') export class UmbPropertyEditorUIStylesheetPickerElement extends UmbLitElement implements UmbPropertyEditorUiElement { @@ -29,7 +29,7 @@ export class UmbPropertyEditorUIStylesheetPickerElement extends UmbLitElement im #onChange(event: CustomEvent) { const target = event.target as UmbStylesheetInputElement; this.#value = target.selection ?? []; - this.dispatchEvent(new UmbPropertyValueChangeEvent()); + this.dispatchEvent(new UmbChangeEvent()); } override render() { diff --git a/src/Umbraco.Web.UI.Client/src/packages/templating/templates/global-components/input-template/input-template.context.ts b/src/Umbraco.Web.UI.Client/src/packages/templating/templates/global-components/input-template/input-template.context.ts index 65d9510c650d..165a5e1b8522 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/templating/templates/global-components/input-template/input-template.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/templating/templates/global-components/input-template/input-template.context.ts @@ -11,6 +11,3 @@ export class UmbTemplatePickerInputContext extends UmbPickerInputContext<UmbTemp super(host, UMB_TEMPLATE_ITEM_REPOSITORY_ALIAS, UMB_TEMPLATE_PICKER_MODAL); } } - -/** @deprecated Use `UmbTemplatePickerInputContext` instead. This method will be removed in Umbraco 15. */ -export { UmbTemplatePickerInputContext as UmbTemplatePickerContext }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/templating/templates/modals/query-builder/query-builder-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/templating/templates/modals/query-builder/query-builder-modal.element.ts index a3e286347147..2ecf70b81933 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/templating/templates/modals/query-builder/query-builder-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/templating/templates/modals/query-builder/query-builder-modal.element.ts @@ -135,29 +135,21 @@ export default class UmbTemplateQueryBuilderModalElement extends UmbModalBaseEle #setSortProperty(event: Event) { const target = event.target as UUIComboboxListElement; - - if (!this._queryRequest.sort) this.#setSortDirection(); - - this.#updateQueryRequest({ - sort: { ...this._queryRequest.sort, propertyAlias: target.value as string }, - }); + this.#setSort(target.value as string, this._queryRequest.sort?.direction as SortOrder ?? this._defaultSortDirection); } #setSortDirection() { - if (!this._queryRequest.sort?.direction) { - this.#updateQueryRequest({ - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - sort: { ...this._queryRequest.sort, direction: this._defaultSortDirection }, - }); - return; - } + const direction = this._queryRequest.sort?.direction + ? this._queryRequest.sort.direction === SortOrder.Ascending ? SortOrder.Descending : SortOrder.Ascending + : this._defaultSortDirection; + this.#setSort(this._queryRequest.sort?.propertyAlias ?? "", direction); + } + #setSort(propertyAlias: string, direction: SortOrder) { this.#updateQueryRequest({ sort: { - ...this._queryRequest.sort, - direction: - this._queryRequest.sort?.direction === SortOrder.Ascending ? SortOrder.Descending : SortOrder.Ascending, + propertyAlias, + direction }, }); } diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/plugins/tinymce-plugin.extension.ts b/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/plugins/tinymce-plugin.extension.ts index a4921daf0545..2506a6659763 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/plugins/tinymce-plugin.extension.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/plugins/tinymce-plugin.extension.ts @@ -28,9 +28,10 @@ export interface MetaTinyMcePlugin { }>; /** - * Sets the default configuration for the TinyMCE editor. This configuration will be used when the editor is initialized. - * @see [TinyMCE Configuration](https://www.tiny.cloud/docs/configure/) for more information. + * @title Sets the default configuration for the TinyMCE editor. + * @description This configuration will be used when the editor is initialized. See the [TinyMCE Configuration](https://www.tiny.cloud/docs/configure/) for more information. * @optional + * @TJS-type object * @examples [ * { * "plugins": "wordcount", diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/property-editors/block/property-editor-ui-block-rte-type-configuration.element.ts b/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/property-editors/block/property-editor-ui-block-rte-type-configuration.element.ts index a830297e3604..008a595ced29 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/property-editors/block/property-editor-ui-block-rte-type-configuration.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/property-editors/block/property-editor-ui-block-rte-type-configuration.element.ts @@ -2,7 +2,7 @@ import { customElement, html, property, state, nothing } from '@umbraco-cms/back import { UmbInputBlockTypeElement } from '@umbraco-cms/backoffice/block-type'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import { UmbModalRouteRegistrationController } from '@umbraco-cms/backoffice/router'; -import { UmbPropertyValueChangeEvent } from '@umbraco-cms/backoffice/property-editor'; +import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; import { UMB_BLOCK_RTE_TYPE } from '@umbraco-cms/backoffice/block-rte'; import { UMB_WORKSPACE_MODAL } from '@umbraco-cms/backoffice/workspace'; import type { UmbBlockTypeBaseModel } from '@umbraco-cms/backoffice/block-type'; @@ -57,7 +57,7 @@ export class UmbPropertyEditorUIBlockRteBlockConfigurationElement #onChange(e: CustomEvent) { e.stopPropagation(); this.value = (e.target as UmbInputBlockTypeElement).value; - this.dispatchEvent(new UmbPropertyValueChangeEvent()); + this.dispatchEvent(new UmbChangeEvent()); } override render() { diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/property-editors/dimensions/property-editor-ui-tiny-mce-dimensions-configuration.element.ts b/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/property-editors/dimensions/property-editor-ui-tiny-mce-dimensions-configuration.element.ts index e47c74dce585..893938217d95 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/property-editors/dimensions/property-editor-ui-tiny-mce-dimensions-configuration.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/property-editors/dimensions/property-editor-ui-tiny-mce-dimensions-configuration.element.ts @@ -2,7 +2,7 @@ import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import { customElement, html, property } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import type { UUIInputEvent } from '@umbraco-cms/backoffice/external/uui'; -import { UmbPropertyValueChangeEvent } from '@umbraco-cms/backoffice/property-editor'; +import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; /** * @element umb-property-editor-ui-tiny-mce-dimensions-configuration @@ -14,11 +14,11 @@ export class UmbPropertyEditorUITinyMceDimensionsConfigurationElement extends Um #onChangeWidth(e: UUIInputEvent) { this.value = { ...this.value, width: Number(e.target.value as string) }; - this.dispatchEvent(new UmbPropertyValueChangeEvent()); + this.dispatchEvent(new UmbChangeEvent()); } #onChangeHeight(e: UUIInputEvent) { this.value = { ...this.value, height: Number(e.target.value as string) }; - this.dispatchEvent(new UmbPropertyValueChangeEvent()); + this.dispatchEvent(new UmbChangeEvent()); } override render() { diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/property-editors/max-image-size/property-editor-ui-tiny-mce-maximagesize.element.ts b/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/property-editors/max-image-size/property-editor-ui-tiny-mce-maximagesize.element.ts index 9136acf3fa47..01b3f1656abd 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/property-editors/max-image-size/property-editor-ui-tiny-mce-maximagesize.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/property-editors/max-image-size/property-editor-ui-tiny-mce-maximagesize.element.ts @@ -1,8 +1,8 @@ import { customElement, html, property } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; -import { UmbPropertyValueChangeEvent } from '@umbraco-cms/backoffice/property-editor'; import type { UmbPropertyEditorUiElement } from '@umbraco-cms/backoffice/property-editor'; import type { UUIInputEvent } from '@umbraco-cms/backoffice/external/uui'; +import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; /** * @element umb-property-editor-ui-tiny-mce-maximagesize @@ -14,7 +14,7 @@ export class UmbPropertyEditorUITinyMceMaxImageSizeElement extends UmbLitElement #onChange(e: UUIInputEvent) { this.value = Number(e.target.value as string); - this.dispatchEvent(new UmbPropertyValueChangeEvent()); + this.dispatchEvent(new UmbChangeEvent()); } override render() { diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/property-editors/stylesheets/property-editor-ui-tiny-mce-stylesheets-configuration.element.ts b/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/property-editors/stylesheets/property-editor-ui-tiny-mce-stylesheets-configuration.element.ts index 048383857c37..b0df41b3a64d 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/property-editors/stylesheets/property-editor-ui-tiny-mce-stylesheets-configuration.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/property-editors/stylesheets/property-editor-ui-tiny-mce-stylesheets-configuration.element.ts @@ -1,13 +1,13 @@ import { customElement, html, property } from '@umbraco-cms/backoffice/external/lit'; import { UmbDeprecation } from '@umbraco-cms/backoffice/utils'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; -import { UmbPropertyValueChangeEvent } from '@umbraco-cms/backoffice/property-editor'; import { UmbServerFilePathUniqueSerializer } from '@umbraco-cms/backoffice/server-file-system'; import type { UmbPropertyEditorConfigCollection, UmbPropertyEditorUiElement, } from '@umbraco-cms/backoffice/property-editor'; import type { UmbStylesheetInputElement } from '@umbraco-cms/backoffice/stylesheet'; +import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; /** * @element umb-property-editor-ui-tiny-mce-stylesheets-configuration @@ -36,7 +36,7 @@ export class UmbPropertyEditorUITinyMceStylesheetsConfigurationElement #onChange(event: CustomEvent) { const target = event.target as UmbStylesheetInputElement; this.#value = target.selection ?? []; - this.dispatchEvent(new UmbPropertyValueChangeEvent()); + this.dispatchEvent(new UmbChangeEvent()); } constructor() { diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/property-editors/toolbar/property-editor-ui-tiny-mce-toolbar-configuration.element.ts b/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/property-editors/toolbar/property-editor-ui-tiny-mce-toolbar-configuration.element.ts index a63effc1e660..f5c1d1e227bc 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/property-editors/toolbar/property-editor-ui-tiny-mce-toolbar-configuration.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/property-editors/toolbar/property-editor-ui-tiny-mce-toolbar-configuration.element.ts @@ -3,13 +3,13 @@ import { firstValueFrom } from '@umbraco-cms/backoffice/external/rxjs'; import { tinymce } from '@umbraco-cms/backoffice/external/tinymce'; import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; -import { UmbPropertyValueChangeEvent } from '@umbraco-cms/backoffice/property-editor'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import type { PropertyValueMap } from '@umbraco-cms/backoffice/external/lit'; import type { UmbPropertyEditorUiElement, UmbPropertyEditorConfigCollection, } from '@umbraco-cms/backoffice/property-editor'; +import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; const tinyIconSet = tinymce.IconManager.get('default'); @@ -109,7 +109,7 @@ export class UmbPropertyEditorUITinyMceToolbarConfigurationElement this.value = value; - this.dispatchEvent(new UmbPropertyValueChangeEvent()); + this.dispatchEvent(new UmbChangeEvent()); } override render() { diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiptap/components/anchor-modal/anchor-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/tiptap/components/anchor-modal/anchor-modal.element.ts new file mode 100644 index 000000000000..e86a99a458ff --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/tiptap/components/anchor-modal/anchor-modal.element.ts @@ -0,0 +1,84 @@ +import type { UmbTiptapAnchorModalData, UmbTiptapAnchorModalValue } from './anchor-modal.token.js'; +import { css, customElement, html } from '@umbraco-cms/backoffice/external/lit'; +import { umbFocus } from '@umbraco-cms/backoffice/lit-element'; +import { UmbModalBaseElement } from '@umbraco-cms/backoffice/modal'; + +@customElement('umb-tiptap-anchor-modal') +export class UmbTiptapAnchorModalElement extends UmbModalBaseElement< + UmbTiptapAnchorModalData, + UmbTiptapAnchorModalValue +> { + async #onSubmit(event: SubmitEvent) { + event.preventDefault(); + + const form = event.target as HTMLFormElement; + if (!form) return; + + const isValid = form.checkValidity(); + if (!isValid) return; + + const formData = new FormData(form); + const name = formData.get('name') as string; + + this.value = name; + this._submitModal(); + } + + override render() { + const label = this.localize.term('tiptap_anchor_input'); + return html` + <uui-dialog-layout> + <uui-form> + <form id="form" @submit=${this.#onSubmit}> + <uui-form-layout-item> + <uui-label for="name" slot="label" required>${label}</uui-label> + <uui-input + type="text" + required + id="name" + name="name" + label=${label} + .value=${this.data?.id || ''} + ${umbFocus()}></uui-input> + </uui-form-layout-item> + </form> + </uui-form> + <uui-button + slot="actions" + label=${this.localize.term('buttons_confirmActionCancel')} + @click=${this._rejectModal}></uui-button> + <uui-button + type="submit" + slot="actions" + form="form" + color="positive" + look="primary" + label=${this.localize.term('general_submit')}></uui-button> + </uui-dialog-layout> + `; + } + + static override styles = [ + css` + :host { + --umb-body-layout-color-background: var(--uui-color-surface); + } + + uui-dialog-layout { + width: var(--uui-size-100); + } + + uui-input { + width: 100%; + } + `, + ]; +} + +export { UmbTiptapAnchorModalElement as element }; + +declare global { + interface HTMLElementTagNameMap { + 'umb-tiptap-anchor-modal': UmbTiptapAnchorModalElement; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiptap/components/anchor-modal/anchor-modal.token.ts b/src/Umbraco.Web.UI.Client/src/packages/tiptap/components/anchor-modal/anchor-modal.token.ts new file mode 100644 index 000000000000..68e763d3767a --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/tiptap/components/anchor-modal/anchor-modal.token.ts @@ -0,0 +1,15 @@ +import { UMB_TIPTAP_ANCHOR_MODAL_ALIAS } from './constants.js'; +import { UmbModalToken } from '@umbraco-cms/backoffice/modal'; + +export type UmbTiptapAnchorModalData = { + id?: string; +}; + +export type UmbTiptapAnchorModalValue = string; + +export const UMB_TIPTAP_ANCHOR_MODAL = new UmbModalToken<UmbTiptapAnchorModalData, UmbTiptapAnchorModalValue>( + UMB_TIPTAP_ANCHOR_MODAL_ALIAS, + { + modal: { type: 'dialog' }, + }, +); diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiptap/components/anchor-modal/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/tiptap/components/anchor-modal/constants.ts new file mode 100644 index 000000000000..16efc1e72606 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/tiptap/components/anchor-modal/constants.ts @@ -0,0 +1 @@ +export const UMB_TIPTAP_ANCHOR_MODAL_ALIAS = 'Umb.Modal.Tiptap.Anchor'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiptap/components/anchor-modal/index.ts b/src/Umbraco.Web.UI.Client/src/packages/tiptap/components/anchor-modal/index.ts new file mode 100644 index 000000000000..2bdc2fe35edd --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/tiptap/components/anchor-modal/index.ts @@ -0,0 +1 @@ +export * from './anchor-modal.token.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiptap/components/anchor-modal/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/tiptap/components/anchor-modal/manifests.ts new file mode 100644 index 000000000000..99bbb4a2b52d --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/tiptap/components/anchor-modal/manifests.ts @@ -0,0 +1,11 @@ +import { UMB_TIPTAP_ANCHOR_MODAL_ALIAS } from './constants.js'; +import type { ManifestModal } from '@umbraco-cms/backoffice/modal'; + +export const manifests: Array<ManifestModal> = [ + { + type: 'modal', + alias: UMB_TIPTAP_ANCHOR_MODAL_ALIAS, + name: 'Tiptap Anchor Modal', + element: () => import('./anchor-modal.element.js'), + }, +]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiptap/components/character-map/character-map-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/tiptap/components/character-map/character-map-modal.element.ts new file mode 100644 index 000000000000..4056251ee2e7 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/tiptap/components/character-map/character-map-modal.element.ts @@ -0,0 +1,472 @@ +import type { UmbTiptapCharacterMapModalData, UmbTiptapCharacterMapModalValue } from './character-map-modal.token.js'; +import { css, customElement, html, repeat, state, when } from '@umbraco-cms/backoffice/external/lit'; +import { umbFocus } from '@umbraco-cms/backoffice/lit-element'; +import { UmbModalBaseElement } from '@umbraco-cms/backoffice/modal'; + +@customElement('umb-character-map-modal') +export class UmbCharacterMapModalElement extends UmbModalBaseElement< + UmbTiptapCharacterMapModalData, + UmbTiptapCharacterMapModalValue +> { + /* The character mapping code has been derived from TinyMCE. + * https://github.com/tinymce/tinymce/blob/6.8.5/modules/tinymce/src/plugins/charmap/main/ts/core/CharMap.ts#L20-L362 + * SPDX-License-Identifier: MIT + * Copyright © 2022 Ephox Corporation DBA Tiny Technologies, Inc. + * Modifications are licensed under the MIT License. */ + #characterMap: Record<string, Array<[number, string]>> = { + '#general_all': [], + '#tiptap_charmap_currency': [ + [36, 'dollar sign'], + [162, 'cent sign'], + [8364, 'euro sign'], + [163, 'pound sign'], + [165, 'yen sign'], + [164, 'currency sign'], + [8352, 'euro-currency sign'], + [8353, 'colon sign'], + [8354, 'cruzeiro sign'], + [8355, 'french franc sign'], + [8356, 'lira sign'], + [8357, 'mill sign'], + [8358, 'naira sign'], + [8359, 'peseta sign'], + [8360, 'rupee sign'], + [8361, 'won sign'], + [8362, 'new sheqel sign'], + [8363, 'dong sign'], + [8365, 'kip sign'], + [8366, 'tugrik sign'], + [8367, 'drachma sign'], + [8368, 'german penny symbol'], + [8369, 'peso sign'], + [8370, 'guarani sign'], + [8371, 'austral sign'], + [8372, 'hryvnia sign'], + [8373, 'cedi sign'], + [8374, 'livre tournois sign'], + [8375, 'spesmilo sign'], + [8376, 'tenge sign'], + [8377, 'indian rupee sign'], + [8378, 'turkish lira sign'], + [8379, 'nordic mark sign'], + [8380, 'manat sign'], + [8381, 'ruble sign'], + [20870, 'yen character'], + [20803, 'yuan character'], + [22291, 'yuan character, in hong kong and taiwan'], + [22278, 'yen/yuan character variant one'], + ], + '#tiptap_charmap_text': [ + [169, 'copyright sign'], + [174, 'registered sign'], + [8482, 'trade mark sign'], + [8240, 'per mille sign'], + [181, 'micro sign'], + [183, 'middle dot'], + [8226, 'bullet'], + [8230, 'three dot leader'], + [8242, 'minutes / feet'], + [8243, 'seconds / inches'], + [167, 'section sign'], + [182, 'paragraph sign'], + [223, 'sharp s / ess-zed'], + ], + '#tiptap_charmap_quotations': [ + [8249, 'single left-pointing angle quotation mark'], + [8250, 'single right-pointing angle quotation mark'], + [171, 'left pointing guillemet'], + [187, 'right pointing guillemet'], + [8216, 'left single quotation mark'], + [8217, 'right single quotation mark'], + [8220, 'left double quotation mark'], + [8221, 'right double quotation mark'], + [8218, 'single low-9 quotation mark'], + [8222, 'double low-9 quotation mark'], + [60, 'less-than sign'], + [62, 'greater-than sign'], + [8804, 'less-than or equal to'], + [8805, 'greater-than or equal to'], + [8211, 'en dash'], + [8212, 'em dash'], + [175, 'macron'], + [8254, 'overline'], + [164, 'currency sign'], + [166, 'broken bar'], + [168, 'diaeresis'], + [161, 'inverted exclamation mark'], + [191, 'turned question mark'], + [710, 'circumflex accent'], + [732, 'small tilde'], + [176, 'degree sign'], + [8722, 'minus sign'], + [177, 'plus-minus sign'], + [247, 'division sign'], + [8260, 'fraction slash'], + [215, 'multiplication sign'], + [185, 'superscript one'], + [178, 'superscript two'], + [179, 'superscript three'], + [188, 'fraction one quarter'], + [189, 'fraction one half'], + [190, 'fraction three quarters'], + ], + '#tiptap_charmap_maths': [ + [402, 'function / florin'], + [8747, 'integral'], + [8721, 'n-ary sumation'], + [8734, 'infinity'], + [8730, 'square root'], + [8764, 'similar to'], + [8773, 'approximately equal to'], + [8776, 'almost equal to'], + [8800, 'not equal to'], + [8801, 'identical to'], + [8712, 'element of'], + [8713, 'not an element of'], + [8715, 'contains as member'], + [8719, 'n-ary product'], + [8743, 'logical and'], + [8744, 'logical or'], + [172, 'not sign'], + [8745, 'intersection'], + [8746, 'union'], + [8706, 'partial differential'], + [8704, 'for all'], + [8707, 'there exists'], + [8709, 'diameter'], + [8711, 'backward difference'], + [8727, 'asterisk operator'], + [8733, 'proportional to'], + [8736, 'angle'], + ], + '#tiptap_charmap_extlatin': [ + [192, 'A - grave'], + [193, 'A - acute'], + [194, 'A - circumflex'], + [195, 'A - tilde'], + [196, 'A - diaeresis'], + [197, 'A - ring above'], + [256, 'A - macron'], + [198, 'ligature AE'], + [199, 'C - cedilla'], + [200, 'E - grave'], + [201, 'E - acute'], + [202, 'E - circumflex'], + [203, 'E - diaeresis'], + [274, 'E - macron'], + [204, 'I - grave'], + [205, 'I - acute'], + [206, 'I - circumflex'], + [207, 'I - diaeresis'], + [298, 'I - macron'], + [208, 'ETH'], + [209, 'N - tilde'], + [210, 'O - grave'], + [211, 'O - acute'], + [212, 'O - circumflex'], + [213, 'O - tilde'], + [214, 'O - diaeresis'], + [216, 'O - slash'], + [332, 'O - macron'], + [338, 'ligature OE'], + [352, 'S - caron'], + [217, 'U - grave'], + [218, 'U - acute'], + [219, 'U - circumflex'], + [220, 'U - diaeresis'], + [362, 'U - macron'], + [221, 'Y - acute'], + [376, 'Y - diaeresis'], + [562, 'Y - macron'], + [222, 'THORN'], + [224, 'a - grave'], + [225, 'a - acute'], + [226, 'a - circumflex'], + [227, 'a - tilde'], + [228, 'a - diaeresis'], + [229, 'a - ring above'], + [257, 'a - macron'], + [230, 'ligature ae'], + [231, 'c - cedilla'], + [232, 'e - grave'], + [233, 'e - acute'], + [234, 'e - circumflex'], + [235, 'e - diaeresis'], + [275, 'e - macron'], + [236, 'i - grave'], + [237, 'i - acute'], + [238, 'i - circumflex'], + [239, 'i - diaeresis'], + [299, 'i - macron'], + [240, 'eth'], + [241, 'n - tilde'], + [242, 'o - grave'], + [243, 'o - acute'], + [244, 'o - circumflex'], + [245, 'o - tilde'], + [246, 'o - diaeresis'], + [248, 'o slash'], + [333, 'o macron'], + [339, 'ligature oe'], + [353, 's - caron'], + [249, 'u - grave'], + [250, 'u - acute'], + [251, 'u - circumflex'], + [252, 'u - diaeresis'], + [363, 'u - macron'], + [253, 'y - acute'], + [254, 'thorn'], + [255, 'y - diaeresis'], + [563, 'y - macron'], + [913, 'Alpha'], + [914, 'Beta'], + [915, 'Gamma'], + [916, 'Delta'], + [917, 'Epsilon'], + [918, 'Zeta'], + [919, 'Eta'], + [920, 'Theta'], + [921, 'Iota'], + [922, 'Kappa'], + [923, 'Lambda'], + [924, 'Mu'], + [925, 'Nu'], + [926, 'Xi'], + [927, 'Omicron'], + [928, 'Pi'], + [929, 'Rho'], + [931, 'Sigma'], + [932, 'Tau'], + [933, 'Upsilon'], + [934, 'Phi'], + [935, 'Chi'], + [936, 'Psi'], + [937, 'Omega'], + [945, 'alpha'], + [946, 'beta'], + [947, 'gamma'], + [948, 'delta'], + [949, 'epsilon'], + [950, 'zeta'], + [951, 'eta'], + [952, 'theta'], + [953, 'iota'], + [954, 'kappa'], + [955, 'lambda'], + [956, 'mu'], + [957, 'nu'], + [958, 'xi'], + [959, 'omicron'], + [960, 'pi'], + [961, 'rho'], + [962, 'final sigma'], + [963, 'sigma'], + [964, 'tau'], + [965, 'upsilon'], + [966, 'phi'], + [967, 'chi'], + [968, 'psi'], + [969, 'omega'], + ], + '#tiptap_charmap_symbols': [ + [8501, 'alef symbol'], + [982, 'pi symbol'], + [8476, 'real part symbol'], + [978, 'upsilon - hook symbol'], + [8472, 'Weierstrass p'], + [8465, 'imaginary part'], + ], + '#tiptap_charmap_arrows': [ + [8592, 'leftwards arrow'], + [8593, 'upwards arrow'], + [8594, 'rightwards arrow'], + [8595, 'downwards arrow'], + [8596, 'left right arrow'], + [8629, 'carriage return'], + [8656, 'leftwards double arrow'], + [8657, 'upwards double arrow'], + [8658, 'rightwards double arrow'], + [8659, 'downwards double arrow'], + [8660, 'left right double arrow'], + [8756, 'therefore'], + [8834, 'subset of'], + [8835, 'superset of'], + [8836, 'not a subset of'], + [8838, 'subset of or equal to'], + [8839, 'superset of or equal to'], + [8853, 'circled plus'], + [8855, 'circled times'], + [8869, 'perpendicular'], + [8901, 'dot operator'], + [8968, 'left ceiling'], + [8969, 'right ceiling'], + [8970, 'left floor'], + [8971, 'right floor'], + [9001, 'left-pointing angle bracket'], + [9002, 'right-pointing angle bracket'], + [9674, 'lozenge'], + [9824, 'black spade suit'], + [9827, 'black club suit'], + [9829, 'black heart suit'], + [9830, 'black diamond suit'], + [8194, 'en space'], + [8195, 'em space'], + [8201, 'thin space'], + [8204, 'zero width non-joiner'], + [8205, 'zero width joiner'], + [8206, 'left-to-right mark'], + [8207, 'right-to-left mark'], + ], + }; + + @state() + private _filterQuery = ''; + + @state() + private _hoverLabel: string = ''; + + @state() + private _selectedGroup: string = '#general_all'; + + #onClickCharacter(code: number) { + this.value = String.fromCharCode(code); + this._submitModal(); + } + + #onFilterInput(event: InputEvent & { target: HTMLInputElement }) { + this._filterQuery = (event.target.value ?? '').toLocaleLowerCase(); + } + + override render() { + return html` + <umb-body-layout headline=${this.localize.term('tiptap_charmap_headline')}> + ${this.#renderCharacterMap()} + <uui-button + slot="actions" + label=${this.localize.term('general_close')} + @click=${this._rejectModal}></uui-button> + </umb-body-layout> + `; + } + + #filterHandler = ([, label]: [number, string]) => + !this._filterQuery || label.toLocaleLowerCase().includes(this._filterQuery); + + #renderCharacterMap() { + const characters = + this._selectedGroup && this._selectedGroup !== '#general_all' + ? this.#characterMap[this._selectedGroup].filter(this.#filterHandler) + : Object.values(this.#characterMap).flat().filter(this.#filterHandler); + return html` + <div id="container"> + <div> + ${repeat( + Object.keys(this.#characterMap), + (group) => group, + (group) => html` + <uui-menu-item + label=${this.localize.string(group)} + ?active=${this._selectedGroup === group} + @click-label=${() => (this._selectedGroup = group)}></uui-menu-item> + `, + )} + </div> + <div id="main"> + <uui-input + type="search" + autocomplete="off" + placeholder=${this.localize.term('placeholders_filter')} + @input=${this.#onFilterInput} + ${umbFocus()}> + <div slot="prepend"> + <uui-icon name="search"></uui-icon> + </div> + </uui-input> + <uui-scroll-container> + ${when( + characters?.length, + () => html` + <div id="characters"> + ${repeat( + characters, + ([code]) => code, + ([code, label]) => html` + <uui-button + label=${label} + title=${label} + @click=${() => this.#onClickCharacter(code)} + @mouseover=${() => (this._hoverLabel = label)} + @mouseleave=${() => (this._hoverLabel = '')}> + <span>${String.fromCharCode(code)}</span> + </uui-button> + `, + )} + </div> + `, + () => html`<p><umb-localize key="content_noItemsToShow">There are no items to show</umb-localize></p>`, + )} + </uui-scroll-container> + </div> + </div> + <div slot="footer-info">${this._hoverLabel}</div> + `; + } + + static override styles = [ + css` + :host { + --umb-body-layout-color-background: var(--uui-color-surface); + --uui-menu-item-flat-structure: 1; + } + + #container { + display: grid; + grid-template-columns: var(--uui-size-48) 1fr; + gap: var(--uui-size-layout-1); + } + + #main { + display: flex; + flex-direction: column; + gap: var(--uui-size-space-4); + } + + uui-scroll-container { + height: 300px; + width: calc(450px + var(--uui-size-layout-1)); + } + + #characters { + display: grid; + grid-template-columns: repeat(auto-fill, var(--uui-size-14)); + gap: var(--uui-size-5); + padding: var(--uui-size-1); + + uui-button { + --uui-button-font-weight: normal; + + border-radius: var(--uui-border-radius); + font-size: 1.5rem; + + &:focus, + &:hover { + outline: 2px solid var(--uui-color-selected); + } + } + } + + div[slot='footer-info'] { + margin-left: var(--uui-size-layout-1); + text-transform: capitalize; + } + `, + ]; +} + +export { UmbCharacterMapModalElement as element }; + +declare global { + interface HTMLElementTagNameMap { + 'umb-character-map-modal': UmbCharacterMapModalElement; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiptap/components/character-map/character-map-modal.token.ts b/src/Umbraco.Web.UI.Client/src/packages/tiptap/components/character-map/character-map-modal.token.ts new file mode 100644 index 000000000000..402ac2a8246f --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/tiptap/components/character-map/character-map-modal.token.ts @@ -0,0 +1,13 @@ +import { UMB_TIPTAP_CHARACTER_MAP_MODAL_ALIAS } from './constants.js'; +import { UmbModalToken } from '@umbraco-cms/backoffice/modal'; + +export type UmbTiptapCharacterMapModalData = never; + +export type UmbTiptapCharacterMapModalValue = string; + +export const UMB_TIPTAP_CHARACTER_MAP_MODAL = new UmbModalToken< + UmbTiptapCharacterMapModalData, + UmbTiptapCharacterMapModalValue +>(UMB_TIPTAP_CHARACTER_MAP_MODAL_ALIAS, { + modal: { type: 'dialog' }, +}); diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiptap/components/character-map/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/tiptap/components/character-map/constants.ts new file mode 100644 index 000000000000..391d3b83ecd6 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/tiptap/components/character-map/constants.ts @@ -0,0 +1 @@ +export const UMB_TIPTAP_CHARACTER_MAP_MODAL_ALIAS = 'Umb.Modal.Tiptap.CharacterMap'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiptap/components/character-map/index.ts b/src/Umbraco.Web.UI.Client/src/packages/tiptap/components/character-map/index.ts new file mode 100644 index 000000000000..49e9efcc4b5e --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/tiptap/components/character-map/index.ts @@ -0,0 +1 @@ +export * from './character-map-modal.token.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiptap/components/character-map/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/tiptap/components/character-map/manifests.ts new file mode 100644 index 000000000000..a238e84eb371 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/tiptap/components/character-map/manifests.ts @@ -0,0 +1,11 @@ +import { UMB_TIPTAP_CHARACTER_MAP_MODAL_ALIAS } from './constants.js'; +import type { ManifestModal } from '@umbraco-cms/backoffice/modal'; + +export const manifests: Array<ManifestModal> = [ + { + type: 'modal', + alias: UMB_TIPTAP_CHARACTER_MAP_MODAL_ALIAS, + name: 'Character Map Modal', + element: () => import('./character-map-modal.element.js'), + }, +]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiptap/components/index.ts b/src/Umbraco.Web.UI.Client/src/packages/tiptap/components/index.ts index edb0192afd0f..3f50b33fda00 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/tiptap/components/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/tiptap/components/index.ts @@ -1,2 +1,4 @@ export * from './input-tiptap/index.js'; +export * from './anchor-modal/index.js'; export * from './cascading-menu-popover/cascading-menu-popover.element.js'; +export * from './character-map/index.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiptap/components/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/tiptap/components/manifests.ts new file mode 100644 index 000000000000..a81acb72c3c3 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/tiptap/components/manifests.ts @@ -0,0 +1,7 @@ +import { manifests as anchorModal } from './anchor-modal/manifests.js'; +import { manifests as characterMap } from './character-map/manifests.js'; + +export const manifests: Array<UmbExtensionManifest> = [ + ...anchorModal, + ...characterMap, +]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiptap/components/toolbar/tiptap-toolbar-menu.element.ts b/src/Umbraco.Web.UI.Client/src/packages/tiptap/components/toolbar/tiptap-toolbar-menu.element.ts index e30cc9aefcdf..089ac20ade9c 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/tiptap/components/toolbar/tiptap-toolbar-menu.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/tiptap/components/toolbar/tiptap-toolbar-menu.element.ts @@ -129,7 +129,7 @@ export class UmbTiptapToolbarMenuElement extends UmbLitElement { </uui-button> `, () => html` - <uui-button compact look="secondary" label=${ifDefined(label)} popovertarget="popover-menu"> + <uui-button compact label=${ifDefined(label)} popovertarget="popover-menu"> <span>${label}</span> <uui-symbol-expand slot="extra" open></uui-symbol-expand> </uui-button> @@ -146,11 +146,12 @@ export class UmbTiptapToolbarMenuElement extends UmbLitElement { --uui-button-font-weight: normal; --uui-menu-item-flat-structure: 1; - margin-inline-start: var(--uui-size-space-1); + margin-left: var(--uui-size-space-1); + margin-bottom: var(--uui-size-space-1); } uui-button > uui-symbol-expand { - margin-left: var(--uui-size-space-4); + margin-left: var(--uui-size-space-2); } `, ]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiptap/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/tiptap/constants.ts index a818f164e93a..8613d3f9b4f2 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/tiptap/constants.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/tiptap/constants.ts @@ -1 +1,3 @@ +export * from './components/anchor-modal/constants.js'; +export * from './components/character-map/constants.js'; export * from './property-editors/tiptap/constants.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiptap/extensions/core/rich-text-essentials.tiptap-api.ts b/src/Umbraco.Web.UI.Client/src/packages/tiptap/extensions/core/rich-text-essentials.tiptap-api.ts index e51609936dbd..9a3e2fb22baa 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/tiptap/extensions/core/rich-text-essentials.tiptap-api.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/tiptap/extensions/core/rich-text-essentials.tiptap-api.ts @@ -1,10 +1,18 @@ import { UmbTiptapExtensionApiBase } from '../base.js'; import { css } from '@umbraco-cms/backoffice/external/lit'; -import { Div, HtmlGlobalAttributes, Span, StarterKit, TrailingNode } from '@umbraco-cms/backoffice/external/tiptap'; +import { + Anchor, + Div, + HtmlGlobalAttributes, + Span, + StarterKit, + TrailingNode, +} from '@umbraco-cms/backoffice/external/tiptap'; export class UmbTiptapRichTextEssentialsExtensionApi extends UmbTiptapExtensionApiBase { getTiptapExtensions = () => [ StarterKit, + Anchor, Div, Span, HtmlGlobalAttributes.configure({ @@ -76,6 +84,19 @@ export class UmbTiptapRichTextEssentialsExtensionApi extends UmbTiptapExtensionA padding: 0; } } + + span[data-umb-anchor] { + &.ProseMirror-selectednode { + border-radius: var(--uui-border-radius); + outline: 2px solid var(--uui-color-selected); + } + + uui-icon { + height: 1rem; + width: 1rem; + vertical-align: text-bottom; + } + } `; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiptap/extensions/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/tiptap/extensions/manifests.ts index 40425119188c..b7584dec6ae0 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/tiptap/extensions/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/tiptap/extensions/manifests.ts @@ -399,6 +399,18 @@ const toolbarExtensions: Array<UmbExtensionManifest> = [ label: 'Ordered List', }, }, + { + type: 'tiptapToolbarExtension', + kind: 'button', + alias: 'Umb.Tiptap.Toolbar.Anchor', + name: 'Anchor Tiptap Extension', + api: () => import('./toolbar/anchor.tiptap-toolbar-api.js'), + meta: { + alias: 'anchor', + icon: 'icon-anchor', + label: '#tiptap_anchor', + }, + }, { type: 'tiptapToolbarExtension', kind: 'button', @@ -582,6 +594,18 @@ const toolbarExtensions: Array<UmbExtensionManifest> = [ ], }, }, + { + type: 'tiptapToolbarExtension', + kind: 'button', + alias: 'Umb.Tiptap.Toolbar.CharacterMap', + name: 'Character Map Tiptap Extension', + api: () => import('./toolbar/character-map.tiptap-toolbar-api.js'), + meta: { + alias: 'umbCharacterMap', + icon: 'icon-omega', + label: '#tiptap_charmap', + }, + }, ]; const extensions = [ diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiptap/extensions/toolbar/anchor.tiptap-toolbar-api.ts b/src/Umbraco.Web.UI.Client/src/packages/tiptap/extensions/toolbar/anchor.tiptap-toolbar-api.ts new file mode 100644 index 000000000000..42a8d5f96a31 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/tiptap/extensions/toolbar/anchor.tiptap-toolbar-api.ts @@ -0,0 +1,24 @@ +import { UmbTiptapToolbarElementApiBase } from '../base.js'; +import { UMB_TIPTAP_ANCHOR_MODAL } from '../../components/anchor-modal/index.js'; +import { Anchor } from '@umbraco-cms/backoffice/external/tiptap'; +import { UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal'; +import type { Editor } from '@umbraco-cms/backoffice/external/tiptap'; + +export default class UmbTiptapToolbarAnchorExtensionApi extends UmbTiptapToolbarElementApiBase { + override async execute(editor?: Editor) { + const attrs = editor?.getAttributes(Anchor.name); + if (!attrs) return; + + const modalManager = await this.getContext(UMB_MODAL_MANAGER_CONTEXT); + const modal = modalManager.open(this, UMB_TIPTAP_ANCHOR_MODAL, { data: { id: attrs?.id } }); + if (!modal) return; + + const result = await modal.onSubmit().catch(() => undefined); + if (!result) return; + + editor + ?.chain() + .insertContent({ type: Anchor.name, attrs: { id: result } }) + .run(); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiptap/extensions/toolbar/character-map.tiptap-toolbar-api.ts b/src/Umbraco.Web.UI.Client/src/packages/tiptap/extensions/toolbar/character-map.tiptap-toolbar-api.ts new file mode 100644 index 000000000000..ef239b3727ea --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/tiptap/extensions/toolbar/character-map.tiptap-toolbar-api.ts @@ -0,0 +1,20 @@ +import { UmbTiptapToolbarElementApiBase } from '../base.js'; +import { UMB_TIPTAP_CHARACTER_MAP_MODAL } from '../../components/character-map/index.js'; +import { UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal'; +import type { Editor } from '@umbraco-cms/backoffice/external/tiptap'; + +export default class UmbTiptapToolbarCharacterMapExtensionApi extends UmbTiptapToolbarElementApiBase { + override async execute(editor?: Editor) { + if (!editor) return; + + const modalManager = await this.getContext(UMB_MODAL_MANAGER_CONTEXT); + const modal = modalManager.open(this, UMB_TIPTAP_CHARACTER_MAP_MODAL); + + if (!modal) return; + + const data = await modal.onSubmit().catch(() => undefined); + if (!data) return; + + editor?.chain().focus().insertContent(data).run(); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiptap/extensions/toolbar/embedded-media.tiptap-toolbar-api.ts b/src/Umbraco.Web.UI.Client/src/packages/tiptap/extensions/toolbar/embedded-media.tiptap-toolbar-api.ts index 0a91f0801bda..b691bb7c7a7c 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/tiptap/extensions/toolbar/embedded-media.tiptap-toolbar-api.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/tiptap/extensions/toolbar/embedded-media.tiptap-toolbar-api.ts @@ -5,8 +5,6 @@ import { UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal'; import type { Editor } from '@umbraco-cms/backoffice/external/tiptap'; export default class UmbTiptapToolbarEmbeddedMediaExtensionApi extends UmbTiptapToolbarElementApiBase { - override isActive = (editor: Editor) => editor.isActive(umbEmbeddedMedia.name) === true; - override async execute(editor?: Editor) { const data = { constrain: false, diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiptap/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/tiptap/manifests.ts index 9325da5e4fe2..18d116074096 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/tiptap/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/tiptap/manifests.ts @@ -1,5 +1,10 @@ +import { manifests as components } from './components/manifests.js'; import { manifests as extensions } from './extensions/manifests.js'; import { manifests as propertyEditors } from './property-editors/manifests.js'; import type { UmbExtensionManifestKind } from '@umbraco-cms/backoffice/extension-registry'; -export const manifests: Array<UmbExtensionManifest | UmbExtensionManifestKind> = [...extensions, ...propertyEditors]; +export const manifests: Array<UmbExtensionManifest | UmbExtensionManifestKind> = [ + ...components, + ...extensions, + ...propertyEditors, +]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiptap/property-editors/tiptap/components/property-editor-ui-tiptap-extensions-configuration.element.ts b/src/Umbraco.Web.UI.Client/src/packages/tiptap/property-editors/tiptap/components/property-editor-ui-tiptap-extensions-configuration.element.ts index 5f9756f9af1c..fae88717a98e 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/tiptap/property-editors/tiptap/components/property-editor-ui-tiptap-extensions-configuration.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/tiptap/property-editors/tiptap/components/property-editor-ui-tiptap-extensions-configuration.element.ts @@ -11,13 +11,13 @@ import { } from '@umbraco-cms/backoffice/external/lit'; import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; -import { UmbPropertyValueChangeEvent } from '@umbraco-cms/backoffice/property-editor'; import type { PropertyValueMap } from '@umbraco-cms/backoffice/external/lit'; import type { UmbPropertyEditorConfigCollection, UmbPropertyEditorUiElement, } from '@umbraco-cms/backoffice/property-editor'; import { UMB_PROPERTY_DATASET_CONTEXT } from '@umbraco-cms/backoffice/property'; +import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; type UmbTiptapExtension = { alias: string; @@ -87,6 +87,8 @@ export class UmbPropertyEditorUiTiptapExtensionsConfigurationElement this.#setValue(tmpValue); this.#syncViewModel(); } + + this.requestUpdate('_extensions'); }, '_observeBlocks', ); @@ -132,7 +134,7 @@ export class UmbPropertyEditorUiTiptapExtensionsConfigurationElement #setValue(value: Array<string>) { this.value = value; - this.dispatchEvent(new UmbPropertyValueChangeEvent()); + this.dispatchEvent(new UmbChangeEvent()); } #syncViewModel() { diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiptap/property-editors/tiptap/components/property-editor-ui-tiptap-toolbar-configuration.element.ts b/src/Umbraco.Web.UI.Client/src/packages/tiptap/property-editors/tiptap/components/property-editor-ui-tiptap-toolbar-configuration.element.ts index 1ce96bb9ff44..46525299dcea 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/tiptap/property-editors/tiptap/components/property-editor-ui-tiptap-toolbar-configuration.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/tiptap/property-editors/tiptap/components/property-editor-ui-tiptap-toolbar-configuration.element.ts @@ -141,16 +141,18 @@ export class UmbPropertyEditorUiTiptapToolbarConfigurationElement </div> </uui-input> </div> - <div class="available-items" dropzone="move" @drop=${this.#onDrop} @dragover=${this.#onDragOver}> - ${when( - this._availableExtensions.length === 0, - () => - html`<umb-localize key="tiptap_toobar_availableItemsEmpty" - >There are no toolbar extensions to show</umb-localize - >`, - () => repeat(this._availableExtensions, (item) => this.#renderAvailableItem(item)), - )} - </div> + <uui-scroll-container> + <div class="available-items" dropzone="move" @drop=${this.#onDrop} @dragover=${this.#onDragOver}> + ${when( + this._availableExtensions.length === 0, + () => + html`<umb-localize key="tiptap_toobar_availableItemsEmpty" + >There are no toolbar extensions to show</umb-localize + >`, + () => repeat(this._availableExtensions, (item) => this.#renderAvailableItem(item)), + )} + </div> + </uui-scroll-container> </uui-box> `; } @@ -158,24 +160,23 @@ export class UmbPropertyEditorUiTiptapToolbarConfigurationElement #renderAvailableItem(item: UmbTiptapToolbarExtension) { const forbidden = !this.#context.isExtensionEnabled(item.alias); const inUse = this.#context.isExtensionInUse(item.alias); - return inUse || forbidden - ? nothing - : html` - <uui-button - compact - class=${forbidden ? 'forbidden' : ''} - draggable="true" - look=${forbidden ? 'placeholder' : 'outline'} - ?disabled=${forbidden || inUse} - @click=${() => this.#onClick(item)} - @dragstart=${(e: DragEvent) => this.#onDragStart(e, item.alias)} - @dragend=${this.#onDragEnd}> - <div class="inner"> - ${when(item.icon, () => html`<umb-icon .name=${item.icon}></umb-icon>`)} - <span>${this.localize.string(item.label)}</span> - </div> - </uui-button> - `; + if (inUse || forbidden) return nothing; + return html` + <uui-button + compact + class=${forbidden ? 'forbidden' : ''} + draggable="true" + look=${forbidden ? 'placeholder' : 'outline'} + ?disabled=${forbidden || inUse} + @click=${() => this.#onClick(item)} + @dragstart=${(e: DragEvent) => this.#onDragStart(e, item.alias)} + @dragend=${this.#onDragEnd}> + <div class="inner"> + ${when(item.icon, () => html`<umb-icon .name=${item.icon}></umb-icon>`)} + <span>${this.localize.string(item.label)}</span> + </div> + </uui-button> + `; } #renderDesigner() { @@ -273,27 +274,52 @@ export class UmbPropertyEditorUiTiptapToolbarConfigurationElement #renderItem(alias: string, rowIndex = 0, groupIndex = 0, itemIndex = 0) { const item = this.#context?.getExtensionByAlias(alias); if (!item) return nothing; + const forbidden = !this.#context?.isExtensionEnabled(item.alias); - return html` - <uui-button - compact - class=${forbidden ? 'forbidden' : ''} - draggable="true" - look=${forbidden ? 'placeholder' : 'outline'} - title=${this.localize.string(item.label)} - ?disabled=${forbidden} - @click=${() => this.#context.removeToolbarItem([rowIndex, groupIndex, itemIndex])} - @dragend=${this.#onDragEnd} - @dragstart=${(e: DragEvent) => this.#onDragStart(e, alias, [rowIndex, groupIndex, itemIndex])}> - <div class="inner"> - ${when( - item.icon, - () => html`<umb-icon .name=${item.icon}></umb-icon>`, - () => html`<span>${this.localize.string(item.label)}</span>`, - )} - </div> - </uui-button> - `; + + switch (item.kind) { + case 'menu': + return html` + <uui-button + compact + class=${forbidden ? 'forbidden' : ''} + draggable="true" + look=${forbidden ? 'placeholder' : 'outline'} + title=${this.localize.string(item.label)} + ?disabled=${forbidden} + @click=${() => this.#context.removeToolbarItem([rowIndex, groupIndex, itemIndex])} + @dragend=${this.#onDragEnd} + @dragstart=${(e: DragEvent) => this.#onDragStart(e, alias, [rowIndex, groupIndex, itemIndex])}> + <div class="inner"> + <span>${this.localize.string(item.label)}</span> + </div> + <uui-symbol-expand slot="extra" open></uui-symbol-expand> + </uui-button> + `; + + case 'button': + default: + return html` + <uui-button + compact + class=${forbidden ? 'forbidden' : ''} + draggable="true" + look=${forbidden ? 'placeholder' : 'outline'} + title=${this.localize.string(item.label)} + ?disabled=${forbidden} + @click=${() => this.#context.removeToolbarItem([rowIndex, groupIndex, itemIndex])} + @dragend=${this.#onDragEnd} + @dragstart=${(e: DragEvent) => this.#onDragStart(e, alias, [rowIndex, groupIndex, itemIndex])}> + <div class="inner"> + ${when( + item.icon, + () => html`<umb-icon .name=${item.icon}></umb-icon>`, + () => html`<span>${this.localize.string(item.label)}</span>`, + )} + </div> + </uui-button> + `; + } } static override readonly styles = [ @@ -339,6 +365,10 @@ export class UmbPropertyEditorUiTiptapToolbarConfigurationElement } } + uui-scroll-container { + max-height: 350px; + } + .available-items { display: flex; flex-wrap: wrap; @@ -466,6 +496,10 @@ export class UmbPropertyEditorUiTiptapToolbarConfigurationElement display: flex; gap: var(--uui-size-1); } + + uui-symbol-expand { + margin-left: var(--uui-size-space-2); + } } } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiptap/property-editors/tiptap/contexts/tiptap-toolbar-configuration.context.ts b/src/Umbraco.Web.UI.Client/src/packages/tiptap/property-editors/tiptap/contexts/tiptap-toolbar-configuration.context.ts index 721450419859..2b8b19e0438e 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/tiptap/property-editors/tiptap/contexts/tiptap-toolbar-configuration.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/tiptap/property-editors/tiptap/contexts/tiptap-toolbar-configuration.context.ts @@ -39,8 +39,8 @@ export class UmbTiptapToolbarConfigurationContext extends UmbContextBase<UmbTipt fontname: 'Umb.Tiptap.Toolbar.FontFamily', fontfamily: 'Umb.Tiptap.Toolbar.FontFamily', fontsize: 'Umb.Tiptap.Toolbar.FontSize', - forecolor: null, - backcolor: null, + forecolor: 'Umb.Tiptap.Toolbar.TextColorForeground', + backcolor: 'Umb.Tiptap.Toolbar.TextColorBackground', blockquote: 'Umb.Tiptap.Toolbar.Blockquote', formatblock: null, removeformat: 'Umb.Tiptap.Toolbar.ClearFormatting', @@ -56,14 +56,14 @@ export class UmbTiptapToolbarConfigurationContext extends UmbContextBase<UmbTipt numlist: 'Umb.Tiptap.Toolbar.OrderedList', outdent: null, indent: null, - anchor: null, + anchor: 'Umb.Tiptap.Toolbar.Anchor', table: 'Umb.Tiptap.Toolbar.Table', hr: 'Umb.Tiptap.Toolbar.HorizontalRule', subscript: 'Umb.Tiptap.Toolbar.Subscript', superscript: 'Umb.Tiptap.Toolbar.Superscript', - charmap: null, - rtl: null, - ltr: null, + charmap: 'Umb.Tiptap.Toolbar.CharacterMap', + rtl: 'Umb.Tiptap.Toolbar.TextDirectionRtl', + ltr: 'Umb.Tiptap.Toolbar.TextDirectionLtr', link: 'Umb.Tiptap.Toolbar.Link', unlink: 'Umb.Tiptap.Toolbar.Unlink', sourcecode: 'Umb.Tiptap.Toolbar.SourceEditor', @@ -76,12 +76,15 @@ export class UmbTiptapToolbarConfigurationContext extends UmbContextBase<UmbTipt super(host, UMB_TIPTAP_TOOLBAR_CONFIGURATION_CONTEXT); this.observe(umbExtensionsRegistry.byType('tiptapToolbarExtension'), (extensions) => { - const _extensions = extensions.map((ext) => ({ - alias: ext.alias, - label: ext.meta.label, - icon: ext.meta.icon, - dependencies: ext.forExtensions, - })); + const _extensions = extensions + .sort((a, b) => a.alias.localeCompare(b.alias)) + .map((ext) => ({ + kind: (ext.kind as string) ?? 'button', + alias: ext.alias, + label: ext.meta.label, + icon: ext.meta.icon, + dependencies: ext.forExtensions, + })); this.#extensions.setValue(_extensions); diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiptap/property-editors/tiptap/types.ts b/src/Umbraco.Web.UI.Client/src/packages/tiptap/property-editors/tiptap/types.ts index 305cd36967ef..448accfa2cbf 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/tiptap/property-editors/tiptap/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/tiptap/property-editors/tiptap/types.ts @@ -1,4 +1,5 @@ export type UmbTiptapToolbarExtension = { + kind?: string; alias: string; label: string; icon: string; diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user-group/components/input-user-group/user-group-input.context.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user-group/components/input-user-group/user-group-input.context.ts index bc768f0f88d4..dd8989fa2f3e 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/user-group/components/input-user-group/user-group-input.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user-group/components/input-user-group/user-group-input.context.ts @@ -8,6 +8,3 @@ export class UmbUserGroupPickerInputContext extends UmbPickerInputContext<UmbUse super(host, UMB_USER_GROUP_ITEM_REPOSITORY_ALIAS, UMB_USER_GROUP_PICKER_MODAL); } } - -/** @deprecated Use `UmbUserGroupPickerInputContext` instead. This method will be removed in Umbraco 15. */ -export { UmbUserGroupPickerInputContext as UmbUserGroupPickerContext }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user/components/user-input/user-input.context.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user/components/user-input/user-input.context.ts index 393ac4214a2f..007d716eec17 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/user/components/user-input/user-input.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user/components/user-input/user-input.context.ts @@ -9,6 +9,3 @@ export class UmbUserPickerInputContext extends UmbPickerInputContext<UmbUserDeta super(host, UMB_USER_ITEM_REPOSITORY_ALIAS, UMB_USER_PICKER_MODAL); } } - -/** @deprecated Use `UmbUserPickerInputContext` instead. This method will be removed in Umbraco 15. */ -export { UmbUserPickerInputContext as UmbUserPickerContext }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user/property-editor/user-picker/property-editor-ui-user-picker.element.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user/property-editor/user-picker/property-editor-ui-user-picker.element.ts index e4c8e80ba145..9347d263fa88 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/user/property-editor/user-picker/property-editor-ui-user-picker.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user/property-editor/user-picker/property-editor-ui-user-picker.element.ts @@ -1,6 +1,6 @@ +import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; import { html, customElement, property } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; -import { UmbPropertyValueChangeEvent } from '@umbraco-cms/backoffice/property-editor'; import type { UmbPropertyEditorConfigCollection, UmbPropertyEditorUiElement, @@ -20,7 +20,7 @@ export class UmbPropertyEditorUIUserPickerElement extends UmbLitElement implemen #onChange(event: CustomEvent & { target: UmbUserInputElement }) { this.value = event.target.value; - this.dispatchEvent(new UmbPropertyValueChangeEvent()); + this.dispatchEvent(new UmbChangeEvent()); } override render() { diff --git a/src/Umbraco.Web.UI.Client/src/rollup.config.js b/src/Umbraco.Web.UI.Client/src/rollup.config.js index 5d64dd78a0c3..f4a313b69c0a 100644 --- a/src/Umbraco.Web.UI.Client/src/rollup.config.js +++ b/src/Umbraco.Web.UI.Client/src/rollup.config.js @@ -52,6 +52,10 @@ console.log('--- Copying TinyMCE i18n done ---'); // Copy monaco-editor console.log('--- Copying monaco-editor ---'); cpSync('./node_modules/monaco-editor/esm/vs/editor/editor.worker.js', `${DIST_DIRECTORY}/monaco-editor/vs/editor/editor.worker.js`); +cpSync('./node_modules/monaco-editor/esm/vs/base', `${DIST_DIRECTORY}/monaco-editor/vs/base`, { recursive: true }); +cpSync('./node_modules/monaco-editor/esm/vs/nls.js', `${DIST_DIRECTORY}/monaco-editor/vs/nls.js`, { recursive: true }); +cpSync('./node_modules/monaco-editor/esm/vs/nls.messages.js', `${DIST_DIRECTORY}/monaco-editor/vs/nls.messages.js`, { recursive: true }); +cpSync('./node_modules/monaco-editor/esm/vs/editor/common', `${DIST_DIRECTORY}/monaco-editor/vs/editor/common`, { recursive: true }); cpSync('./node_modules/monaco-editor/esm/vs/language', `${DIST_DIRECTORY}/monaco-editor/vs/language`, { recursive: true }); cpSync('./node_modules/monaco-editor/min/vs/base/browser/ui/codicons', `${DIST_DIRECTORY}/assets/fonts`, { recursive: true }); console.log('--- Copying monaco-editor done ---'); diff --git a/src/Umbraco.Web.UI.Client/tsconfig.json b/src/Umbraco.Web.UI.Client/tsconfig.json index 512ce70e9a7b..a649fe486262 100644 --- a/src/Umbraco.Web.UI.Client/tsconfig.json +++ b/src/Umbraco.Web.UI.Client/tsconfig.json @@ -40,7 +40,6 @@ DON'T EDIT THIS FILE DIRECTLY. It is generated by /devops/tsconfig/index.js "@umbraco-cms/backoffice/element-api": ["./src/libs/element-api/index.ts"], "@umbraco-cms/backoffice/embedded-media": ["./src/packages/embedded-media/index.ts"], "@umbraco-cms/backoffice/extension-api": ["./src/libs/extension-api/index.ts"], - "@umbraco-cms/backoffice/formatting-api": ["./src/libs/formatting-api/index.ts"], "@umbraco-cms/backoffice/localization-api": ["./src/libs/localization-api/index.ts"], "@umbraco-cms/backoffice/observable-api": ["./src/libs/observable-api/index.ts"], "@umbraco-cms/backoffice/action": ["./src/packages/core/action/index.ts"], diff --git a/src/Umbraco.Web.UI.Client/utils/all-umb-consts/imports.ts b/src/Umbraco.Web.UI.Client/utils/all-umb-consts/imports.ts index 0cd1f3878320..e1cf26147510 100644 --- a/src/Umbraco.Web.UI.Client/utils/all-umb-consts/imports.ts +++ b/src/Umbraco.Web.UI.Client/utils/all-umb-consts/imports.ts @@ -6,104 +6,103 @@ import * as import3 from '@umbraco-cms/backoffice/controller-api'; import * as import4 from '@umbraco-cms/backoffice/element-api'; import * as import5 from '@umbraco-cms/backoffice/embedded-media'; import * as import6 from '@umbraco-cms/backoffice/extension-api'; -import * as import7 from '@umbraco-cms/backoffice/formatting-api'; -import * as import8 from '@umbraco-cms/backoffice/localization-api'; -import * as import9 from '@umbraco-cms/backoffice/observable-api'; -import * as import10 from '@umbraco-cms/backoffice/action'; -import * as import11 from '@umbraco-cms/backoffice/audit-log'; -import * as import12 from '@umbraco-cms/backoffice/auth'; -import * as import13 from '@umbraco-cms/backoffice/block-custom-view'; -import * as import14 from '@umbraco-cms/backoffice/block-grid'; -import * as import15 from '@umbraco-cms/backoffice/block-list'; -import * as import16 from '@umbraco-cms/backoffice/block-rte'; -import * as import17 from '@umbraco-cms/backoffice/block-type'; -import * as import18 from '@umbraco-cms/backoffice/block'; -import * as import19 from '@umbraco-cms/backoffice/clipboard'; -import * as import20 from '@umbraco-cms/backoffice/code-editor'; -import * as import21 from '@umbraco-cms/backoffice/collection'; -import * as import22 from '@umbraco-cms/backoffice/components'; -import * as import23 from '@umbraco-cms/backoffice/const'; -import * as import24 from '@umbraco-cms/backoffice/content-type'; -import * as import25 from '@umbraco-cms/backoffice/content'; -import * as import26 from '@umbraco-cms/backoffice/culture'; -import * as import27 from '@umbraco-cms/backoffice/current-user'; -import * as import28 from '@umbraco-cms/backoffice/dashboard'; -import * as import29 from '@umbraco-cms/backoffice/data-type'; -import * as import30 from '@umbraco-cms/backoffice/debug'; -import * as import31 from '@umbraco-cms/backoffice/dictionary'; -import * as import32 from '@umbraco-cms/backoffice/document-blueprint'; -import * as import33 from '@umbraco-cms/backoffice/document-type'; -import * as import34 from '@umbraco-cms/backoffice/document'; -import * as import35 from '@umbraco-cms/backoffice/entity-action'; -import * as import36 from '@umbraco-cms/backoffice/entity-bulk-action'; -import * as import37 from '@umbraco-cms/backoffice/entity-create-option-action'; -import * as import38 from '@umbraco-cms/backoffice/entity'; -import * as import39 from '@umbraco-cms/backoffice/entity-item'; -import * as import40 from '@umbraco-cms/backoffice/event'; -import * as import41 from '@umbraco-cms/backoffice/extension-registry'; -import * as import42 from '@umbraco-cms/backoffice/health-check'; -import * as import43 from '@umbraco-cms/backoffice/help'; -import * as import44 from '@umbraco-cms/backoffice/icon'; -import * as import45 from '@umbraco-cms/backoffice/id'; -import * as import46 from '@umbraco-cms/backoffice/imaging'; -import * as import47 from '@umbraco-cms/backoffice/language'; -import * as import48 from '@umbraco-cms/backoffice/lit-element'; -import * as import49 from '@umbraco-cms/backoffice/localization'; -import * as import50 from '@umbraco-cms/backoffice/log-viewer'; -import * as import51 from '@umbraco-cms/backoffice/media-type'; -import * as import52 from '@umbraco-cms/backoffice/media'; -import * as import53 from '@umbraco-cms/backoffice/member-group'; -import * as import54 from '@umbraco-cms/backoffice/member-type'; -import * as import55 from '@umbraco-cms/backoffice/member'; -import * as import56 from '@umbraco-cms/backoffice/menu'; -import * as import57 from '@umbraco-cms/backoffice/modal'; -import * as import58 from '@umbraco-cms/backoffice/multi-url-picker'; -import * as import59 from '@umbraco-cms/backoffice/notification'; -import * as import60 from '@umbraco-cms/backoffice/object-type'; -import * as import61 from '@umbraco-cms/backoffice/package'; -import * as import62 from '@umbraco-cms/backoffice/partial-view'; -import * as import63 from '@umbraco-cms/backoffice/picker-input'; -import * as import64 from '@umbraco-cms/backoffice/picker'; -import * as import65 from '@umbraco-cms/backoffice/property-action'; -import * as import66 from '@umbraco-cms/backoffice/property-editor'; -import * as import67 from '@umbraco-cms/backoffice/property-type'; -import * as import68 from '@umbraco-cms/backoffice/property'; -import * as import69 from '@umbraco-cms/backoffice/recycle-bin'; -import * as import70 from '@umbraco-cms/backoffice/relation-type'; -import * as import71 from '@umbraco-cms/backoffice/relations'; -import * as import72 from '@umbraco-cms/backoffice/repository'; -import * as import73 from '@umbraco-cms/backoffice/resources'; -import * as import74 from '@umbraco-cms/backoffice/router'; -import * as import75 from '@umbraco-cms/backoffice/rte'; -import * as import76 from '@umbraco-cms/backoffice/script'; -import * as import77 from '@umbraco-cms/backoffice/search'; -import * as import78 from '@umbraco-cms/backoffice/section'; -import * as import79 from '@umbraco-cms/backoffice/server-file-system'; -import * as import80 from '@umbraco-cms/backoffice/settings'; -import * as import81 from '@umbraco-cms/backoffice/sorter'; -import * as import82 from '@umbraco-cms/backoffice/static-file'; -import * as import83 from '@umbraco-cms/backoffice/store'; -import * as import84 from '@umbraco-cms/backoffice/style'; -import * as import85 from '@umbraco-cms/backoffice/stylesheet'; -import * as import86 from '@umbraco-cms/backoffice/sysinfo'; -import * as import87 from '@umbraco-cms/backoffice/tags'; -import * as import88 from '@umbraco-cms/backoffice/template'; -import * as import89 from '@umbraco-cms/backoffice/temporary-file'; -import * as import90 from '@umbraco-cms/backoffice/themes'; -import * as import91 from '@umbraco-cms/backoffice/tiny-mce'; -import * as import92 from '@umbraco-cms/backoffice/tiptap'; -import * as import93 from '@umbraco-cms/backoffice/translation'; -import * as import94 from '@umbraco-cms/backoffice/tree'; -import * as import95 from '@umbraco-cms/backoffice/ufm'; -import * as import96 from '@umbraco-cms/backoffice/user-change-password'; -import * as import97 from '@umbraco-cms/backoffice/user-group'; -import * as import98 from '@umbraco-cms/backoffice/user-permission'; -import * as import99 from '@umbraco-cms/backoffice/user'; -import * as import100 from '@umbraco-cms/backoffice/utils'; -import * as import101 from '@umbraco-cms/backoffice/validation'; -import * as import102 from '@umbraco-cms/backoffice/variant'; -import * as import103 from '@umbraco-cms/backoffice/webhook'; -import * as import104 from '@umbraco-cms/backoffice/workspace'; +import * as import7 from '@umbraco-cms/backoffice/localization-api'; +import * as import8 from '@umbraco-cms/backoffice/observable-api'; +import * as import9 from '@umbraco-cms/backoffice/action'; +import * as import10 from '@umbraco-cms/backoffice/audit-log'; +import * as import11 from '@umbraco-cms/backoffice/auth'; +import * as import12 from '@umbraco-cms/backoffice/block-custom-view'; +import * as import13 from '@umbraco-cms/backoffice/block-grid'; +import * as import14 from '@umbraco-cms/backoffice/block-list'; +import * as import15 from '@umbraco-cms/backoffice/block-rte'; +import * as import16 from '@umbraco-cms/backoffice/block-type'; +import * as import17 from '@umbraco-cms/backoffice/block'; +import * as import18 from '@umbraco-cms/backoffice/clipboard'; +import * as import19 from '@umbraco-cms/backoffice/code-editor'; +import * as import20 from '@umbraco-cms/backoffice/collection'; +import * as import21 from '@umbraco-cms/backoffice/components'; +import * as import22 from '@umbraco-cms/backoffice/const'; +import * as import23 from '@umbraco-cms/backoffice/content-type'; +import * as import24 from '@umbraco-cms/backoffice/content'; +import * as import25 from '@umbraco-cms/backoffice/culture'; +import * as import26 from '@umbraco-cms/backoffice/current-user'; +import * as import27 from '@umbraco-cms/backoffice/dashboard'; +import * as import28 from '@umbraco-cms/backoffice/data-type'; +import * as import29 from '@umbraco-cms/backoffice/debug'; +import * as import30 from '@umbraco-cms/backoffice/dictionary'; +import * as import31 from '@umbraco-cms/backoffice/document-blueprint'; +import * as import32 from '@umbraco-cms/backoffice/document-type'; +import * as import33 from '@umbraco-cms/backoffice/document'; +import * as import34 from '@umbraco-cms/backoffice/entity-action'; +import * as import35 from '@umbraco-cms/backoffice/entity-bulk-action'; +import * as import36 from '@umbraco-cms/backoffice/entity-create-option-action'; +import * as import37 from '@umbraco-cms/backoffice/entity'; +import * as import38 from '@umbraco-cms/backoffice/entity-item'; +import * as import39 from '@umbraco-cms/backoffice/event'; +import * as import40 from '@umbraco-cms/backoffice/extension-registry'; +import * as import41 from '@umbraco-cms/backoffice/health-check'; +import * as import42 from '@umbraco-cms/backoffice/help'; +import * as import43 from '@umbraco-cms/backoffice/icon'; +import * as import44 from '@umbraco-cms/backoffice/id'; +import * as import45 from '@umbraco-cms/backoffice/imaging'; +import * as import46 from '@umbraco-cms/backoffice/language'; +import * as import47 from '@umbraco-cms/backoffice/lit-element'; +import * as import48 from '@umbraco-cms/backoffice/localization'; +import * as import49 from '@umbraco-cms/backoffice/log-viewer'; +import * as import50 from '@umbraco-cms/backoffice/media-type'; +import * as import51 from '@umbraco-cms/backoffice/media'; +import * as import52 from '@umbraco-cms/backoffice/member-group'; +import * as import53 from '@umbraco-cms/backoffice/member-type'; +import * as import54 from '@umbraco-cms/backoffice/member'; +import * as import55 from '@umbraco-cms/backoffice/menu'; +import * as import56 from '@umbraco-cms/backoffice/modal'; +import * as import57 from '@umbraco-cms/backoffice/multi-url-picker'; +import * as import58 from '@umbraco-cms/backoffice/notification'; +import * as import59 from '@umbraco-cms/backoffice/object-type'; +import * as import60 from '@umbraco-cms/backoffice/package'; +import * as import61 from '@umbraco-cms/backoffice/partial-view'; +import * as import62 from '@umbraco-cms/backoffice/picker-input'; +import * as import63 from '@umbraco-cms/backoffice/picker'; +import * as import64 from '@umbraco-cms/backoffice/property-action'; +import * as import65 from '@umbraco-cms/backoffice/property-editor'; +import * as import66 from '@umbraco-cms/backoffice/property-type'; +import * as import67 from '@umbraco-cms/backoffice/property'; +import * as import68 from '@umbraco-cms/backoffice/recycle-bin'; +import * as import69 from '@umbraco-cms/backoffice/relation-type'; +import * as import70 from '@umbraco-cms/backoffice/relations'; +import * as import71 from '@umbraco-cms/backoffice/repository'; +import * as import72 from '@umbraco-cms/backoffice/resources'; +import * as import73 from '@umbraco-cms/backoffice/router'; +import * as import74 from '@umbraco-cms/backoffice/rte'; +import * as import75 from '@umbraco-cms/backoffice/script'; +import * as import76 from '@umbraco-cms/backoffice/search'; +import * as import77 from '@umbraco-cms/backoffice/section'; +import * as import78 from '@umbraco-cms/backoffice/server-file-system'; +import * as import79 from '@umbraco-cms/backoffice/settings'; +import * as import80 from '@umbraco-cms/backoffice/sorter'; +import * as import81 from '@umbraco-cms/backoffice/static-file'; +import * as import82 from '@umbraco-cms/backoffice/store'; +import * as import83 from '@umbraco-cms/backoffice/style'; +import * as import84 from '@umbraco-cms/backoffice/stylesheet'; +import * as import85 from '@umbraco-cms/backoffice/sysinfo'; +import * as import86 from '@umbraco-cms/backoffice/tags'; +import * as import87 from '@umbraco-cms/backoffice/template'; +import * as import88 from '@umbraco-cms/backoffice/temporary-file'; +import * as import89 from '@umbraco-cms/backoffice/themes'; +import * as import90 from '@umbraco-cms/backoffice/tiny-mce'; +import * as import91 from '@umbraco-cms/backoffice/tiptap'; +import * as import92 from '@umbraco-cms/backoffice/translation'; +import * as import93 from '@umbraco-cms/backoffice/tree'; +import * as import94 from '@umbraco-cms/backoffice/ufm'; +import * as import95 from '@umbraco-cms/backoffice/user-change-password'; +import * as import96 from '@umbraco-cms/backoffice/user-group'; +import * as import97 from '@umbraco-cms/backoffice/user-permission'; +import * as import98 from '@umbraco-cms/backoffice/user'; +import * as import99 from '@umbraco-cms/backoffice/utils'; +import * as import100 from '@umbraco-cms/backoffice/validation'; +import * as import101 from '@umbraco-cms/backoffice/variant'; +import * as import102 from '@umbraco-cms/backoffice/webhook'; +import * as import103 from '@umbraco-cms/backoffice/workspace'; export const imports = [ { @@ -134,397 +133,393 @@ import * as import104 from '@umbraco-cms/backoffice/workspace'; path: '@umbraco-cms/backoffice/extension-api', package: import6 }, -{ - path: '@umbraco-cms/backoffice/formatting-api', - package: import7 - }, { path: '@umbraco-cms/backoffice/localization-api', - package: import8 + package: import7 }, { path: '@umbraco-cms/backoffice/observable-api', - package: import9 + package: import8 }, { path: '@umbraco-cms/backoffice/action', - package: import10 + package: import9 }, { path: '@umbraco-cms/backoffice/audit-log', - package: import11 + package: import10 }, { path: '@umbraco-cms/backoffice/auth', - package: import12 + package: import11 }, { path: '@umbraco-cms/backoffice/block-custom-view', - package: import13 + package: import12 }, { path: '@umbraco-cms/backoffice/block-grid', - package: import14 + package: import13 }, { path: '@umbraco-cms/backoffice/block-list', - package: import15 + package: import14 }, { path: '@umbraco-cms/backoffice/block-rte', - package: import16 + package: import15 }, { path: '@umbraco-cms/backoffice/block-type', - package: import17 + package: import16 }, { path: '@umbraco-cms/backoffice/block', - package: import18 + package: import17 }, { path: '@umbraco-cms/backoffice/clipboard', - package: import19 + package: import18 }, { path: '@umbraco-cms/backoffice/code-editor', - package: import20 + package: import19 }, { path: '@umbraco-cms/backoffice/collection', - package: import21 + package: import20 }, { path: '@umbraco-cms/backoffice/components', - package: import22 + package: import21 }, { path: '@umbraco-cms/backoffice/const', - package: import23 + package: import22 }, { path: '@umbraco-cms/backoffice/content-type', - package: import24 + package: import23 }, { path: '@umbraco-cms/backoffice/content', - package: import25 + package: import24 }, { path: '@umbraco-cms/backoffice/culture', - package: import26 + package: import25 }, { path: '@umbraco-cms/backoffice/current-user', - package: import27 + package: import26 }, { path: '@umbraco-cms/backoffice/dashboard', - package: import28 + package: import27 }, { path: '@umbraco-cms/backoffice/data-type', - package: import29 + package: import28 }, { path: '@umbraco-cms/backoffice/debug', - package: import30 + package: import29 }, { path: '@umbraco-cms/backoffice/dictionary', - package: import31 + package: import30 }, { path: '@umbraco-cms/backoffice/document-blueprint', - package: import32 + package: import31 }, { path: '@umbraco-cms/backoffice/document-type', - package: import33 + package: import32 }, { path: '@umbraco-cms/backoffice/document', - package: import34 + package: import33 }, { path: '@umbraco-cms/backoffice/entity-action', - package: import35 + package: import34 }, { path: '@umbraco-cms/backoffice/entity-bulk-action', - package: import36 + package: import35 }, { path: '@umbraco-cms/backoffice/entity-create-option-action', - package: import37 + package: import36 }, { path: '@umbraco-cms/backoffice/entity', - package: import38 + package: import37 }, { path: '@umbraco-cms/backoffice/entity-item', - package: import39 + package: import38 }, { path: '@umbraco-cms/backoffice/event', - package: import40 + package: import39 }, { path: '@umbraco-cms/backoffice/extension-registry', - package: import41 + package: import40 }, { path: '@umbraco-cms/backoffice/health-check', - package: import42 + package: import41 }, { path: '@umbraco-cms/backoffice/help', - package: import43 + package: import42 }, { path: '@umbraco-cms/backoffice/icon', - package: import44 + package: import43 }, { path: '@umbraco-cms/backoffice/id', - package: import45 + package: import44 }, { path: '@umbraco-cms/backoffice/imaging', - package: import46 + package: import45 }, { path: '@umbraco-cms/backoffice/language', - package: import47 + package: import46 }, { path: '@umbraco-cms/backoffice/lit-element', - package: import48 + package: import47 }, { path: '@umbraco-cms/backoffice/localization', - package: import49 + package: import48 }, { path: '@umbraco-cms/backoffice/log-viewer', - package: import50 + package: import49 }, { path: '@umbraco-cms/backoffice/media-type', - package: import51 + package: import50 }, { path: '@umbraco-cms/backoffice/media', - package: import52 + package: import51 }, { path: '@umbraco-cms/backoffice/member-group', - package: import53 + package: import52 }, { path: '@umbraco-cms/backoffice/member-type', - package: import54 + package: import53 }, { path: '@umbraco-cms/backoffice/member', - package: import55 + package: import54 }, { path: '@umbraco-cms/backoffice/menu', - package: import56 + package: import55 }, { path: '@umbraco-cms/backoffice/modal', - package: import57 + package: import56 }, { path: '@umbraco-cms/backoffice/multi-url-picker', - package: import58 + package: import57 }, { path: '@umbraco-cms/backoffice/notification', - package: import59 + package: import58 }, { path: '@umbraco-cms/backoffice/object-type', - package: import60 + package: import59 }, { path: '@umbraco-cms/backoffice/package', - package: import61 + package: import60 }, { path: '@umbraco-cms/backoffice/partial-view', - package: import62 + package: import61 }, { path: '@umbraco-cms/backoffice/picker-input', - package: import63 + package: import62 }, { path: '@umbraco-cms/backoffice/picker', - package: import64 + package: import63 }, { path: '@umbraco-cms/backoffice/property-action', - package: import65 + package: import64 }, { path: '@umbraco-cms/backoffice/property-editor', - package: import66 + package: import65 }, { path: '@umbraco-cms/backoffice/property-type', - package: import67 + package: import66 }, { path: '@umbraco-cms/backoffice/property', - package: import68 + package: import67 }, { path: '@umbraco-cms/backoffice/recycle-bin', - package: import69 + package: import68 }, { path: '@umbraco-cms/backoffice/relation-type', - package: import70 + package: import69 }, { path: '@umbraco-cms/backoffice/relations', - package: import71 + package: import70 }, { path: '@umbraco-cms/backoffice/repository', - package: import72 + package: import71 }, { path: '@umbraco-cms/backoffice/resources', - package: import73 + package: import72 }, { path: '@umbraco-cms/backoffice/router', - package: import74 + package: import73 }, { path: '@umbraco-cms/backoffice/rte', - package: import75 + package: import74 }, { path: '@umbraco-cms/backoffice/script', - package: import76 + package: import75 }, { path: '@umbraco-cms/backoffice/search', - package: import77 + package: import76 }, { path: '@umbraco-cms/backoffice/section', - package: import78 + package: import77 }, { path: '@umbraco-cms/backoffice/server-file-system', - package: import79 + package: import78 }, { path: '@umbraco-cms/backoffice/settings', - package: import80 + package: import79 }, { path: '@umbraco-cms/backoffice/sorter', - package: import81 + package: import80 }, { path: '@umbraco-cms/backoffice/static-file', - package: import82 + package: import81 }, { path: '@umbraco-cms/backoffice/store', - package: import83 + package: import82 }, { path: '@umbraco-cms/backoffice/style', - package: import84 + package: import83 }, { path: '@umbraco-cms/backoffice/stylesheet', - package: import85 + package: import84 }, { path: '@umbraco-cms/backoffice/sysinfo', - package: import86 + package: import85 }, { path: '@umbraco-cms/backoffice/tags', - package: import87 + package: import86 }, { path: '@umbraco-cms/backoffice/template', - package: import88 + package: import87 }, { path: '@umbraco-cms/backoffice/temporary-file', - package: import89 + package: import88 }, { path: '@umbraco-cms/backoffice/themes', - package: import90 + package: import89 }, { path: '@umbraco-cms/backoffice/tiny-mce', - package: import91 + package: import90 }, { path: '@umbraco-cms/backoffice/tiptap', - package: import92 + package: import91 }, { path: '@umbraco-cms/backoffice/translation', - package: import93 + package: import92 }, { path: '@umbraco-cms/backoffice/tree', - package: import94 + package: import93 }, { path: '@umbraco-cms/backoffice/ufm', - package: import95 + package: import94 }, { path: '@umbraco-cms/backoffice/user-change-password', - package: import96 + package: import95 }, { path: '@umbraco-cms/backoffice/user-group', - package: import97 + package: import96 }, { path: '@umbraco-cms/backoffice/user-permission', - package: import98 + package: import97 }, { path: '@umbraco-cms/backoffice/user', - package: import99 + package: import98 }, { path: '@umbraco-cms/backoffice/utils', - package: import100 + package: import99 }, { path: '@umbraco-cms/backoffice/validation', - package: import101 + package: import100 }, { path: '@umbraco-cms/backoffice/variant', - package: import102 + package: import101 }, { path: '@umbraco-cms/backoffice/webhook', - package: import103 + package: import102 }, { path: '@umbraco-cms/backoffice/workspace', - package: import104 + package: import103 } ]; \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/vite.config.ts b/src/Umbraco.Web.UI.Client/vite.config.ts index 53747768db1d..c57b7aa65d9e 100644 --- a/src/Umbraco.Web.UI.Client/vite.config.ts +++ b/src/Umbraco.Web.UI.Client/vite.config.ts @@ -35,7 +35,7 @@ export const plugins: PluginOption[] = [ }, { src: 'node_modules/monaco-editor/esm/**/*', - dest: 'umbraco/backoffice/monaco-editor/esm', + dest: 'umbraco/backoffice/monaco-editor/vs', }, ], }), diff --git a/src/Umbraco.Web.UI/Composers/ControllersAsServicesComposer.cs b/src/Umbraco.Web.UI/Composers/ControllersAsServicesComposer.cs index d70a2183f46b..32de743d1a48 100644 --- a/src/Umbraco.Web.UI/Composers/ControllersAsServicesComposer.cs +++ b/src/Umbraco.Web.UI/Composers/ControllersAsServicesComposer.cs @@ -58,7 +58,7 @@ public static IMvcBuilder AddControllersAsServicesWithoutChangingActivator(this builder.Services.TryAddTransient(controller, controller); } - builder.Services.AddUnique<RenderNoContentController>(x => new RenderNoContentController(x.GetService<IUmbracoContextAccessor>()!, x.GetService<IOptionsSnapshot<GlobalSettings>>()!, x.GetService<IHostingEnvironment>()!)); + builder.Services.AddUnique<RenderNoContentController>(x => new RenderNoContentController(x.GetRequiredService<IUmbracoContextAccessor>(), x.GetRequiredService<IHostingEnvironment>(), x.GetRequiredService<IOptionsSnapshot<GlobalSettings>>())); return builder; } } diff --git a/src/Umbraco.Web.Website/Controllers/RenderNoContentController.cs b/src/Umbraco.Web.Website/Controllers/RenderNoContentController.cs index 92d1c67ba2a2..4f90fe79f0e4 100644 --- a/src/Umbraco.Web.Website/Controllers/RenderNoContentController.cs +++ b/src/Umbraco.Web.Website/Controllers/RenderNoContentController.cs @@ -1,11 +1,8 @@ using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Configuration.Models; -using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Hosting; -using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.PublishedCache; using Umbraco.Cms.Core.Web; using Umbraco.Cms.Web.Website.Models; @@ -15,28 +12,17 @@ namespace Umbraco.Cms.Web.Website.Controllers; public class RenderNoContentController : Controller { - private readonly GlobalSettings _globalSettings; - private readonly IHostingEnvironment _hostingEnvironment; private readonly IUmbracoContextAccessor _umbracoContextAccessor; + private readonly IHostingEnvironment _hostingEnvironment; + private readonly GlobalSettings _globalSettings; - [Obsolete("Please use constructor that takes an IHostingEnvironment instead")] public RenderNoContentController( IUmbracoContextAccessor umbracoContextAccessor, - IIOHelper ioHelper, + IHostingEnvironment hostingEnvironment, IOptionsSnapshot<GlobalSettings> globalSettings) - : this(umbracoContextAccessor, globalSettings, StaticServiceProvider.Instance.GetRequiredService<IHostingEnvironment>()) - { - } - - [ActivatorUtilitiesConstructor] - public RenderNoContentController( - IUmbracoContextAccessor umbracoContextAccessor, - IOptionsSnapshot<GlobalSettings> globalSettings, - IHostingEnvironment hostingEnvironment) { - _umbracoContextAccessor = - umbracoContextAccessor ?? throw new ArgumentNullException(nameof(umbracoContextAccessor)); - _hostingEnvironment = hostingEnvironment; + _umbracoContextAccessor = umbracoContextAccessor ?? throw new ArgumentNullException(nameof(umbracoContextAccessor)); + _hostingEnvironment = hostingEnvironment ?? throw new ArgumentNullException(nameof(hostingEnvironment)); _globalSettings = globalSettings.Value ?? throw new ArgumentNullException(nameof(globalSettings)); } @@ -50,7 +36,7 @@ public ActionResult Index() return Redirect("~/"); } - var model = new NoNodesViewModel { UmbracoPath = _hostingEnvironment.ToAbsolute(Constants.System.DefaultUmbracoPath) }; + var model = new NoNodesViewModel { UmbracoPath = _hostingEnvironment.GetBackOfficePath() }; return View(_globalSettings.NoNodesViewPath, model); } diff --git a/src/Umbraco.Web.Website/Extensions/HtmlHelperRenderExtensions.cs b/src/Umbraco.Web.Website/Extensions/HtmlHelperRenderExtensions.cs index 9cdff29795de..6892ffd1452f 100644 --- a/src/Umbraco.Web.Website/Extensions/HtmlHelperRenderExtensions.cs +++ b/src/Umbraco.Web.Website/Extensions/HtmlHelperRenderExtensions.cs @@ -86,7 +86,7 @@ public static IHtmlContent PreviewBadge( var htmlBadge = string.Format( contentSettings.PreviewBadge, - hostingEnvironment.ToAbsolute(Constants.System.DefaultUmbracoPath), + hostingEnvironment.GetBackOfficePath(), WebUtility.UrlEncode(httpContextAccessor.GetRequiredHttpContext().Request.Path), umbracoContext.PublishedRequest?.PublishedContent?.Key); return new HtmlString(htmlBadge); diff --git a/src/Umbraco.Web.Website/Middleware/BasicAuthenticationMiddleware.cs b/src/Umbraco.Web.Website/Middleware/BasicAuthenticationMiddleware.cs index 463b50695aa0..f473cf367706 100644 --- a/src/Umbraco.Web.Website/Middleware/BasicAuthenticationMiddleware.cs +++ b/src/Umbraco.Web.Website/Middleware/BasicAuthenticationMiddleware.cs @@ -27,15 +27,22 @@ public class BasicAuthenticationMiddleware : IMiddleware public BasicAuthenticationMiddleware( IRuntimeState runtimeState, IBasicAuthService basicAuthService, - IOptionsMonitor<GlobalSettings> globalSettings, IHostingEnvironment hostingEnvironment) { _runtimeState = runtimeState; _basicAuthService = basicAuthService; - - _backOfficePath = globalSettings.CurrentValue.GetBackOfficePath(hostingEnvironment); + _backOfficePath = hostingEnvironment.GetBackOfficePath(); } + [Obsolete("The globalSettings parameter is not required anymore, use the other constructor instead. Scheduled for removal in Umbraco 17.")] + public BasicAuthenticationMiddleware( + IRuntimeState runtimeState, + IBasicAuthService basicAuthService, + IOptionsMonitor<GlobalSettings> globalSettings, + IHostingEnvironment hostingEnvironment) + : this(runtimeState, basicAuthService, hostingEnvironment) + { } + /// <inheritdoc /> public async Task InvokeAsync(HttpContext context, RequestDelegate next) { diff --git a/src/Umbraco.Web.Website/Routing/FrontEndRoutes.cs b/src/Umbraco.Web.Website/Routing/FrontEndRoutes.cs index c67a5acfa4c0..e6ecb1e0cc01 100644 --- a/src/Umbraco.Web.Website/Routing/FrontEndRoutes.cs +++ b/src/Umbraco.Web.Website/Routing/FrontEndRoutes.cs @@ -1,10 +1,5 @@ using Microsoft.AspNetCore.Routing; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Options; using Umbraco.Cms.Core; -using Umbraco.Cms.Core.Configuration.Models; -using Umbraco.Cms.Core.DependencyInjection; -using Umbraco.Cms.Core.Hosting; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Web.Mvc; using Umbraco.Cms.Web.Common.Controllers; @@ -19,61 +14,28 @@ namespace Umbraco.Cms.Web.Website.Routing; /// </summary> public sealed class FrontEndRoutes : IAreaRoutes { - private readonly UmbracoApiControllerTypeCollection _apiControllers; private readonly IRuntimeState _runtimeState; private readonly SurfaceControllerTypeCollection _surfaceControllerTypeCollection; - private readonly string _umbracoPathSegment; - - [Obsolete("Use non-obsolete constructor. This will be removed in Umbraco 15.")] - public FrontEndRoutes( - IOptions<GlobalSettings> globalSettings, - IHostingEnvironment hostingEnvironment, - IRuntimeState runtimeState, - SurfaceControllerTypeCollection surfaceControllerTypeCollection, - UmbracoApiControllerTypeCollection apiControllers) - { - _runtimeState = runtimeState; - _surfaceControllerTypeCollection = surfaceControllerTypeCollection; - _apiControllers = apiControllers; - _umbracoPathSegment = globalSettings.Value.GetUmbracoMvcArea(hostingEnvironment); - } + private readonly UmbracoApiControllerTypeCollection _umbracoApiControllerTypeCollection; /// <summary> - /// Initializes a new instance of the <see cref="FrontEndRoutes" /> class. + /// Initializes a new instance of the <see cref="FrontEndRoutes" /> class. /// </summary> - public FrontEndRoutes( - IOptions<GlobalSettings> globalSettings, - IHostingEnvironment hostingEnvironment, - IRuntimeState runtimeState, - SurfaceControllerTypeCollection surfaceControllerTypeCollection) - : this( - globalSettings, - hostingEnvironment, - runtimeState, - surfaceControllerTypeCollection, - StaticServiceProvider.Instance.GetRequiredService<UmbracoApiControllerTypeCollection>()) + public FrontEndRoutes(IRuntimeState runtimeState, SurfaceControllerTypeCollection surfaceControllerTypeCollection, UmbracoApiControllerTypeCollection umbracoApiControllerTypeCollection) { + _runtimeState = runtimeState; + _surfaceControllerTypeCollection = surfaceControllerTypeCollection; + _umbracoApiControllerTypeCollection = umbracoApiControllerTypeCollection; } /// <inheritdoc /> public void CreateRoutes(IEndpointRouteBuilder endpoints) { - switch (_runtimeState.Level) + if (_runtimeState.Level is RuntimeLevel.Install or RuntimeLevel.Upgrade or RuntimeLevel.Run) { - case RuntimeLevel.Install: - case RuntimeLevel.Upgrade: - case RuntimeLevel.Run: - - AutoRouteSurfaceControllers(endpoints); - AutoRouteFrontEndApiControllers(endpoints); - break; - case RuntimeLevel.BootFailed: - case RuntimeLevel.Unknown: - case RuntimeLevel.Boot: - break; + AutoRouteSurfaceControllers(endpoints); + AutoRouteFrontEndApiControllers(endpoints); } - - } /// <summary> @@ -88,7 +50,7 @@ private void AutoRouteSurfaceControllers(IEndpointRouteBuilder endpoints) endpoints.MapUmbracoSurfaceRoute( meta.ControllerType, - _umbracoPathSegment, + Constants.System.UmbracoPathSegment, meta.AreaName); } } @@ -98,7 +60,7 @@ private void AutoRouteSurfaceControllers(IEndpointRouteBuilder endpoints) /// </summary> private void AutoRouteFrontEndApiControllers(IEndpointRouteBuilder endpoints) { - foreach (Type controller in _apiControllers) + foreach (Type controller in _umbracoApiControllerTypeCollection) { PluginControllerMetadata meta = PluginController.GetMetadata(controller); @@ -110,7 +72,7 @@ private void AutoRouteFrontEndApiControllers(IEndpointRouteBuilder endpoints) endpoints.MapUmbracoApiRoute( meta.ControllerType, - _umbracoPathSegment, + Constants.System.UmbracoPathSegment, meta.AreaName, meta.IsBackOffice, string.Empty); // no default action (this is what we had before) diff --git a/templates/UmbracoExtension/.template.config/template.json b/templates/UmbracoExtension/.template.config/template.json index 5ada4ae33f05..b915adc90f19 100644 --- a/templates/UmbracoExtension/.template.config/template.json +++ b/templates/UmbracoExtension/.template.config/template.json @@ -136,22 +136,6 @@ ], "actionId": "210D431B-A78B-4D2F-B762-4ED3E3EA9025", "continueOnError": true - }, - { - "actionId": "3A7C4B45-1F5D-4A30-959A-51B88E82B5D2", - "args": { - "executable": "powershell", - "args": "cd Client;npm install;npm run build;", - "redirectStandardError": false, - "redirectStandardOutput": false - }, - "manualInstructions": [ - { - "text": "From the 'Client' folder run 'npm install' and then 'npm run build'" - } - ], - "continueOnError": true, - "description ": "Installs node modules" } ], "sources": [ @@ -160,11 +144,10 @@ { "condition": "(!IncludeExample)", "exclude": [ - "[Cc]lient/src/dashboards/**", - "[Cc]lient/src/api/schemas.gen.ts" + "[Cc]lient/src/dashboards/**" ] } ] } ] -} +} \ No newline at end of file diff --git a/templates/UmbracoExtension/Client/package.json b/templates/UmbracoExtension/Client/package.json index c4b1ae9b6b84..f62550adfe1e 100644 --- a/templates/UmbracoExtension/Client/package.json +++ b/templates/UmbracoExtension/Client/package.json @@ -9,13 +9,13 @@ "generate-client": "node scripts/generate-openapi.js https://localhost:44339/umbraco/swagger/umbracoextension/swagger.json" }, "devDependencies": { - "@hey-api/client-fetch": "^0.4.2", - "@hey-api/openapi-ts": "^0.53.11", + "@hey-api/client-fetch": "^0.8.3", + "@hey-api/openapi-ts": "^0.64.10", "@umbraco-cms/backoffice": "^UMBRACO_VERSION_FROM_TEMPLATE", - "chalk": "^5.3.0", + "chalk": "^5.4.1", "cross-env": "^7.0.3", "node-fetch": "^3.3.2", - "typescript": "^5.6.3", - "vite": "^5.4.9" + "typescript": "^5.8.2", + "vite": "^6.2.0" } -} +} \ No newline at end of file diff --git a/templates/UmbracoExtension/Client/public/umbraco-package.json b/templates/UmbracoExtension/Client/public/umbraco-package.json index 2ca2f833177d..ca3d52d518eb 100644 --- a/templates/UmbracoExtension/Client/public/umbraco-package.json +++ b/templates/UmbracoExtension/Client/public/umbraco-package.json @@ -2,7 +2,7 @@ "id": "Umbraco.Extension", "name": "Umbraco.Extension", "version": "0.0.0", - "allowPackageTelemetry": true, + "allowTelemetry": true, "extensions": [ { "name": "Umbraco ExtensionBundle", @@ -11,4 +11,4 @@ "js": "/App_Plugins/UmbracoExtension/umbraco-extension.js" } ] -} +} \ No newline at end of file diff --git a/templates/UmbracoExtension/Client/scripts/generate-openapi.js b/templates/UmbracoExtension/Client/scripts/generate-openapi.js index 93bb4812897a..b320781bc74b 100644 --- a/templates/UmbracoExtension/Client/scripts/generate-openapi.js +++ b/templates/UmbracoExtension/Client/scripts/generate-openapi.js @@ -1,6 +1,6 @@ import fetch from 'node-fetch'; import chalk from 'chalk'; -import { createClient } from '@hey-api/openapi-ts'; +import { createClient, defaultPlugins } from '@hey-api/openapi-ts'; // Start notifying user we are generating the TypeScript client console.log(chalk.green("Generating OpenAPI client...")); @@ -20,7 +20,7 @@ process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; console.log("Ensure your Umbraco instance is running"); console.log(`Fetching OpenAPI definition from ${chalk.yellow(swaggerUrl)}`); -fetch(swaggerUrl).then(response => { +fetch(swaggerUrl).then(async (response) => { if (!response.ok) { console.error(chalk.red(`ERROR: OpenAPI spec returned with a non OK (200) response: ${response.status} ${response.statusText}`)); console.error(`The URL to your Umbraco instance may be wrong or the instance is not running`); @@ -31,13 +31,21 @@ fetch(swaggerUrl).then(response => { console.log(`OpenAPI spec fetched successfully`); console.log(`Calling ${chalk.yellow('hey-api')} to generate TypeScript client`); - createClient({ - client: '@hey-api/client-fetch', + await createClient({ input: swaggerUrl, output: 'src/api', - services: { - asClass: true, - } + plugins: [ + ...defaultPlugins, + '@hey-api/client-fetch', + { + name: '@hey-api/typescript', + enums: 'typescript' + }, + { + name: '@hey-api/sdk', + asClass: true + } + ], }); }) diff --git a/templates/UmbracoExtension/Client/src/api/client.gen.ts b/templates/UmbracoExtension/Client/src/api/client.gen.ts new file mode 100644 index 000000000000..d7bb5e827275 --- /dev/null +++ b/templates/UmbracoExtension/Client/src/api/client.gen.ts @@ -0,0 +1,18 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { ClientOptions } from './types.gen'; +import { type Config, type ClientOptions as DefaultClientOptions, createClient, createConfig } from '@hey-api/client-fetch'; + +/** + * The `createClientConfig()` function will be called on client initialization + * and the returned object will become the client's initial configuration. + * + * You may want to initialize your client this way instead of calling + * `setConfig()`. This is useful for example if you're using Next.js + * to ensure your client always has the correct values. + */ +export type CreateClientConfig<T extends DefaultClientOptions = ClientOptions> = (override?: Config<DefaultClientOptions & T>) => Config<Required<DefaultClientOptions> & T>; + +export const client = createClient(createConfig<ClientOptions>({ + baseUrl: 'https://localhost:44389' +})); \ No newline at end of file diff --git a/templates/UmbracoExtension/Client/src/api/index.ts b/templates/UmbracoExtension/Client/src/api/index.ts index 7661d6537130..e64537d21293 100644 --- a/templates/UmbracoExtension/Client/src/api/index.ts +++ b/templates/UmbracoExtension/Client/src/api/index.ts @@ -1,6 +1,3 @@ // This file is auto-generated by @hey-api/openapi-ts -//#if(IncludeExample) -export * from './schemas.gen'; -//#endif -export * from './services.gen'; export * from './types.gen'; +export * from './sdk.gen'; \ No newline at end of file diff --git a/templates/UmbracoExtension/Client/src/api/schemas.gen.ts b/templates/UmbracoExtension/Client/src/api/schemas.gen.ts deleted file mode 100644 index 1a1885f5d5de..000000000000 --- a/templates/UmbracoExtension/Client/src/api/schemas.gen.ts +++ /dev/null @@ -1,391 +0,0 @@ -// This file is auto-generated by @hey-api/openapi-ts - -export const DocumentGranularPermissionModelSchema = { - required: ['context', 'key', 'permission'], - type: 'object', - properties: { - key: { - type: 'string', - format: 'uuid' - }, - context: { - type: 'string', - readOnly: true - }, - permission: { - type: 'string' - } - }, - additionalProperties: false -} as const; - -export const ReadOnlyUserGroupModelSchema = { - required: ['alias', 'allowedLanguages', 'allowedSections', 'granularPermissions', 'hasAccessToAllLanguages', 'id', 'key', 'name', 'permissions'], - type: 'object', - properties: { - id: { - type: 'integer', - format: 'int32' - }, - key: { - type: 'string', - format: 'uuid' - }, - name: { - type: 'string' - }, - icon: { - type: 'string', - nullable: true - }, - startContentId: { - type: 'integer', - format: 'int32', - nullable: true - }, - startMediaId: { - type: 'integer', - format: 'int32', - nullable: true - }, - alias: { - type: 'string' - }, - hasAccessToAllLanguages: { - type: 'boolean' - }, - allowedLanguages: { - type: 'array', - items: { - type: 'integer', - format: 'int32' - } - }, - permissions: { - uniqueItems: true, - type: 'array', - items: { - type: 'string' - } - }, - granularPermissions: { - uniqueItems: true, - type: 'array', - items: { - oneOf: [ - { - '$ref': '#/components/schemas/DocumentGranularPermissionModel' - }, - { - '$ref': '#/components/schemas/UnknownTypeGranularPermissionModel' - } - ] - } - }, - allowedSections: { - type: 'array', - items: { - type: 'string' - } - } - }, - additionalProperties: false -} as const; - -export const UnknownTypeGranularPermissionModelSchema = { - required: ['context', 'permission'], - type: 'object', - properties: { - context: { - type: 'string' - }, - permission: { - type: 'string' - } - }, - additionalProperties: false -} as const; - -export const UserGroupModelSchema = { - required: ['alias', 'allowedLanguages', 'allowedSections', 'createDate', 'granularPermissions', 'hasAccessToAllLanguages', 'hasIdentity', 'id', 'key', 'permissions', 'updateDate', 'userCount'], - type: 'object', - properties: { - id: { - type: 'integer', - format: 'int32' - }, - key: { - type: 'string', - format: 'uuid' - }, - createDate: { - type: 'string', - format: 'date-time' - }, - updateDate: { - type: 'string', - format: 'date-time' - }, - deleteDate: { - type: 'string', - format: 'date-time', - nullable: true - }, - hasIdentity: { - type: 'boolean', - readOnly: true - }, - startMediaId: { - type: 'integer', - format: 'int32', - nullable: true - }, - startContentId: { - type: 'integer', - format: 'int32', - nullable: true - }, - icon: { - type: 'string', - nullable: true - }, - alias: { - type: 'string' - }, - name: { - type: 'string', - nullable: true - }, - hasAccessToAllLanguages: { - type: 'boolean' - }, - permissions: { - uniqueItems: true, - type: 'array', - items: { - type: 'string' - } - }, - granularPermissions: { - uniqueItems: true, - type: 'array', - items: { - oneOf: [ - { - '$ref': '#/components/schemas/DocumentGranularPermissionModel' - }, - { - '$ref': '#/components/schemas/UnknownTypeGranularPermissionModel' - } - ] - } - }, - allowedSections: { - type: 'array', - items: { - type: 'string' - }, - readOnly: true - }, - userCount: { - type: 'integer', - format: 'int32', - readOnly: true - }, - allowedLanguages: { - type: 'array', - items: { - type: 'integer', - format: 'int32' - }, - readOnly: true - } - }, - additionalProperties: false -} as const; - -export const UserKindModelSchema = { - enum: ['Default', 'Api'], - type: 'string' -} as const; - -export const UserModelSchema = { - required: ['allowedSections', 'createDate', 'email', 'failedPasswordAttempts', 'groups', 'hasIdentity', 'id', 'isApproved', 'isLockedOut', 'key', 'kind', 'profileData', 'sessionTimeout', 'updateDate', 'username', 'userState'], - type: 'object', - properties: { - id: { - type: 'integer', - format: 'int32' - }, - key: { - type: 'string', - format: 'uuid' - }, - createDate: { - type: 'string', - format: 'date-time' - }, - updateDate: { - type: 'string', - format: 'date-time' - }, - deleteDate: { - type: 'string', - format: 'date-time', - nullable: true - }, - hasIdentity: { - type: 'boolean', - readOnly: true - }, - emailConfirmedDate: { - type: 'string', - format: 'date-time', - nullable: true - }, - invitedDate: { - type: 'string', - format: 'date-time', - nullable: true - }, - username: { - type: 'string' - }, - email: { - type: 'string' - }, - rawPasswordValue: { - type: 'string', - nullable: true - }, - passwordConfiguration: { - type: 'string', - nullable: true - }, - isApproved: { - type: 'boolean' - }, - isLockedOut: { - type: 'boolean' - }, - lastLoginDate: { - type: 'string', - format: 'date-time', - nullable: true - }, - lastPasswordChangeDate: { - type: 'string', - format: 'date-time', - nullable: true - }, - lastLockoutDate: { - type: 'string', - format: 'date-time', - nullable: true - }, - failedPasswordAttempts: { - type: 'integer', - format: 'int32' - }, - comments: { - type: 'string', - nullable: true - }, - userState: { - '$ref': '#/components/schemas/UserStateModel' - }, - name: { - type: 'string', - nullable: true - }, - allowedSections: { - type: 'array', - items: { - type: 'string' - }, - readOnly: true - }, - profileData: { - oneOf: [ - { - '$ref': '#/components/schemas/UserModel' - }, - { - '$ref': '#/components/schemas/UserProfileModel' - } - ], - readOnly: true - }, - securityStamp: { - type: 'string', - nullable: true - }, - avatar: { - type: 'string', - nullable: true - }, - sessionTimeout: { - type: 'integer', - format: 'int32' - }, - startContentIds: { - type: 'array', - items: { - type: 'integer', - format: 'int32' - }, - nullable: true - }, - startMediaIds: { - type: 'array', - items: { - type: 'integer', - format: 'int32' - }, - nullable: true - }, - language: { - type: 'string', - nullable: true - }, - kind: { - '$ref': '#/components/schemas/UserKindModel' - }, - groups: { - type: 'array', - items: { - oneOf: [ - { - '$ref': '#/components/schemas/ReadOnlyUserGroupModel' - }, - { - '$ref': '#/components/schemas/UserGroupModel' - } - ] - }, - readOnly: true - } - }, - additionalProperties: false -} as const; - -export const UserProfileModelSchema = { - required: ['id'], - type: 'object', - properties: { - id: { - type: 'integer', - format: 'int32' - }, - name: { - type: 'string', - nullable: true - } - }, - additionalProperties: false -} as const; - -export const UserStateModelSchema = { - enum: ['Active', 'Disabled', 'LockedOut', 'Invited', 'Inactive', 'All'], - type: 'string' -} as const; \ No newline at end of file diff --git a/templates/UmbracoExtension/Client/src/api/sdk.gen.ts b/templates/UmbracoExtension/Client/src/api/sdk.gen.ts new file mode 100644 index 000000000000..0465ad97b564 --- /dev/null +++ b/templates/UmbracoExtension/Client/src/api/sdk.gen.ts @@ -0,0 +1,78 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { Options as ClientOptions, TDataShape, Client } from '@hey-api/client-fetch'; +//#if(IncludeExample) +import type { PingData, PingResponse, WhatsMyNameData, WhatsMyNameResponse, WhatsTheTimeMrWolfData, WhatsTheTimeMrWolfResponse, WhoAmIData, WhoAmIResponse } from './types.gen'; +//#else +import type { PingData, PingResponse } from './types.gen'; +//#endif +import { client as _heyApiClient } from './client.gen'; + +export type Options<TData extends TDataShape = TDataShape, ThrowOnError extends boolean = boolean> = ClientOptions<TData, ThrowOnError> & { + /** + * You can provide a client instance returned by `createClient()` instead of + * individual options. This might be also useful if you want to implement a + * custom client. + */ + client?: Client; + /** + * You can pass arbitrary values through the `meta` object. This can be + * used to access values that aren't defined as part of the SDK function. + */ + meta?: Record<string, unknown>; +}; + +export class UmbracoExtensionService { + public static ping<ThrowOnError extends boolean = false>(options?: Options<PingData, ThrowOnError>) { + return (options?.client ?? _heyApiClient).get<PingResponse, unknown, ThrowOnError>({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/umbraco/umbracoextension/api/v1/ping', + ...options + }); + } +//#if(IncludeExample) + public static whatsMyName<ThrowOnError extends boolean = false>(options?: Options<WhatsMyNameData, ThrowOnError>) { + return (options?.client ?? _heyApiClient).get<WhatsMyNameResponse, unknown, ThrowOnError>({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/umbraco/umbracoextension/api/v1/whatsMyName', + ...options + }); + } + + public static whatsTheTimeMrWolf<ThrowOnError extends boolean = false>(options?: Options<WhatsTheTimeMrWolfData, ThrowOnError>) { + return (options?.client ?? _heyApiClient).get<WhatsTheTimeMrWolfResponse, unknown, ThrowOnError>({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/umbraco/umbracoextension/api/v1/whatsTheTimeMrWolf', + ...options + }); + } + + public static whoAmI<ThrowOnError extends boolean = false>(options?: Options<WhoAmIData, ThrowOnError>) { + return (options?.client ?? _heyApiClient).get<WhoAmIResponse, unknown, ThrowOnError>({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/umbraco/umbracoextension/api/v1/whoAmI', + ...options + }); + } +//#endif +} \ No newline at end of file diff --git a/templates/UmbracoExtension/Client/src/api/services.gen.ts b/templates/UmbracoExtension/Client/src/api/services.gen.ts deleted file mode 100644 index c9d1c0b91b45..000000000000 --- a/templates/UmbracoExtension/Client/src/api/services.gen.ts +++ /dev/null @@ -1,41 +0,0 @@ -// This file is auto-generated by @hey-api/openapi-ts - -import { createClient, createConfig, type Options } from '@hey-api/client-fetch'; -//#if(IncludeExample) -import type { PingError, PingResponse, WhatsMyNameError, WhatsMyNameResponse, WhatsTheTimeMrWolfError, WhatsTheTimeMrWolfResponse, WhoAmIError, WhoAmIResponse } from './types.gen'; -//#else -import type { PingError, PingResponse } from './types.gen'; -//#endif - -export const client = createClient(createConfig()); - -export class UmbracoExtensionService { - public static ping<ThrowOnError extends boolean = false>(options?: Options<unknown, ThrowOnError>) { - return (options?.client ?? client).get<PingResponse, PingError, ThrowOnError>({ - ...options, - url: '/umbraco/umbracoextension/api/v1/ping' - }); - } -//#if(IncludeExample) - public static whatsMyName<ThrowOnError extends boolean = false>(options?: Options<unknown, ThrowOnError>) { - return (options?.client ?? client).get<WhatsMyNameResponse, WhatsMyNameError, ThrowOnError>({ - ...options, - url: '/umbraco/umbracoextension/api/v1/whatsMyName' - }); - } - - public static whatsTheTimeMrWolf<ThrowOnError extends boolean = false>(options?: Options<unknown, ThrowOnError>) { - return (options?.client ?? client).get<WhatsTheTimeMrWolfResponse, WhatsTheTimeMrWolfError, ThrowOnError>({ - ...options, - url: '/umbraco/umbracoextension/api/v1/whatsTheTimeMrWolf' - }); - } - - public static whoAmI<ThrowOnError extends boolean = false>(options?: Options<unknown, ThrowOnError>) { - return (options?.client ?? client).get<WhoAmIResponse, WhoAmIError, ThrowOnError>({ - ...options, - url: '/umbraco/umbracoextension/api/v1/whoAmI' - }); - } -//#endif -} diff --git a/templates/UmbracoExtension/Client/src/api/types.gen.ts b/templates/UmbracoExtension/Client/src/api/types.gen.ts index e66679280370..9da3ce689750 100644 --- a/templates/UmbracoExtension/Client/src/api/types.gen.ts +++ b/templates/UmbracoExtension/Client/src/api/types.gen.ts @@ -1,5 +1,5 @@ // This file is auto-generated by @hey-api/openapi-ts -//#if(IncludeExample) +//#if(IncludeExample) export type DocumentGranularPermissionModel = { key: string; readonly context: string; @@ -10,15 +10,15 @@ export type ReadOnlyUserGroupModel = { id: number; key: string; name: string; - icon?: (string) | null; - startContentId?: (number) | null; - startMediaId?: (number) | null; + icon?: string | null; + startContentId?: number | null; + startMediaId?: number | null; alias: string; hasAccessToAllLanguages: boolean; - allowedLanguages: Array<(number)>; - permissions: Array<(string)>; - granularPermissions: Array<(DocumentGranularPermissionModel | UnknownTypeGranularPermissionModel)>; - allowedSections: Array<(string)>; + allowedLanguages: Array<number>; + permissions: Array<string>; + granularPermissions: Array<DocumentGranularPermissionModel | UnknownTypeGranularPermissionModel>; + allowedSections: Array<string>; }; export type UnknownTypeGranularPermissionModel = { @@ -31,77 +31,166 @@ export type UserGroupModel = { key: string; createDate: string; updateDate: string; - deleteDate?: (string) | null; + deleteDate?: string | null; readonly hasIdentity: boolean; - startMediaId?: (number) | null; - startContentId?: (number) | null; - icon?: (string) | null; + startMediaId?: number | null; + startContentId?: number | null; + icon?: string | null; alias: string; - name?: (string) | null; + name?: string | null; hasAccessToAllLanguages: boolean; - permissions: Array<(string)>; - granularPermissions: Array<(DocumentGranularPermissionModel | UnknownTypeGranularPermissionModel)>; - readonly allowedSections: Array<(string)>; + permissions: Array<string>; + granularPermissions: Array<DocumentGranularPermissionModel | UnknownTypeGranularPermissionModel>; + readonly allowedSections: Array<string>; readonly userCount: number; - readonly allowedLanguages: Array<(number)>; + readonly allowedLanguages: Array<number>; }; -export type UserKindModel = 'Default' | 'Api'; +export enum UserKindModel { + DEFAULT = 'Default', + API = 'Api' +} export type UserModel = { id: number; key: string; createDate: string; updateDate: string; - deleteDate?: (string) | null; + deleteDate?: string | null; readonly hasIdentity: boolean; - emailConfirmedDate?: (string) | null; - invitedDate?: (string) | null; + emailConfirmedDate?: string | null; + invitedDate?: string | null; username: string; email: string; - rawPasswordValue?: (string) | null; - passwordConfiguration?: (string) | null; + rawPasswordValue?: string | null; + passwordConfiguration?: string | null; isApproved: boolean; isLockedOut: boolean; - lastLoginDate?: (string) | null; - lastPasswordChangeDate?: (string) | null; - lastLockoutDate?: (string) | null; + lastLoginDate?: string | null; + lastPasswordChangeDate?: string | null; + lastLockoutDate?: string | null; failedPasswordAttempts: number; - comments?: (string) | null; + comments?: string | null; userState: UserStateModel; - name?: (string) | null; - readonly allowedSections: Array<(string)>; - readonly profileData: (UserModel | UserProfileModel); - securityStamp?: (string) | null; - avatar?: (string) | null; + name?: string | null; + readonly allowedSections: Array<string>; + profileData: UserModel | UserProfileModel; + securityStamp?: string | null; + avatar?: string | null; sessionTimeout: number; - startContentIds?: Array<(number)> | null; - startMediaIds?: Array<(number)> | null; - language?: (string) | null; + startContentIds?: Array<number> | null; + startMediaIds?: Array<number> | null; + language?: string | null; kind: UserKindModel; - readonly groups: Array<(ReadOnlyUserGroupModel | UserGroupModel)>; + readonly groups: Array<ReadOnlyUserGroupModel | UserGroupModel>; }; export type UserProfileModel = { id: number; - name?: (string) | null; + name?: string | null; }; -export type UserStateModel = 'Active' | 'Disabled' | 'LockedOut' | 'Invited' | 'Inactive' | 'All'; +export enum UserStateModel { + ACTIVE = 'Active', + DISABLED = 'Disabled', + LOCKED_OUT = 'LockedOut', + INVITED = 'Invited', + INACTIVE = 'Inactive', + ALL = 'All' +} //#endif -export type PingResponse = (string); +export type PingData = { + body?: never; + path?: never; + query?: never; + url: '/umbraco/hackclient/api/v1/ping'; +}; -export type PingError = (unknown); -//#if(IncludeExample) -export type WhatsMyNameResponse = (string); +export type PingErrors = { + /** + * The resource is protected and requires an authentication token + */ + 401: unknown; +}; -export type WhatsMyNameError = (unknown); +export type PingResponses = { + /** + * OK + */ + 200: string; +}; -export type WhatsTheTimeMrWolfResponse = (string); +export type PingResponse = PingResponses[keyof PingResponses]; +//#if(IncludeExample) +export type WhatsMyNameData = { + body?: never; + path?: never; + query?: never; + url: '/umbraco/hackclient/api/v1/whatsMyName'; +}; -export type WhatsTheTimeMrWolfError = (unknown); +export type WhatsMyNameErrors = { + /** + * The resource is protected and requires an authentication token + */ + 401: unknown; +}; -export type WhoAmIResponse = ((UserModel)); +export type WhatsMyNameResponses = { + /** + * OK + */ + 200: string; +}; + +export type WhatsMyNameResponse = WhatsMyNameResponses[keyof WhatsMyNameResponses]; + +export type WhatsTheTimeMrWolfData = { + body?: never; + path?: never; + query?: never; + url: '/umbraco/hackclient/api/v1/whatsTheTimeMrWolf'; +}; + +export type WhatsTheTimeMrWolfErrors = { + /** + * The resource is protected and requires an authentication token + */ + 401: unknown; +}; -export type WhoAmIError = (unknown); +export type WhatsTheTimeMrWolfResponses = { + /** + * OK + */ + 200: string; +}; + +export type WhatsTheTimeMrWolfResponse = WhatsTheTimeMrWolfResponses[keyof WhatsTheTimeMrWolfResponses]; + +export type WhoAmIData = { + body?: never; + path?: never; + query?: never; + url: '/umbraco/hackclient/api/v1/whoAmI'; +}; + +export type WhoAmIErrors = { + /** + * The resource is protected and requires an authentication token + */ + 401: unknown; +}; + +export type WhoAmIResponses = { + /** + * OK + */ + 200: UserModel; +}; + +export type WhoAmIResponse = WhoAmIResponses[keyof WhoAmIResponses]; //#endif +export type ClientOptions = { + baseUrl: 'https://localhost:44389' | (string & {}); +}; diff --git a/templates/UmbracoExtension/Client/src/bundle.manifests.ts b/templates/UmbracoExtension/Client/src/bundle.manifests.ts index fb2d2bfa09f1..6186b361f3b4 100644 --- a/templates/UmbracoExtension/Client/src/bundle.manifests.ts +++ b/templates/UmbracoExtension/Client/src/bundle.manifests.ts @@ -1,6 +1,6 @@ -import { manifests as entrypoints } from './entrypoints/manifest'; +import { manifests as entrypoints } from "./entrypoints/manifest.js"; //#if IncludeExample -import { manifests as dashboards } from './dashboards/manifest'; +import { manifests as dashboards } from "./dashboards/manifest.js"; //#endif // Job of the bundle is to collate all the manifests from different parts of the extension and load other manifests diff --git a/templates/UmbracoExtension/Client/src/dashboards/dashboard.element.ts b/templates/UmbracoExtension/Client/src/dashboards/dashboard.element.ts index 287464086e7c..7a2d0756ecaa 100644 --- a/templates/UmbracoExtension/Client/src/dashboards/dashboard.element.ts +++ b/templates/UmbracoExtension/Client/src/dashboards/dashboard.element.ts @@ -1,13 +1,24 @@ -import { LitElement, css, html, customElement, state } from "@umbraco-cms/backoffice/external/lit"; +import { + LitElement, + css, + html, + customElement, + state, +} from "@umbraco-cms/backoffice/external/lit"; import { UmbElementMixin } from "@umbraco-cms/backoffice/element-api"; -import { UmbracoExtensionService, UserModel } from "../api"; import { UUIButtonElement } from "@umbraco-cms/backoffice/external/uui"; -import { UMB_NOTIFICATION_CONTEXT, UmbNotificationContext } from "@umbraco-cms/backoffice/notification"; -import { UMB_CURRENT_USER_CONTEXT, UmbCurrentUserModel } from "@umbraco-cms/backoffice/current-user"; - -@customElement('example-dashboard') +import { + UMB_NOTIFICATION_CONTEXT, + UmbNotificationContext, +} from "@umbraco-cms/backoffice/notification"; +import { + UMB_CURRENT_USER_CONTEXT, + UmbCurrentUserModel, +} from "@umbraco-cms/backoffice/current-user"; +import { UmbracoExtensionService, UserModel } from "../api/index.js"; + +@customElement("example-dashboard") export class ExampleDashboardElement extends UmbElementMixin(LitElement) { - @state() private _yourName: string | undefined = "Press the button!"; @@ -28,7 +39,6 @@ export class ExampleDashboardElement extends UmbElementMixin(LitElement) { }); this.consumeContext(UMB_CURRENT_USER_CONTEXT, (currentUserContext) => { - // When we have the current user context // We can observe properties from it, such as the current user or perhaps just individual properties // When the currentUser object changes we will get notified and can reset the @state properrty @@ -62,10 +72,10 @@ export class ExampleDashboardElement extends UmbElementMixin(LitElement) { data: { headline: `You are ${this._serverUserData?.name}`, message: `Your email is ${this._serverUserData?.email}`, - } - }) + }, + }); } - } + }; #onClickWhatsTheTimeMrWolf = async (ev: Event) => { const buttonElement = ev.target as UUIButtonElement; @@ -84,7 +94,7 @@ export class ExampleDashboardElement extends UmbElementMixin(LitElement) { this._timeFromMrWolf = new Date(data); buttonElement.state = "success"; } - } + }; #onClickWhatsMyName = async (ev: Event) => { const buttonElement = ev.target as UUIButtonElement; @@ -100,77 +110,111 @@ export class ExampleDashboardElement extends UmbElementMixin(LitElement) { this._yourName = data; buttonElement.state = "success"; - } + }; render() { return html` - <uui-box headline="Who am I?"> - <div slot="header">[Server]</div> - <h2><uui-icon name="icon-user"></uui-icon>${this._serverUserData?.email ? this._serverUserData.email : 'Press the button!'}</h2> - <ul> - ${this._serverUserData?.groups.map(group => html`<li>${group.name}</li>`)} - </ul> - <uui-button color="default" look="primary" @click="${this.#onClickWhoAmI}"> - Who am I? - </uui-button> - <p>This endpoint gets your current user from the server and displays your email and list of user groups. - It also displays a Notification with your details.</p> - </uui-box> - - <uui-box headline="What's my Name?"> - <div slot="header">[Server]</div> - <h2><uui-icon name="icon-user"></uui-icon> ${this._yourName }</h2> - <uui-button color="default" look="primary" @click="${this.#onClickWhatsMyName}"> - Whats my name? - </uui-button> - <p>This endpoint has a forced delay to show the button 'waiting' state for a few seconds before completing the request.</p> - </uui-box> - - <uui-box headline="What's the Time?"> - <div slot="header">[Server]</div> - <h2><uui-icon name="icon-alarm-clock"></uui-icon> ${this._timeFromMrWolf ? this._timeFromMrWolf.toLocaleString() : 'Press the button!'}</h2> - <uui-button color="default" look="primary" @click="${this.#onClickWhatsTheTimeMrWolf}"> - Whats the time Mr Wolf? - </uui-button> - <p>This endpoint gets the current date and time from the server.</p> - </uui-box> - - <uui-box headline="Who am I?" class="wide"> - <div slot="header">[Context]</div> - <p>Current user email: <b>${this._contextCurrentUser?.email}</b></p> - <p>This is the JSON object available by consuming the 'UMB_CURRENT_USER_CONTEXT' context:</p> - <umb-code-block language="json" copy>${JSON.stringify(this._contextCurrentUser, null, 2)}</umb-code-block> - </uui-box> + <uui-box headline="Who am I?"> + <div slot="header">[Server]</div> + <h2> + <uui-icon name="icon-user"></uui-icon>${this._serverUserData?.email + ? this._serverUserData.email + : "Press the button!"} + </h2> + <ul> + ${this._serverUserData?.groups.map( + (group) => html`<li>${group.name}</li>` + )} + </ul> + <uui-button + color="default" + look="primary" + @click="${this.#onClickWhoAmI}" + > + Who am I? + </uui-button> + <p> + This endpoint gets your current user from the server and displays your + email and list of user groups. It also displays a Notification with + your details. + </p> + </uui-box> + + <uui-box headline="What's my Name?"> + <div slot="header">[Server]</div> + <h2><uui-icon name="icon-user"></uui-icon> ${this._yourName}</h2> + <uui-button + color="default" + look="primary" + @click="${this.#onClickWhatsMyName}" + > + Whats my name? + </uui-button> + <p> + This endpoint has a forced delay to show the button 'waiting' state + for a few seconds before completing the request. + </p> + </uui-box> + + <uui-box headline="What's the Time?"> + <div slot="header">[Server]</div> + <h2> + <uui-icon name="icon-alarm-clock"></uui-icon> ${this._timeFromMrWolf + ? this._timeFromMrWolf.toLocaleString() + : "Press the button!"} + </h2> + <uui-button + color="default" + look="primary" + @click="${this.#onClickWhatsTheTimeMrWolf}" + > + Whats the time Mr Wolf? + </uui-button> + <p>This endpoint gets the current date and time from the server.</p> + </uui-box> + + <uui-box headline="Who am I?" class="wide"> + <div slot="header">[Context]</div> + <p>Current user email: <b>${this._contextCurrentUser?.email}</b></p> + <p> + This is the JSON object available by consuming the + 'UMB_CURRENT_USER_CONTEXT' context: + </p> + <umb-code-block language="json" copy + >${JSON.stringify(this._contextCurrentUser, null, 2)}</umb-code-block + > + </uui-box> `; } static styles = [ css` - :host { - display: grid; - gap: var(--uui-size-layout-1); - padding: var(--uui-size-layout-1); - grid-template-columns: 1fr 1fr 1fr; - } - - uui-box { - margin-bottom: var(--uui-size-layout-1); - } - - h2 { - margin-top:0; - } - - .wide { - grid-column: span 3; - } - `]; + :host { + display: grid; + gap: var(--uui-size-layout-1); + padding: var(--uui-size-layout-1); + grid-template-columns: 1fr 1fr 1fr; + } + + uui-box { + margin-bottom: var(--uui-size-layout-1); + } + + h2 { + margin-top: 0; + } + + .wide { + grid-column: span 3; + } + `, + ]; } export default ExampleDashboardElement; declare global { interface HTMLElementTagNameMap { - 'example-dashboard': ExampleDashboardElement; + "example-dashboard": ExampleDashboardElement; } } diff --git a/templates/UmbracoExtension/Client/src/dashboards/manifest.ts b/templates/UmbracoExtension/Client/src/dashboards/manifest.ts index a020e3ab2c42..1fa7c360b8f7 100644 --- a/templates/UmbracoExtension/Client/src/dashboards/manifest.ts +++ b/templates/UmbracoExtension/Client/src/dashboards/manifest.ts @@ -2,17 +2,17 @@ export const manifests: Array<UmbExtensionManifest> = [ { name: "Umbraco ExtensionDashboard", alias: "Umbraco.Extension.Dashboard", - type: 'dashboard', - js: () => import("./dashboard.element"), + type: "dashboard", + js: () => import("./dashboard.element.js"), meta: { label: "Example Dashboard", - pathname: "example-dashboard" + pathname: "example-dashboard", }, conditions: [ { - alias: 'Umb.Condition.SectionAlias', - match: 'Umb.Section.Content', - } + alias: "Umb.Condition.SectionAlias", + match: "Umb.Section.Content", + }, ], - } + }, ]; diff --git a/templates/UmbracoExtension/Client/src/entrypoints/entrypoint.ts b/templates/UmbracoExtension/Client/src/entrypoints/entrypoint.ts index 65796c32023a..d6115312f4f8 100644 --- a/templates/UmbracoExtension/Client/src/entrypoints/entrypoint.ts +++ b/templates/UmbracoExtension/Client/src/entrypoints/entrypoint.ts @@ -1,38 +1,31 @@ -import { UmbEntryPointOnInit, UmbEntryPointOnUnload } from '@umbraco-cms/backoffice/extension-api'; +import { + UmbEntryPointOnInit, + UmbEntryPointOnUnload, +} from "@umbraco-cms/backoffice/extension-api"; //#if IncludeExample -import { UMB_AUTH_CONTEXT } from '@umbraco-cms/backoffice/auth'; -import { client } from '../api'; +import { UMB_AUTH_CONTEXT } from "@umbraco-cms/backoffice/auth"; +import { client } from "../api/client.gen.js"; //#endif // load up the manifests here export const onInit: UmbEntryPointOnInit = (_host, _extensionRegistry) => { - - console.log('Hello from my extension 🎉'); + console.log("Hello from my extension 🎉"); //#if IncludeExample // Will use only to add in Open API config with generated TS OpenAPI HTTPS Client // Do the OAuth token handshake stuff _host.consumeContext(UMB_AUTH_CONTEXT, async (authContext) => { - // Get the token info from Umbraco const config = authContext.getOpenApiConfiguration(); client.setConfig({ + auth: config.token, baseUrl: config.base, - credentials: config.credentials - }); - - // For every request being made, add the token to the headers - // Can't use the setConfig approach above as its set only once and - // tokens expire and get refreshed - client.interceptors.request.use(async (request, _options) => { - const token = await config.token(); - request.headers.set('Authorization', `Bearer ${token}`); - return request; + credentials: config.credentials, }); }); //#endif }; export const onUnload: UmbEntryPointOnUnload = (_host, _extensionRegistry) => { - console.log('Goodbye from my extension 👋'); + console.log("Goodbye from my extension 👋"); }; diff --git a/templates/UmbracoExtension/Client/src/entrypoints/manifest.ts b/templates/UmbracoExtension/Client/src/entrypoints/manifest.ts index 5dcda9de89bb..cd97f1e6a761 100644 --- a/templates/UmbracoExtension/Client/src/entrypoints/manifest.ts +++ b/templates/UmbracoExtension/Client/src/entrypoints/manifest.ts @@ -3,6 +3,6 @@ export const manifests: Array<UmbExtensionManifest> = [ name: "Umbraco ExtensionEntrypoint", alias: "Umbraco.Extension.Entrypoint", type: "backofficeEntryPoint", - js: () => import("./entrypoint"), - } + js: () => import("./entrypoint.js"), + }, ]; diff --git a/templates/UmbracoExtension/Client/vite.config.ts b/templates/UmbracoExtension/Client/vite.config.ts index 5232076b8ce8..d2db7d4ffda7 100644 --- a/templates/UmbracoExtension/Client/vite.config.ts +++ b/templates/UmbracoExtension/Client/vite.config.ts @@ -13,5 +13,5 @@ export default defineConfig({ rollupOptions: { external: [/^@umbraco/], }, - } + }, }); diff --git a/templates/UmbracoExtension/Umbraco.Extension.csproj b/templates/UmbracoExtension/Umbraco.Extension.csproj index 3b70140f3542..d6b03e516cd9 100644 --- a/templates/UmbracoExtension/Umbraco.Extension.csproj +++ b/templates/UmbracoExtension/Umbraco.Extension.csproj @@ -24,13 +24,34 @@ <PackageReference Include="Umbraco.Cms.Api.Common" Version="UMBRACO_VERSION_FROM_TEMPLATE" /> <PackageReference Include="Umbraco.Cms.Api.Management" Version="UMBRACO_VERSION_FROM_TEMPLATE" /> </ItemGroup> - + <ItemGroup> + <ClientAssetsInputs Include="Client\**" Exclude="$(DefaultItemExcludes)" /> + <!-- Dont include the client folder as part of packaging nuget build --> <Content Remove="Client\**" /> <!-- However make the Umbraco-package.json included for dotnet pack or nuget package and visible to the solution --> <None Include="Client\public\umbraco-package.json" Pack="false" /> </ItemGroup> - + + <ItemGroup> + <Folder Include="wwwroot\" /> + </ItemGroup> + + <!-- Restore and build Client files --> + <Target Name="RestoreClient" Inputs="Client\package.json;Client\package-lock.json" Outputs="Client\node_modules\.package-lock.json"> + <Message Importance="high" Text="Restoring Client NPM packages..." /> + <Exec Command="npm i" WorkingDirectory="Client" /> + </Target> + + <Target Name="BuildClient" BeforeTargets="AssignTargetPaths" DependsOnTargets="RestoreClient" Inputs="@(ClientAssetsInputs)" Outputs="$(IntermediateOutputPath)client.complete.txt"> + <Message Importance="high" Text="Executing Client NPM build script..." /> + <Exec Command="npm run build" WorkingDirectory="Client" /> + <ItemGroup> + <_ClientAssetsBuildOutput Include="wwwroot\App_Plugins\**" /> + </ItemGroup> + <WriteLinesToFile File="$(IntermediateOutputPath)client.complete.txt" Lines="@(_ClientAssetsBuildOutput)" Overwrite="true" /> + </Target> + </Project> diff --git a/tests/Umbraco.Tests.AcceptanceTest/package-lock.json b/tests/Umbraco.Tests.AcceptanceTest/package-lock.json index 8ea239c32748..0518f7a7f50c 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/package-lock.json +++ b/tests/Umbraco.Tests.AcceptanceTest/package-lock.json @@ -8,7 +8,7 @@ "hasInstallScript": true, "dependencies": { "@umbraco/json-models-builders": "^2.0.29", - "@umbraco/playwright-testhelpers": "^15.0.24", + "@umbraco/playwright-testhelpers": "^15.0.34", "camelize": "^1.0.0", "dotenv": "^16.3.1", "node-fetch": "^2.6.7" @@ -67,9 +67,9 @@ } }, "node_modules/@umbraco/playwright-testhelpers": { - "version": "15.0.24", - "resolved": "https://registry.npmjs.org/@umbraco/playwright-testhelpers/-/playwright-testhelpers-15.0.24.tgz", - "integrity": "sha512-cv7sr3e1vhOoqAKOgj82kKgWY9dCQCnQdP+4rGllM/Dhvup+nSs93XKOAnTc2Fn3ZqhpwA8PDL8Pg9riUpt5JQ==", + "version": "15.0.34", + "resolved": "https://registry.npmjs.org/@umbraco/playwright-testhelpers/-/playwright-testhelpers-15.0.34.tgz", + "integrity": "sha512-dEWjiUCWdxBpvDnCoShqRZ5xEfNEo02BBgNIKqDAUDEBht/lAM/pDaUgzu2sUbb7D8AbkrrIxPiNn69XakoNSg==", "dependencies": { "@umbraco/json-models-builders": "2.0.30", "node-fetch": "^2.6.7" diff --git a/tests/Umbraco.Tests.AcceptanceTest/package.json b/tests/Umbraco.Tests.AcceptanceTest/package.json index 9c542f9bf062..da652bf8fc09 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/package.json +++ b/tests/Umbraco.Tests.AcceptanceTest/package.json @@ -21,7 +21,7 @@ }, "dependencies": { "@umbraco/json-models-builders": "^2.0.29", - "@umbraco/playwright-testhelpers": "^15.0.24", + "@umbraco/playwright-testhelpers": "^15.0.34", "camelize": "^1.0.0", "dotenv": "^16.3.1", "node-fetch": "^2.6.7" diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/BlockGrid/BlockGridArea.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/BlockGrid/BlockGridArea.spec.ts new file mode 100644 index 000000000000..a669c4f527bd --- /dev/null +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/BlockGrid/BlockGridArea.spec.ts @@ -0,0 +1,220 @@ +import {expect} from '@playwright/test'; +import {AliasHelper, ConstantHelper, NotificationConstantHelper, test} from '@umbraco/playwright-testhelpers'; + +// Document Type +const documentTypeName = 'TestDocumentTypeForContent'; +let documentTypeId = ''; + +// Content +const contentName = 'TestContent'; + +// Element Types +const firstElementTypeName = 'FirstBlockGridElement'; +let firstElementTypeId = null; +const secondElementTypeName = 'SecondBlockGridElement'; + +// Block Grid Data Type +const blockGridDataTypeName = 'BlockGridTester'; +let blockGridDataTypeId = null; +const firstAreaName = 'FirstArea'; +const areaCreateLabel = 'CreateLabel'; +const toAllowInAreas = true; + +test.beforeEach(async ({umbracoApi}) => { + await umbracoApi.documentType.ensureNameNotExists(documentTypeName); + await umbracoApi.documentType.ensureNameNotExists(firstElementTypeName); + await umbracoApi.documentType.ensureNameNotExists(secondElementTypeName); + await umbracoApi.document.ensureNameNotExists(contentName); + await umbracoApi.dataType.ensureNameNotExists(blockGridDataTypeName); +}); + +test.afterEach(async ({umbracoApi}) => { + await umbracoApi.documentType.ensureNameNotExists(documentTypeName); + await umbracoApi.documentType.ensureNameNotExists(firstElementTypeName); + await umbracoApi.documentType.ensureNameNotExists(secondElementTypeName); + await umbracoApi.document.ensureNameNotExists(contentName); + await umbracoApi.dataType.ensureNameNotExists(blockGridDataTypeName); +}); + +test('can create content with a block grid with an empty block in a area', {tag: '@smoke'}, async ({umbracoApi, umbracoUi}) => { + // Arrange + firstElementTypeId = await umbracoApi.documentType.createEmptyElementType(firstElementTypeName); + blockGridDataTypeId = await umbracoApi.dataType.createBlockGridWithAnAreaInABlockWithAllowInAreas(blockGridDataTypeName, firstElementTypeId, firstAreaName, toAllowInAreas, areaCreateLabel); + documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, blockGridDataTypeName, blockGridDataTypeId); + await umbracoApi.document.createDefaultDocument(contentName, documentTypeId); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + await umbracoUi.content.goToContentWithName(contentName); + + // Act + await umbracoUi.content.clickAddBlockGridElementWithName(firstElementTypeName); + await umbracoUi.content.clickSelectBlockElementWithName(firstElementTypeName); + await umbracoUi.content.clickLinkWithName(areaCreateLabel); + await umbracoUi.content.clickSelectBlockElementInAreaWithName(firstElementTypeName); + await umbracoUi.content.clickSaveAndPublishButton(); + + // Assert + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.published); + await umbracoUi.reloadPage(); + await umbracoUi.content.doesBlockContainBlockInAreaWithName(firstElementTypeName, firstAreaName, firstElementTypeName); + await umbracoUi.content.doesBlockContainBlockCountInArea(firstElementTypeName, firstAreaName, 1); +}); + +test('can create content with a block grid with two empty blocks in a area', async ({umbracoApi, umbracoUi}) => { + // Arrange + firstElementTypeId = await umbracoApi.documentType.createEmptyElementType(firstElementTypeName); + blockGridDataTypeId = await umbracoApi.dataType.createBlockGridWithAnAreaInABlockWithAllowInAreas(blockGridDataTypeName, firstElementTypeId, firstAreaName, toAllowInAreas, areaCreateLabel); + documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, blockGridDataTypeName, blockGridDataTypeId); + await umbracoApi.document.createDefaultDocument(contentName, documentTypeId); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + await umbracoUi.content.goToContentWithName(contentName); + + // Act + await umbracoUi.content.clickAddBlockGridElementWithName(firstElementTypeName); + await umbracoUi.content.clickSelectBlockElementWithName(firstElementTypeName); + await umbracoUi.content.clickLinkWithName(areaCreateLabel); + await umbracoUi.content.clickSelectBlockElementInAreaWithName(firstElementTypeName); + await umbracoUi.content.addBlockToAreasWithExistingBlock(firstElementTypeName, firstAreaName, 0, 0); + await umbracoUi.content.clickSelectBlockElementInAreaWithName(firstElementTypeName); + await umbracoUi.content.clickSaveAndPublishButton(); + + // Assert + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.published); + await umbracoUi.reloadPage(); + await umbracoUi.content.doesBlockContainCountOfBlockInArea(firstElementTypeName, firstAreaName, firstElementTypeName, 2); + + // Checks if the block grid contains the blocks through the API + const parentBlockKey = await umbracoUi.content.getBlockAtRootDataElementKey(firstElementTypeName, 0); + const areaKey = await umbracoUi.content.getBlockAreaKeyFromParentBlockDataElementKey(parentBlockKey, 0); + const firstBlockInAreaKey = await umbracoUi.content.getBlockDataElementKeyInArea(firstElementTypeName, firstAreaName, firstElementTypeName, 0, 0); + const secondBlockInAreaKey = await umbracoUi.content.getBlockDataElementKeyInArea(firstElementTypeName, firstAreaName, firstElementTypeName, 0, 1); + expect(await umbracoApi.document.doesBlockGridContainBlocksWithDataElementKeyInAreaWithKey(contentName, AliasHelper.toAlias(blockGridDataTypeName), parentBlockKey, areaKey, [firstBlockInAreaKey, secondBlockInAreaKey])).toBeTruthy(); +}); + +test('can create content with block grid area with a create label', async ({umbracoApi, umbracoUi}) => { + // Arrange + firstElementTypeId = await umbracoApi.documentType.createEmptyElementType(firstElementTypeName); + const createLabel = 'ThisIsACreateLabel'; + blockGridDataTypeId = await umbracoApi.dataType.createBlockGridWithAnAreaInABlockWithACreateLabel(blockGridDataTypeName, firstElementTypeId, createLabel, firstAreaName); + documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, blockGridDataTypeName, blockGridDataTypeId); + await umbracoApi.document.createDefaultDocument(contentName, documentTypeId); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + await umbracoUi.content.goToContentWithName(contentName); + + // Act + await umbracoUi.content.clickAddBlockGridElementWithName(firstElementTypeName); + await umbracoUi.content.clickSelectBlockElementWithName(firstElementTypeName); + await umbracoUi.content.clickSaveAndPublishButton(); + + // Assert + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.published); + await umbracoUi.content.doesBlockGridBlockWithAreaContainCreateLabel(firstElementTypeName, createLabel); +}); + +test('can create content with block grid area with column span', async ({umbracoApi, umbracoUi}) => { + // Arrange + firstElementTypeId = await umbracoApi.documentType.createEmptyElementType(firstElementTypeName); + const columnSpan = 2; + blockGridDataTypeId = await umbracoApi.dataType.createBlockGridWithAnAreaInABlockWithColumnSpanAndRowSpan(blockGridDataTypeName, firstElementTypeId, columnSpan, 1, firstAreaName, areaCreateLabel); + documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, blockGridDataTypeName, blockGridDataTypeId); + await umbracoApi.document.createDefaultDocument(contentName, documentTypeId); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + await umbracoUi.content.goToContentWithName(contentName); + + // Act + await umbracoUi.content.clickAddBlockGridElementWithName(firstElementTypeName); + await umbracoUi.content.clickSelectBlockElementWithName(firstElementTypeName); + await umbracoUi.content.clickSaveAndPublishButton(); + + // Assert + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.published); + await umbracoUi.content.doesBlockAreaContainColumnSpan(firstElementTypeName, firstAreaName, columnSpan, 0); +}); + +test('can create content with block grid area with row span', async ({umbracoApi, umbracoUi}) => { + // Arrange + firstElementTypeId = await umbracoApi.documentType.createEmptyElementType(firstElementTypeName); + const rowSpan = 4; + blockGridDataTypeId = await umbracoApi.dataType.createBlockGridWithAnAreaInABlockWithColumnSpanAndRowSpan(blockGridDataTypeName, firstElementTypeId, 12, rowSpan, firstAreaName, areaCreateLabel); + documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, blockGridDataTypeName, blockGridDataTypeId); + await umbracoApi.document.createDefaultDocument(contentName, documentTypeId); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + await umbracoUi.content.goToContentWithName(contentName); + + // Act + await umbracoUi.content.clickAddBlockGridElementWithName(firstElementTypeName); + await umbracoUi.content.clickSelectBlockElementWithName(firstElementTypeName); + await umbracoUi.content.clickSaveAndPublishButton(); + + // Assert + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.published); + await umbracoUi.content.doesBlockAreaContainRowSpan(firstElementTypeName, firstAreaName, rowSpan, 0); +}); + +// Remove fixme when this issue is fixed https://github.com/umbraco/Umbraco-CMS/issues/18639 +test.fixme('can create content with block grid area with min allowed', async ({umbracoApi, umbracoUi}) => { + // Arrange + firstElementTypeId = await umbracoApi.documentType.createEmptyElementType(firstElementTypeName); + const secondElementTypeId = await umbracoApi.documentType.createEmptyElementType(secondElementTypeName); + const minAllowed = 2; + blockGridDataTypeId = await umbracoApi.dataType.createBlockGridWithAnAreaInABlockWithMinAndMaxAllowed(blockGridDataTypeName, firstElementTypeId, secondElementTypeId, minAllowed, 10, firstAreaName, areaCreateLabel); + documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, blockGridDataTypeName, blockGridDataTypeId); + await umbracoApi.document.createDefaultDocument(contentName, documentTypeId); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + await umbracoUi.content.goToContentWithName(contentName); + + // Act + await umbracoUi.content.clickAddBlockGridElementWithName(firstElementTypeName); + await umbracoUi.content.clickSelectBlockElementWithName(firstElementTypeName); + await umbracoUi.content.clickLinkWithName(areaCreateLabel); + await umbracoUi.content.clickSelectBlockElementInAreaWithName(secondElementTypeName); + await umbracoUi.content.isTextWithExactNameVisible('Minimum 2 entries, requires 1 more.'); + await umbracoUi.content.clickSaveAndPublishButton(); + await umbracoUi.content.doesErrorNotificationHaveText(NotificationConstantHelper.error.documentCouldNotBePublished); + await umbracoUi.content.clickInlineAddToAreaButton(firstElementTypeName, firstAreaName, 0, 1); + await umbracoUi.content.clickSelectBlockElementInAreaWithName(secondElementTypeName); + await umbracoUi.content.clickSaveAndPublishButton(); + + // Assert + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.published); + + // Clean + await umbracoApi.documentType.ensureNameNotExists(secondElementTypeName); +}); + +// Remove fixme when this issue is fixed https://github.com/umbraco/Umbraco-CMS/issues/18639 +test.fixme('can create content with block grid area with max allowed', async ({umbracoApi, umbracoUi}) => { + // Arrange + firstElementTypeId = await umbracoApi.documentType.createEmptyElementType(firstElementTypeName); + const secondElementTypeId = await umbracoApi.documentType.createEmptyElementType(secondElementTypeName); + const maxAllowed = 0; + blockGridDataTypeId = await umbracoApi.dataType.createBlockGridWithAnAreaInABlockWithMinAndMaxAllowed(blockGridDataTypeName, firstElementTypeId, secondElementTypeId, 0, maxAllowed, firstAreaName, areaCreateLabel); + documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, blockGridDataTypeName, blockGridDataTypeId); + await umbracoApi.document.createDefaultDocument(contentName, documentTypeId); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + await umbracoUi.content.goToContentWithName(contentName); + + // Act + await umbracoUi.content.clickAddBlockGridElementWithName(firstElementTypeName); + await umbracoUi.content.clickSelectBlockElementWithName(firstElementTypeName); + await umbracoUi.content.clickLinkWithName(areaCreateLabel); + await umbracoUi.content.clickSelectBlockElementInAreaWithName(secondElementTypeName); + await umbracoUi.content.isTextWithExactNameVisible('Maximum 0 entries, 1 too many.'); + await umbracoUi.content.clickSaveAndPublishButton(); + await umbracoUi.content.doesErrorNotificationHaveText(NotificationConstantHelper.error.documentCouldNotBePublished); + await umbracoUi.content.removeBlockFromArea(firstElementTypeName, firstAreaName, secondElementTypeName); + await umbracoUi.content.clickConfirmToDeleteButton(); + await umbracoUi.content.clickSaveAndPublishButton(); + + // Assert + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.published); + + // Clean + await umbracoApi.documentType.ensureNameNotExists(secondElementTypeName); +}); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithBlockGrid.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/BlockGrid/ContentWithBlockGrid.spec.ts similarity index 100% rename from tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithBlockGrid.spec.ts rename to tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/BlockGrid/ContentWithBlockGrid.spec.ts diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ChildrenContent.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ChildrenContent.spec.ts index f36fb09883a5..78cc3db04d2d 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ChildrenContent.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ChildrenContent.spec.ts @@ -43,7 +43,7 @@ test('can create child node', {tag: '@smoke'}, async ({umbracoApi, umbracoUi}) = expect(childData[0].variants[0].name).toBe(childContentName); // verify that the child content displays in the tree after reloading children await umbracoUi.content.clickActionsMenuForContent(contentName); - await umbracoUi.content.clickReloadButton(); + await umbracoUi.content.clickReloadChildrenButton(); await umbracoUi.content.clickCaretButtonForContentName(contentName); await umbracoUi.content.doesContentTreeHaveName(childContentName); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithCustomDataType.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithCustomDataType.spec.ts index c13538b73f3a..d605cb1936c8 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithCustomDataType.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithCustomDataType.spec.ts @@ -244,8 +244,8 @@ test('can add string to the multiple text string in the content section', async test('can create content with the custom data type with slider property editor', async ({umbracoApi, umbracoUi}) => { // Arrange - customDataTypeName = 'Slider'; - const customDataTypeId = await umbracoApi.dataType.createSliderDataTyper(customDataTypeName); + customDataTypeName = 'Custom Slider'; + const customDataTypeId = await umbracoApi.dataType.createSliderDataType(customDataTypeName); const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, customDataTypeName, customDataTypeId); await umbracoApi.document.createDefaultDocument(contentName, documentTypeId); await umbracoUi.goToBackOffice(); @@ -273,7 +273,7 @@ test('can change slider value in the content section', async ({umbracoApi, umbra "from": sliderValue, "to": sliderValue } - const customDataTypeId = await umbracoApi.dataType.createSliderDataTyper(customDataTypeName); + const customDataTypeId = await umbracoApi.dataType.createSliderDataType(customDataTypeName); const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, customDataTypeName, customDataTypeId); await umbracoApi.document.createDefaultDocument(contentName, documentTypeId); await umbracoUi.goToBackOffice(); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithDocumentTypeProperties/ContentWithAllowedChildNodes.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithDocumentTypeProperties/ContentWithAllowedChildNodes.spec.ts index 766b26cdf185..d567a0984f5e 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithDocumentTypeProperties/ContentWithAllowedChildNodes.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithDocumentTypeProperties/ContentWithAllowedChildNodes.spec.ts @@ -83,7 +83,7 @@ test('can create multiple child nodes with different document types', async ({um expect(childData[1].variants[0].name).toBe(secondChildContentName); // verify that the child content displays in the tree after reloading children await umbracoUi.content.clickActionsMenuForContent(contentName); - await umbracoUi.content.clickReloadButton(); + await umbracoUi.content.clickReloadChildrenButton(); await umbracoUi.content.clickCaretButtonForContentName(contentName); await umbracoUi.content.doesContentTreeHaveName(firstChildContentName); await umbracoUi.content.doesContentTreeHaveName(secondChildContentName); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithDocumentTypeProperties/ContentWithCollections.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithDocumentTypeProperties/ContentWithCollections.spec.ts index 17f17d6dab1a..98868fc1c17b 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithDocumentTypeProperties/ContentWithCollections.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithDocumentTypeProperties/ContentWithCollections.spec.ts @@ -62,7 +62,7 @@ test('can create child content in a collection', async ({umbracoApi, umbracoUi}) expect(childData[0].variants[0].name).toBe(firstChildContentName); // verify that the child content displays in collection list after reloading tree await umbracoUi.content.clickActionsMenuForContent(contentName); - await umbracoUi.content.clickReloadButton(); + await umbracoUi.content.clickReloadChildrenButton(); await umbracoUi.content.goToContentWithName(contentName); await umbracoUi.content.doesDocumentTableColumnNameValuesMatch(expectedNames); @@ -95,7 +95,7 @@ test('can create multiple child nodes in a collection', async ({umbracoApi, umbr expect(childData[1].variants[0].name).toBe(secondChildContentName); // verify that the child content displays in collection list after reloading tree await umbracoUi.content.clickActionsMenuForContent(contentName); - await umbracoUi.content.clickReloadButton(); + await umbracoUi.content.clickReloadChildrenButton(); await umbracoUi.content.goToContentWithName(contentName); await umbracoUi.content.doesDocumentTableColumnNameValuesMatch(expectedNames); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithListViewContent.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithListViewContent.spec.ts index 06e720c62d24..b86bc575b2b3 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithListViewContent.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithListViewContent.spec.ts @@ -22,7 +22,8 @@ test.afterEach(async ({umbracoApi}) => { await umbracoApi.documentType.ensureNameNotExists(childDocumentTypeName); }); -test('can create content with the list view data type', async ({umbracoApi, umbracoUi}) => { +// Remove .fixme when the issue is fixed: https://github.com/umbraco/Umbraco-CMS/issues/18615 +test.fixme('can create content with the list view data type', async ({umbracoApi, umbracoUi}) => { // Arrange const expectedState = 'Draft'; const defaultListViewDataTypeName = 'List View - Content'; @@ -40,6 +41,7 @@ test('can create content with the list view data type', async ({umbracoApi, umbr // Assert await umbracoUi.content.isSuccessNotificationVisible(); + await umbracoUi.content.isErrorNotificationVisible(false); expect(await umbracoApi.document.doesNameExist(contentName)).toBeTruthy(); const contentData = await umbracoApi.document.getByName(contentName); expect(contentData.variants[0].state).toBe(expectedState); @@ -204,8 +206,7 @@ test('can publish child content from list', async ({umbracoApi, umbracoUi}) => { const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithAPropertyEditorAndAnAllowedChildNode(documentTypeName, dataTypeName, dataTypeData.id, childDocumentTypeId); const documentId = await umbracoApi.document.createDefaultDocument(contentName, documentTypeId); await umbracoApi.document.createDefaultDocumentWithParent(childContentName, childDocumentTypeId, documentId); - const publishData = {"publishSchedules": [{"culture": null}]}; - await umbracoApi.document.publish(documentId, publishData); + await umbracoApi.document.publish(documentId); await umbracoUi.content.goToSection(ConstantHelper.sections.content); await umbracoUi.content.goToContentWithName(contentName); @@ -251,9 +252,8 @@ test('can unpublish child content from list', async ({umbracoApi, umbracoUi}) => const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithAPropertyEditorAndAnAllowedChildNode(documentTypeName, dataTypeName, dataTypeData.id, childDocumentTypeId); const documentId = await umbracoApi.document.createDefaultDocument(contentName, documentTypeId); const childDocumentId = await umbracoApi.document.createDefaultDocumentWithParent(childContentName, childDocumentTypeId, documentId); - const publishData = {"publishSchedules": [{"culture": null}]}; - await umbracoApi.document.publish(documentId, publishData); - await umbracoApi.document.publish(childDocumentId, publishData); + await umbracoApi.document.publish(documentId); + await umbracoApi.document.publish(childDocumentId); const childContentDataBeforeUnpublished = await umbracoApi.document.getByName(childContentName); expect(childContentDataBeforeUnpublished.variants[0].state).toBe('Published'); await umbracoUi.content.goToSection(ConstantHelper.sections.content); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithTiptap.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithTiptap.spec.ts index b1097e3c2ece..cbac5c752e38 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithTiptap.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithTiptap.spec.ts @@ -86,9 +86,9 @@ test('can publish content with RTE Tiptap property editor', async ({umbracoApi, expect(contentData.values[0].value.markup).toEqual('<p>' + inputText + '</p>'); }); -test('can add a media in RTE Tiptap property editor', async ({umbracoApi, umbracoUi}) => { +test.fixme('can add a media in RTE Tiptap property editor', async ({umbracoApi, umbracoUi}) => { // Arrange - const iconTitle = 'Media picker'; + const iconTitle = 'Media Picker'; const imageName = 'Test Image For Content'; await umbracoApi.media.ensureNameNotExists(imageName); await umbracoApi.media.createDefaultMediaWithImage(imageName); @@ -100,6 +100,7 @@ test('can add a media in RTE Tiptap property editor', async ({umbracoApi, umbrac // Act await umbracoUi.content.goToContentWithName(contentName); await umbracoUi.content.clickTipTapToolbarIconWithTitle(iconTitle); + // fix this await umbracoUi.content.selectMediaWithName(imageName); await umbracoUi.content.clickChooseModalButton(); await umbracoUi.content.clickMediaCaptionAltTextModalSubmitButton(); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithTrueFalse.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithTrueFalse.spec.ts index 479176b40166..e015ea61151d 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithTrueFalse.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithTrueFalse.spec.ts @@ -84,13 +84,15 @@ test('can toggle the true/false value in the content ', async ({umbracoApi, umbr test('can toggle the true/false value with the initial state enabled', async ({umbracoApi, umbracoUi}) => { // Arrange const dataTypeId = await umbracoApi.dataType.createTrueFalseDataTypeWithInitialState(customDataTypeName); - const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, customDataTypeName, dataTypeId); - await umbracoApi.document.createDefaultDocument(contentName, documentTypeId); + await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, customDataTypeName, dataTypeId); await umbracoUi.goToBackOffice(); await umbracoUi.content.goToSection(ConstantHelper.sections.content); // Act - await umbracoUi.content.goToContentWithName(contentName); + await umbracoUi.content.clickActionsMenuAtRoot(); + await umbracoUi.content.clickCreateButton(); + await umbracoUi.content.chooseDocumentType(documentTypeName); + await umbracoUi.content.enterContentName(contentName); await umbracoUi.content.clickToggleButton(); await umbracoUi.content.clickSaveButton(); @@ -100,6 +102,9 @@ test('can toggle the true/false value with the initial state enabled', async ({u const contentData = await umbracoApi.document.getByName(contentName); expect(contentData.values[0].alias).toEqual(AliasHelper.toAlias(customDataTypeName)); expect(contentData.values[0].value).toEqual(false); + + // Clean + await umbracoApi.dataType.ensureNameNotExists(customDataTypeName); }); test('can show the label on for the true/false in the content ', async ({umbracoApi, umbracoUi}) => { diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/IssueWithScheduledPublishing.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/IssueWithScheduledPublishing.spec.ts new file mode 100644 index 000000000000..c2845760e586 --- /dev/null +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/IssueWithScheduledPublishing.spec.ts @@ -0,0 +1,42 @@ +import {ConstantHelper, test} from '@umbraco/playwright-testhelpers'; + +const documentTypeName = "DocumentType"; +const contentName = "Content"; +const languageName = 'Danish'; +let documentTypeId = null; + +test.beforeEach(async ({umbracoApi}) => { + await umbracoApi.documentType.ensureNameNotExists(documentTypeName); + documentTypeId = await umbracoApi.documentType.createDocumentTypeWithAllowVaryByCulture(documentTypeName); + await umbracoApi.document.createDefaultDocumentWithEnglishCulture(contentName, documentTypeId); + await umbracoApi.language.ensureNameNotExists(languageName); + await umbracoApi.language.createDanishLanguage(); +}); + +test.afterEach(async ({umbracoApi}) => { + await umbracoApi.documentType.ensureNameNotExists(documentTypeName); + await umbracoApi.language.ensureNameNotExists(languageName); +}); + +// https://github.com/umbraco/Umbraco-CMS/issues/18555 +test.skip('Can schedule publish after unselecting all languages', async ({umbracoUi}) => { + // Arrange + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + + // Act + // Open schedule modal and click schedule + await umbracoUi.content.changeDocumentSectionLanguage(languageName); + await umbracoUi.content.goToContentWithName('(' + contentName + ')'); + await umbracoUi.content.enterContentName('Tester'); + await umbracoUi.content.clickViewMoreOptionsButton(); + await umbracoUi.content.clickScheduleButton(); + await umbracoUi.waitForTimeout(500); + await umbracoUi.content.clickSelectAllCheckbox(); + await umbracoUi.waitForTimeout(500); + await umbracoUi.content.clickSelectAllCheckbox(); + await umbracoUi.content.clickButtonWithName(contentName); + + // Assert + await umbracoUi.content.doesSchedulePublishModalButtonContainDisabledTag(false); +}); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/TrashContent.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/TrashContent.spec.ts new file mode 100644 index 000000000000..c008ea38ba50 --- /dev/null +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/TrashContent.spec.ts @@ -0,0 +1,257 @@ +import {ConstantHelper, NotificationConstantHelper, test} from '@umbraco/playwright-testhelpers'; +import {expect} from "@playwright/test"; + +let dataTypeId = ''; +const contentName = 'TestContent'; +const documentTypeName = 'TestDocumentTypeForContent'; +const dataTypeName = 'Textstring'; +const contentText = 'This is test content text'; +const referenceHeadline = 'The following items depend on this'; +const documentPickerName = ['TestPicker', 'DocumentTypeForPicker']; + +test.beforeEach(async ({umbracoApi}) => { + const dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); + dataTypeId = dataTypeData.id; + await umbracoApi.documentType.ensureNameNotExists(documentTypeName); +}); + +test.afterEach(async ({umbracoApi}) => { + await umbracoApi.document.ensureNameNotExists(contentName); + await umbracoApi.documentType.ensureNameNotExists(documentTypeName); + await umbracoApi.document.emptyRecycleBin(); + await umbracoApi.documentType.ensureNameNotExists(documentPickerName[1]); +}); + +test('can trash an invariant content node', {tag: '@smoke'}, async ({umbracoApi, umbracoUi}) => { + // Arrange + const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, dataTypeName, dataTypeId); + await umbracoApi.document.createDocumentWithTextContent(contentName, documentTypeId, contentText, dataTypeName); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + + // Act + await umbracoUi.content.clickActionsMenuForContent(contentName); + await umbracoUi.content.clickTrashButton(); + // Verify the references list not displayed + await umbracoUi.content.isReferenceHeadlineVisible(false); + await umbracoUi.content.clickConfirmTrashButton(); + + // Assert + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.movedToRecycleBin); + expect(await umbracoApi.document.doesNameExist(contentName)).toBeFalsy(); + await umbracoUi.content.isItemVisibleInRecycleBin(contentName); + expect(await umbracoApi.document.doesItemExistInRecycleBin(contentName)).toBeTruthy(); +}); + +test('can trash a variant content node', async ({umbracoApi, umbracoUi}) => { + // Arrange + const documentTypeId = await umbracoApi.documentType.createVariantDocumentTypeWithInvariantPropertyEditor(documentTypeName, dataTypeName, dataTypeId); + await umbracoApi.document.createDocumentWithEnglishCultureAndTextContent(contentName, documentTypeId, contentText, dataTypeName); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + + // Act + await umbracoUi.content.clickActionsMenuForContent(contentName); + await umbracoUi.content.clickTrashButton(); + // Verify the references list not displayed + await umbracoUi.content.isReferenceHeadlineVisible(false); + await umbracoUi.content.clickConfirmTrashButton(); + + // Assert + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.movedToRecycleBin); + expect(await umbracoApi.document.doesNameExist(contentName)).toBeFalsy(); + await umbracoUi.content.isItemVisibleInRecycleBin(contentName); + expect(await umbracoApi.document.doesItemExistInRecycleBin(contentName)).toBeTruthy(); +}); + +test('can trash a published content node', async ({umbracoApi, umbracoUi}) => { + // Arrange + const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, dataTypeName, dataTypeId); + const contentId = await umbracoApi.document.createDocumentWithTextContent(contentName, documentTypeId, contentText, dataTypeName); + await umbracoApi.document.publish(contentId); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + + // Act + await umbracoUi.content.clickActionsMenuForContent(contentName); + await umbracoUi.content.clickTrashButton(); + // Verify the references list not displayed + await umbracoUi.content.isReferenceHeadlineVisible(false); + await umbracoUi.content.clickConfirmTrashButton(); + + // Assert + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.movedToRecycleBin); + expect(await umbracoApi.document.doesNameExist(contentName)).toBeFalsy(); + await umbracoUi.content.isItemVisibleInRecycleBin(contentName); + expect(await umbracoApi.document.doesItemExistInRecycleBin(contentName)).toBeTruthy(); +}); + +test('can trash an invariant content node that references one item', async ({umbracoApi, umbracoUi}) => { + // Arrange + // Create an invariant published content node + const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, dataTypeName, dataTypeId); + const contentId = await umbracoApi.document.createDocumentWithTextContent(contentName, documentTypeId, contentText, dataTypeName); + await umbracoApi.document.publish(contentId); + // Create a document link picker + await umbracoApi.document.createDefaultDocumentWithOneDocumentLink(documentPickerName[0], contentName, contentId, documentPickerName[1]); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + + // Act + await umbracoUi.content.clickActionsMenuForContent(contentName); + await umbracoUi.content.clickTrashButton(); + // Verify the references list + await umbracoUi.content.doesReferenceHeadlineHaveText(referenceHeadline); + await umbracoUi.content.doesReferenceItemsHaveCount(1); + await umbracoUi.content.isReferenceItemNameVisible(documentPickerName[0]); + await umbracoUi.content.clickConfirmTrashButton(); + + // Assert + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.movedToRecycleBin); + expect(await umbracoApi.document.doesNameExist(contentName)).toBeFalsy(); + await umbracoUi.content.isItemVisibleInRecycleBin(contentName); + expect(await umbracoApi.document.doesItemExistInRecycleBin(contentName)).toBeTruthy(); +}); + +test('can trash a variant content node that references one item', async ({umbracoApi, umbracoUi}) => { + // Arrange + // Create a variant published content node + const documentTypeId = await umbracoApi.documentType.createVariantDocumentTypeWithInvariantPropertyEditor(documentTypeName, dataTypeName, dataTypeId); + const contentId = await umbracoApi.document.createDocumentWithEnglishCultureAndTextContent(contentName, documentTypeId, contentText, dataTypeName); + await umbracoApi.document.publishDocumentWithCulture(contentId, 'en-US'); + // Create a document link picker + await umbracoApi.document.createDefaultDocumentWithOneDocumentLink(documentPickerName[0], contentName, contentId, documentPickerName[1]); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + + // Act + await umbracoUi.content.clickActionsMenuForContent(contentName); + await umbracoUi.content.clickTrashButton(); + // Verify the references list + await umbracoUi.content.doesReferenceHeadlineHaveText(referenceHeadline); + await umbracoUi.content.doesReferenceItemsHaveCount(1); + await umbracoUi.content.isReferenceItemNameVisible(documentPickerName[0]); + await umbracoUi.content.clickConfirmTrashButton(); + + // Assert + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.movedToRecycleBin); + expect(await umbracoApi.document.doesNameExist(contentName)).toBeFalsy(); + await umbracoUi.content.isItemVisibleInRecycleBin(contentName); + expect(await umbracoApi.document.doesItemExistInRecycleBin(contentName)).toBeTruthy(); +}); + +test('can trash an invariant content node that references more than 3 items', async ({umbracoApi, umbracoUi}) => { + // Arrange + const documentPickerName2 = ['TestPicker2', 'DocumentTypeForPicker2']; + const documentPickerName3 = ['TestPicker3', 'DocumentTypeForPicker3']; + const documentPickerName4 = ['TestPicker4', 'DocumentTypeForPicker4']; + // Create an invariant published content node + const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, dataTypeName, dataTypeId); + const contentId = await umbracoApi.document.createDocumentWithTextContent(contentName, documentTypeId, contentText, dataTypeName); + await umbracoApi.document.publish(contentId); + // Create 4 document link pickers + await umbracoApi.document.createDefaultDocumentWithOneDocumentLink(documentPickerName[0], contentName, contentId, documentPickerName[1]); + await umbracoApi.document.createDefaultDocumentWithOneDocumentLink(documentPickerName2[0], contentName, contentId, documentPickerName2[1]); + await umbracoApi.document.createDefaultDocumentWithOneDocumentLink(documentPickerName3[0], contentName, contentId, documentPickerName3[1]); + await umbracoApi.document.createDefaultDocumentWithOneDocumentLink(documentPickerName4[0], contentName, contentId, documentPickerName4[1]); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + + // Act + await umbracoUi.content.clickActionsMenuForContent(contentName); + await umbracoUi.content.clickTrashButton(); + // Verify the references list has 3 items and has the text '...and one more item' + await umbracoUi.content.doesReferenceHeadlineHaveText(referenceHeadline); + await umbracoUi.content.doesReferenceItemsHaveCount(3); + await umbracoUi.content.isReferenceItemNameVisible(documentPickerName[0]); + await umbracoUi.content.isReferenceItemNameVisible(documentPickerName2[0]); + await umbracoUi.content.isReferenceItemNameVisible(documentPickerName3[0]); + await umbracoUi.content.doesReferencesContainText('...and one more item'); + await umbracoUi.content.clickConfirmTrashButton(); + + // Assert + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.movedToRecycleBin); + expect(await umbracoApi.document.doesNameExist(contentName)).toBeFalsy(); + await umbracoUi.content.isItemVisibleInRecycleBin(contentName); + expect(await umbracoApi.document.doesItemExistInRecycleBin(contentName)).toBeTruthy(); + + // Clean + await umbracoApi.documentType.ensureNameNotExists(documentPickerName2[1]); + await umbracoApi.documentType.ensureNameNotExists(documentPickerName3[1]); + await umbracoApi.documentType.ensureNameNotExists(documentPickerName4[1]); +}); + +test('can trash a variant content node that references more than 3 items', async ({umbracoApi, umbracoUi}) => { + // Arrange + const documentPickerName2 = ['TestPicker2', 'DocumentTypeForPicker2']; + const documentPickerName3 = ['TestPicker3', 'DocumentTypeForPicker3']; + const documentPickerName4 = ['TestPicker4', 'DocumentTypeForPicker4']; + // Create a variant published content node + const documentTypeId = await umbracoApi.documentType.createVariantDocumentTypeWithInvariantPropertyEditor(documentTypeName, dataTypeName, dataTypeId); + const contentId = await umbracoApi.document.createDocumentWithEnglishCultureAndTextContent(contentName, documentTypeId, contentText, dataTypeName); + await umbracoApi.document.publishDocumentWithCulture(contentId, 'en-US'); + // Create 4 document link pickers + await umbracoApi.document.createDefaultDocumentWithOneDocumentLink(documentPickerName[0], contentName, contentId, documentPickerName[1]); + await umbracoApi.document.createDefaultDocumentWithOneDocumentLink(documentPickerName2[0], contentName, contentId, documentPickerName2[1]); + await umbracoApi.document.createDefaultDocumentWithOneDocumentLink(documentPickerName3[0], contentName, contentId, documentPickerName3[1]); + await umbracoApi.document.createDefaultDocumentWithOneDocumentLink(documentPickerName4[0], contentName, contentId, documentPickerName4[1]); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + + // Act + await umbracoUi.content.clickActionsMenuForContent(contentName); + await umbracoUi.content.clickTrashButton(); + // Verify the references list has 3 items and has the text '...and one more item' + await umbracoUi.content.doesReferenceHeadlineHaveText(referenceHeadline); + await umbracoUi.content.doesReferenceItemsHaveCount(3); + await umbracoUi.content.isReferenceItemNameVisible(documentPickerName[0]); + await umbracoUi.content.isReferenceItemNameVisible(documentPickerName2[0]); + await umbracoUi.content.isReferenceItemNameVisible(documentPickerName3[0]); + await umbracoUi.content.doesReferencesContainText('...and one more item'); + await umbracoUi.content.clickConfirmTrashButton(); + + // Assert + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.movedToRecycleBin); + expect(await umbracoApi.document.doesNameExist(contentName)).toBeFalsy(); + await umbracoUi.content.isItemVisibleInRecycleBin(contentName); + expect(await umbracoApi.document.doesItemExistInRecycleBin(contentName)).toBeTruthy(); + + // Clean + await umbracoApi.documentType.ensureNameNotExists(documentPickerName2[1]); + await umbracoApi.documentType.ensureNameNotExists(documentPickerName3[1]); + await umbracoApi.documentType.ensureNameNotExists(documentPickerName4[1]); +}); + +test('can trash a content node with multiple cultures that references one item', async ({umbracoApi, umbracoUi}) => { + // Arrange + const firstCulture = 'en-US'; + const secondCulture = 'da'; + await umbracoApi.language.createDanishLanguage(); + // Create a content node with multiple cultures + const documentTypeId = await umbracoApi.documentType.createVariantDocumentTypeWithInvariantPropertyEditor(documentTypeName, dataTypeName, dataTypeId); + const contentId = await umbracoApi.document.createDocumentWithTwoCulturesAndTextContent(contentName, documentTypeId, contentText, dataTypeName, firstCulture, secondCulture); + await umbracoApi.document.publishDocumentWithCulture(contentId, firstCulture); + await umbracoApi.document.publishDocumentWithCulture(contentId, secondCulture); + // Create a document link picker + await umbracoApi.document.createDefaultDocumentWithOneDocumentLink(documentPickerName[0], contentName, contentId, documentPickerName[1]); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + + // Act + await umbracoUi.content.clickActionsMenuForContent(contentName); + await umbracoUi.content.clickTrashButton(); + // Verify the references list + await umbracoUi.content.doesReferenceHeadlineHaveText(referenceHeadline); + await umbracoUi.content.doesReferenceItemsHaveCount(1); + await umbracoUi.content.isReferenceItemNameVisible(documentPickerName[0]); + await umbracoUi.content.clickConfirmTrashButton(); + + // Assert + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.movedToRecycleBin); + expect(await umbracoApi.document.doesNameExist(contentName)).toBeFalsy(); + await umbracoUi.content.isItemVisibleInRecycleBin(contentName); + expect(await umbracoApi.document.doesItemExistInRecycleBin(contentName)).toBeTruthy(); + + // Clean + await umbracoApi.language.ensureIsoCodeNotExists(secondCulture); +}); \ No newline at end of file diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/ApprovedColor.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/ApprovedColor.spec.ts index 61b8fbf225fb..e853b2afa895 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/ApprovedColor.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/ApprovedColor.spec.ts @@ -34,7 +34,7 @@ test('can include label', async ({umbracoApi, umbracoUi}) => { await umbracoUi.dataType.goToDataType(dataTypeName); // Act - await umbracoUi.dataType.clickIncludeLabelsSlider(); + await umbracoUi.dataType.clickIncludeLabelsToggle(); await umbracoUi.dataType.clickSaveButton(); // Assert diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/ContentPicker.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/ContentPicker.spec.ts index 9cf26d793cbe..8330c7136a73 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/ContentPicker.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/ContentPicker.spec.ts @@ -26,7 +26,7 @@ test('can show open button', async ({umbracoApi, umbracoUi}) => { await umbracoUi.dataType.goToDataType(dataTypeName); // Act - await umbracoUi.dataType.clickShowOpenButtonSlider(); + await umbracoUi.dataType.clickShowOpenButtonToggle(); await umbracoUi.dataType.clickSaveButton(); // Assert @@ -43,7 +43,7 @@ test('can ignore user start nodes', async ({umbracoApi, umbracoUi}) => { await umbracoUi.dataType.goToDataType(dataTypeName); // Act - await umbracoUi.dataType.clickIgnoreUserStartNodesSlider(); + await umbracoUi.dataType.clickIgnoreUserStartNodesToggle(); await umbracoUi.dataType.clickSaveButton(); // Assert diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/DataTypeFolder.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/DataTypeFolder.spec.ts index 1454cb6f3438..ba07bf81236b 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/DataTypeFolder.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/DataTypeFolder.spec.ts @@ -37,9 +37,9 @@ test('can rename a data type folder', async ({umbracoApi, umbracoUi}) => { // Act await umbracoUi.dataType.clickRootFolderCaretButton(); await umbracoUi.dataType.clickActionsMenuForDataType(wrongDataTypeFolderName); - await umbracoUi.dataType.clickRenameFolderThreeDotsButton(); + await umbracoUi.dataType.clickRenameFolderButton(); await umbracoUi.dataType.enterFolderName(dataTypeFolderName); - await umbracoUi.dataType.clickConfirmRenameFolderButton(); + await umbracoUi.dataType.clickConfirmRenameButton(); // Assert await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/DatePicker.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/DatePicker.spec.ts index 1eb2d0a51267..3e71aa59c954 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/DatePicker.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/DatePicker.spec.ts @@ -47,7 +47,7 @@ for (const datePickerType of datePickerTypes) { ]; // Act - await umbracoUi.dataType.clickOffsetTimeSlider(); + await umbracoUi.dataType.clickOffsetTimeToggle(); await umbracoUi.dataType.clickSaveButton(); // Assert diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/Dropdown.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/Dropdown.spec.ts index 2c63e953b751..5e7f3586f785 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/Dropdown.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/Dropdown.spec.ts @@ -2,7 +2,7 @@ import {expect} from "@playwright/test"; const dataTypeName = 'Dropdown'; -let dataTypeDefaultData = null; +let dataTypeDefaultData = null; let dataTypeData = null; test.beforeEach(async ({umbracoUi, umbracoApi}) => { @@ -13,8 +13,8 @@ test.beforeEach(async ({umbracoUi, umbracoApi}) => { test.afterEach(async ({umbracoApi}) => { if (dataTypeDefaultData !== null) { - await umbracoApi.dataType.update(dataTypeDefaultData.id, dataTypeDefaultData); - } + await umbracoApi.dataType.update(dataTypeDefaultData.id, dataTypeDefaultData); + } }); test('can enable multiple choice', async ({umbracoApi, umbracoUi}) => { @@ -26,11 +26,11 @@ test('can enable multiple choice', async ({umbracoApi, umbracoUi}) => { // Remove all existing options dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); dataTypeData.values = []; - await umbracoApi.dataType.update(dataTypeData.id, dataTypeData); + await umbracoApi.dataType.update(dataTypeData.id, dataTypeData); await umbracoUi.dataType.goToDataType(dataTypeName); // Act - await umbracoUi.dataType.clickEnableMultipleChoiceSlider(); + await umbracoUi.dataType.clickEnableMultipleChoiceToggle(); await umbracoUi.dataType.clickSaveButton(); // Assert @@ -50,7 +50,7 @@ test('can add option', async ({umbracoApi, umbracoUi}) => { // Remove all existing options dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); dataTypeData.values = []; - await umbracoApi.dataType.update(dataTypeData.id, dataTypeData); + await umbracoApi.dataType.update(dataTypeData.id, dataTypeData); await umbracoUi.dataType.goToDataType(dataTypeName); // Act @@ -75,7 +75,7 @@ test('can remove option', async ({umbracoApi, umbracoUi}) => { // Remove all existing options and add an option to remove dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); dataTypeData.values = removedOptionValues; - await umbracoApi.dataType.update(dataTypeData.id, dataTypeData); + await umbracoApi.dataType.update(dataTypeData.id, dataTypeData); await umbracoUi.dataType.goToDataType(dataTypeName); // Act diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/ListView.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/ListView.spec.ts index 6b0c4e2edd39..41d7af774e9c 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/ListView.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/ListView.spec.ts @@ -208,7 +208,7 @@ for (const listViewType of listViewTypes) { // Act await umbracoUi.dataType.goToDataType(listViewType); - await umbracoUi.dataType.clickBulkActionPermissionsSliderByValue(bulkActionPermissionValue); + await umbracoUi.dataType.clickBulkActionPermissionsToggleByValue(bulkActionPermissionValue); await umbracoUi.dataType.clickSaveButton(); // Assert @@ -262,7 +262,7 @@ for (const listViewType of listViewTypes) { // Act await umbracoUi.dataType.goToDataType(listViewType); - await umbracoUi.dataType.clickShowContentWorkspaceViewFirstSlider(); + await umbracoUi.dataType.clickShowContentWorkspaceViewFirstToggle(); await umbracoUi.dataType.clickSaveButton(); // Assert @@ -280,7 +280,7 @@ for (const listViewType of listViewTypes) { // Act await umbracoUi.dataType.goToDataType(listViewType); - await umbracoUi.dataType.clickEditInInfiniteEditorSlider(); + await umbracoUi.dataType.clickEditInInfiniteEditorToggle(); await umbracoUi.dataType.clickSaveButton(); // Assert diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/MediaPicker.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/MediaPicker.spec.ts index e29b1d2e96e0..d4945b1cb1a5 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/MediaPicker.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/MediaPicker.spec.ts @@ -28,7 +28,7 @@ for (const dataTypeName of dataTypes) { // Act await umbracoUi.dataType.goToDataType(dataTypeName); - await umbracoUi.dataType.clickPickMultipleItemsSlider(); + await umbracoUi.dataType.clickPickMultipleItemsToggle(); await umbracoUi.dataType.clickSaveButton(); // Assert @@ -67,7 +67,7 @@ for (const dataTypeName of dataTypes) { // Act await umbracoUi.dataType.goToDataType(dataTypeName); - await umbracoUi.dataType.clickEnableFocalPointSlider(); + await umbracoUi.dataType.clickEnableFocalPointToggle(); await umbracoUi.dataType.clickSaveButton(); // Assert @@ -118,7 +118,7 @@ for (const dataTypeName of dataTypes) { // Act await umbracoUi.dataType.goToDataType(dataTypeName); - await umbracoUi.dataType.clickIgnoreUserStartNodesSlider(); + await umbracoUi.dataType.clickIgnoreUserStartNodesToggle(); await umbracoUi.dataType.clickSaveButton(); // Assert diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/MultiUrlPicker.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/MultiUrlPicker.spec.ts index 96ab56d73b60..1cb557aba1be 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/MultiUrlPicker.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/MultiUrlPicker.spec.ts @@ -14,8 +14,8 @@ test.beforeEach(async ({umbracoUi, umbracoApi}) => { test.afterEach(async ({umbracoApi}) => { if (dataTypeDefaultData !== null) { - await umbracoApi.dataType.update(dataTypeDefaultData.id, dataTypeDefaultData); - } + await umbracoApi.dataType.update(dataTypeDefaultData.id, dataTypeDefaultData); + } }); test('can update minimum number of items value', async ({umbracoApi, umbracoUi}) => { @@ -60,7 +60,7 @@ test('can enable ignore user start nodes', async ({umbracoApi, umbracoUi}) => { }; // Act - await umbracoUi.dataType.clickIgnoreUserStartNodesSlider(); + await umbracoUi.dataType.clickIgnoreUserStartNodesToggle(); await umbracoUi.dataType.clickSaveButton(); // Assert @@ -93,7 +93,7 @@ test('can update hide anchor/query string input', async ({umbracoApi, umbracoUi} }; // Act - await umbracoUi.dataType.clickHideAnchorQueryStringInputSlider(); + await umbracoUi.dataType.clickHideAnchorQueryStringInputToggle(); await umbracoUi.dataType.clickSaveButton(); // Assert diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/Numeric.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/Numeric.spec.ts index 77201cabd57b..2bd0da1404e2 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/Numeric.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/Numeric.spec.ts @@ -77,7 +77,7 @@ test.skip('can allow decimals', async ({umbracoApi, umbracoUi}) => { }; // Act - await umbracoUi.dataType.clickAllowDecimalsSlider(); + await umbracoUi.dataType.clickAllowDecimalsToggle(); await umbracoUi.dataType.clickSaveButton(); // Assert diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/TinyMCE.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/TinyMCE.spec.ts index edc15196e703..590df30a4427 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/TinyMCE.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/TinyMCE.spec.ts @@ -234,7 +234,7 @@ test('can enable hide label', async ({umbracoApi, umbracoUi}) => { await umbracoUi.dataType.goToDataType(tinyMCEName); // Act - await umbracoUi.dataType.clickHideLabelSlider(); + await umbracoUi.dataType.clickHideLabelToggle(); await umbracoUi.dataType.clickSaveButton(); // Assert @@ -278,7 +278,7 @@ test('can enable ignore user start nodes', async ({umbracoApi, umbracoUi}) => { await umbracoUi.dataType.goToDataType(tinyMCEName); // Act - await umbracoUi.dataType.clickIgnoreUserStartNodesSlider(); + await umbracoUi.dataType.clickIgnoreUserStartNodesToggle(); await umbracoUi.dataType.clickSaveButton(); // Assert diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/Tiptap.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/Tiptap.spec.ts index 2144d848d505..d999675f0ef8 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/Tiptap.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/Tiptap.spec.ts @@ -195,7 +195,7 @@ test('can enable ignore user start nodes', async ({umbracoApi, umbracoUi}) => { await umbracoUi.dataType.goToDataType(tipTapName); // Act - await umbracoUi.dataType.clickIgnoreUserStartNodesSlider(); + await umbracoUi.dataType.clickIgnoreUserStartNodesToggle(); await umbracoUi.dataType.clickSaveButton(); // Assert diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/TrueFalse.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/TrueFalse.spec.ts index b465fb6ef887..3c8abcf0f233 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/TrueFalse.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/TrueFalse.spec.ts @@ -26,7 +26,7 @@ test('can update preset value state', async ({umbracoApi, umbracoUi}) => { }; // Act - await umbracoUi.dataType.clickPresetValueSlider(); + await umbracoUi.dataType.clickPresetValueToggle(); await umbracoUi.dataType.clickSaveButton(); // Assert @@ -42,7 +42,7 @@ test('can update show toggle labels', async ({umbracoApi, umbracoUi}) => { }; // Act - await umbracoUi.dataType.clickShowToggleLabelsSlider(); + await umbracoUi.dataType.clickShowToggleLabelsToggle(); await umbracoUi.dataType.clickSaveButton(); // Assert diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Media/ListViewMedia.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Media/ListViewMedia.spec.ts index c55d98d24032..bf0af8415902 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Media/ListViewMedia.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Media/ListViewMedia.spec.ts @@ -1,5 +1,5 @@ import {expect} from '@playwright/test'; -import {ConstantHelper, test} from '@umbraco/playwright-testhelpers'; +import {ConstantHelper, NotificationConstantHelper, test} from '@umbraco/playwright-testhelpers'; const dataTypeName = 'List View - Media'; let dataTypeDefaultData = null; @@ -105,7 +105,7 @@ test('can allow bulk trash in the media section', async ({umbracoApi, umbracoUi} await umbracoUi.media.clickConfirmTrashButton(); // Assert - await umbracoUi.media.reloadMediaTree(); + await umbracoUi.media.isSuccessNotificationVisible(); expect(await umbracoApi.media.doesNameExist(firstMediaFileName)).toBeFalsy(); expect(await umbracoApi.media.doesNameExist(secondMediaFileName)).toBeFalsy(); expect(await umbracoApi.media.doesMediaItemExistInRecycleBin(firstMediaFileName)).toBeTruthy(); @@ -114,8 +114,8 @@ test('can allow bulk trash in the media section', async ({umbracoApi, umbracoUi} await umbracoUi.media.isItemVisibleInRecycleBin(secondMediaFileName, true, false); }); -// TODO: Remove skip when update code to select media successfully. -test.skip('can allow bulk move in the media section', async ({umbracoApi, umbracoUi}) => { +// TODO: Remove fixme when update code to select media successfully. +test.fixme('can allow bulk move in the media section', async ({umbracoApi, umbracoUi}) => { // Arrange const mediaFolderName = 'Test Folder Name'; await umbracoApi.media.ensureNameNotExists(mediaFolderName); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Media/Media.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Media/Media.spec.ts index ae338730d389..8e7f87816695 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Media/Media.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Media/Media.spec.ts @@ -39,7 +39,7 @@ test('can rename a media file', async ({umbracoApi, umbracoUi}) => { await umbracoUi.media.goToSection(ConstantHelper.sections.media); // Arrange - await umbracoUi.media.clickLabelWithName(wrongMediaFileName, true); + await umbracoUi.media.goToMediaWithName(wrongMediaFileName); await umbracoUi.media.enterMediaItemName(mediaFileName); await umbracoUi.media.clickSaveButton(); @@ -74,7 +74,6 @@ for (const mediaFileType of mediaFileTypes) { await umbracoUi.media.doesSuccessNotificationHaveText(NotificationConstantHelper.success.created); const mediaData = await umbracoApi.media.getByName(mediaFileType.fileName); await umbracoUi.media.doesMediaHaveThumbnail(mediaData.id, mediaFileType.thumbnail, mediaData.urls[0].url); - await umbracoUi.media.reloadMediaTree(); await umbracoUi.media.isMediaTreeItemVisible(mediaFileType.fileName); expect(await umbracoApi.media.doesNameExist(mediaFileType.fileName)).toBeTruthy(); @@ -200,7 +199,7 @@ test('can restore a media item from the recycle bin', async ({umbracoApi, umbrac // Assert await umbracoUi.media.doesSuccessNotificationHaveText(NotificationConstantHelper.success.restored); - await umbracoUi.media.isItemVisibleInRecycleBin(mediaFileName, false); + await umbracoUi.media.isItemVisibleInRecycleBin(mediaFileName, false, false); await umbracoUi.media.reloadMediaTree(); await umbracoUi.media.isMediaTreeItemVisible(mediaFileName); expect(await umbracoApi.media.doesNameExist(mediaFileName)).toBeTruthy(); @@ -223,7 +222,7 @@ test('can delete a media item from the recycle bin', async ({umbracoApi, umbraco // Assert await umbracoUi.media.doesSuccessNotificationHaveText(NotificationConstantHelper.success.deleted); - await umbracoUi.media.isItemVisibleInRecycleBin(mediaFileName, false); + await umbracoUi.media.isItemVisibleInRecycleBin(mediaFileName, false, false); expect(await umbracoApi.media.doesNameExist(mediaFileName)).toBeFalsy(); expect(await umbracoApi.media.doesMediaItemExistInRecycleBin(mediaFileName)).toBeFalsy(); }); @@ -241,7 +240,7 @@ test('can empty the recycle bin', async ({umbracoApi, umbracoUi}) => { await umbracoUi.media.clickConfirmEmptyRecycleBinButton(); // Assert - await umbracoUi.media.isItemVisibleInRecycleBin(mediaFileName, false); + await umbracoUi.media.isItemVisibleInRecycleBin(mediaFileName, false, false); await umbracoUi.media.doesSuccessNotificationHaveText(NotificationConstantHelper.success.emptiedRecycleBin); expect(await umbracoApi.media.doesNameExist(mediaFileName)).toBeFalsy(); expect(await umbracoApi.media.doesMediaItemExistInRecycleBin(mediaFileName)).toBeFalsy(); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Members/Members.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Members/Members.spec.ts index 61f30db52145..1555986b1cac 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Members/Members.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Members/Members.spec.ts @@ -192,7 +192,7 @@ test('can enable approved', async ({umbracoApi, umbracoUi}) => { // Act await umbracoUi.member.clickMemberLinkByName(memberName); - await umbracoUi.member.clickApprovedSlider(); + await umbracoUi.member.clickApprovedToggle(); await umbracoUi.member.clickSaveButton(); // Assert diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/RelationTypes/RelationTypes.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/RelationTypes/RelationTypes.spec.ts index 41e8b043eb3f..cd0fd93eb783 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/RelationTypes/RelationTypes.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/RelationTypes/RelationTypes.spec.ts @@ -73,7 +73,7 @@ test.skip('can update isDependency value of a relation type', async ({umbracoApi // Act await umbracoUi.relationType.openRelationTypeByNameAtRoot(relationTypeName); - await umbracoUi.relationType.clickIsDependencySlider(); + await umbracoUi.relationType.clickIsDependencyToggle(); await umbracoUi.relationType.clickSaveButton(); // Assert diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/RenderingContent/RenderingContentWithMultipleMediaPicker.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/RenderingContent/RenderingContentWithMultipleMediaPicker.spec.ts index 24776ed65b1b..0f0416066f8c 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/RenderingContent/RenderingContentWithMultipleMediaPicker.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/RenderingContent/RenderingContentWithMultipleMediaPicker.spec.ts @@ -41,7 +41,8 @@ test('can render content with multiple media picker value', async ({umbracoApi, await umbracoUi.contentRender.doesContentRenderValueContainText(secondMediaFileName); }); -test('can render content with multiple image media picker value', async ({umbracoApi, umbracoUi}) => { +// Remove .fixme when the issue is fixed: https://github.com/umbraco/Umbraco-CMS/issues/18531 +test.fixme('can render content with multiple image media picker value', async ({umbracoApi, umbracoUi}) => { // Arrange const dataTypeName = 'Multiple Image Media Picker'; const dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/Dashboard/Profiling.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/Dashboard/Profiling.spec.ts index 61b99355571d..a0d1c5e80d98 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/Dashboard/Profiling.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/Dashboard/Profiling.spec.ts @@ -8,9 +8,9 @@ test.beforeEach(async ({umbracoUi}) => { test('can update value of activate the profiler by default', async ({umbracoUi}) => { // Act - await umbracoUi.profiling.clickActivateProfilerByDefaultSlider(); + await umbracoUi.profiling.clickActivateProfilerByDefaultToggle(); await umbracoUi.reloadPage(); // Assert - await umbracoUi.profiling.isActivateProfilerByDefaultSliderChecked(true); + await umbracoUi.profiling.isActivateProfilerByDefaultToggleChecked(true); }); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/DocumentType/DocumentTypeDesignTab.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/DocumentType/DocumentTypeDesignTab.spec.ts index ff24ed2416ab..e04ed4262236 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/DocumentType/DocumentTypeDesignTab.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/DocumentType/DocumentTypeDesignTab.spec.ts @@ -360,7 +360,7 @@ test('can set is mandatory for a property in a document type', {tag: '@smoke'}, // Act await umbracoUi.documentType.goToDocumentType(documentTypeName); await umbracoUi.documentType.clickEditorSettingsButton(); - await umbracoUi.documentType.clickMandatorySlider(); + await umbracoUi.documentType.clickMandatoryToggle(); await umbracoUi.documentType.clickSubmitButton(); await umbracoUi.documentType.clickSaveButton(); @@ -403,7 +403,7 @@ test('can allow vary by culture for a property in a document type', {tag: '@smok // Act await umbracoUi.documentType.goToDocumentType(documentTypeName); await umbracoUi.documentType.clickEditorSettingsButton(); - await umbracoUi.documentType.clickVaryByCultureSlider(); + await umbracoUi.documentType.clickVaryByCultureToggle(); await umbracoUi.documentType.clickSubmitButton(); await umbracoUi.documentType.clickSaveButton(); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/DocumentType/DocumentTypeFolder.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/DocumentType/DocumentTypeFolder.spec.ts index dacd51f82a45..2ea441b0fdaa 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/DocumentType/DocumentTypeFolder.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/DocumentType/DocumentTypeFolder.spec.ts @@ -58,7 +58,7 @@ test('can rename a document type folder', async ({umbracoApi, umbracoUi}) => { await umbracoUi.documentType.clickActionsMenuForName(oldFolderName); await umbracoUi.documentType.clickRenameFolderButton(); await umbracoUi.documentType.enterFolderName(documentFolderName); - await umbracoUi.documentType.clickConfirmRenameFolderButton(); + await umbracoUi.documentType.clickConfirmRenameButton(); // Assert await umbracoUi.documentType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/DocumentType/DocumentTypeStructureTab.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/DocumentType/DocumentTypeStructureTab.spec.ts index 1a82360e124b..2c307a7c2dfc 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/DocumentType/DocumentTypeStructureTab.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/DocumentType/DocumentTypeStructureTab.spec.ts @@ -83,7 +83,7 @@ test('can configure a collection for a document type', async ({umbracoApi, umbra // Act await umbracoUi.documentType.goToDocumentType(documentTypeName); await umbracoUi.documentType.clickStructureTab(); - await umbracoUi.documentType.clickConfigureAsACollectionButton(); + await umbracoUi.documentType.clickAddCollectionButton(); await umbracoUi.documentType.clickTextButtonWithName(collectionDataTypeName); await umbracoUi.documentType.clickSaveButton(); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/MediaType/MediaTypeDesignTab.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/MediaType/MediaTypeDesignTab.spec.ts index 3a07a0d3dd3a..d5bf9e3d529f 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/MediaType/MediaTypeDesignTab.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/MediaType/MediaTypeDesignTab.spec.ts @@ -117,7 +117,7 @@ test('can set a property as mandatory in a media type', {tag: '@smoke'}, async ( // Act await umbracoUi.mediaType.goToMediaType(mediaTypeName); await umbracoUi.mediaType.clickEditorSettingsButton(); - await umbracoUi.mediaType.clickMandatorySlider(); + await umbracoUi.mediaType.clickMandatoryToggle(); await umbracoUi.mediaType.clickSubmitButton(); await umbracoUi.mediaType.clickSaveButton(); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/MediaType/MediaTypeFolder.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/MediaType/MediaTypeFolder.spec.ts index b78d27f596bd..f318fd732fc1 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/MediaType/MediaTypeFolder.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/MediaType/MediaTypeFolder.spec.ts @@ -55,7 +55,7 @@ test('can rename a media type folder', async ({umbracoApi, umbracoUi}) => { await umbracoUi.mediaType.clickActionsMenuForName(oldFolderName); await umbracoUi.mediaType.clickRenameFolderButton(); await umbracoUi.mediaType.enterFolderName(mediaTypeFolderName); - await umbracoUi.mediaType.clickConfirmRenameFolderButton(); + await umbracoUi.mediaType.clickConfirmRenameButton(); // Assert await umbracoUi.mediaType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/MediaType/MediaTypeStructureTab.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/MediaType/MediaTypeStructureTab.spec.ts index 3ed918f9cf22..920ba4f3d917 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/MediaType/MediaTypeStructureTab.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/MediaType/MediaTypeStructureTab.spec.ts @@ -104,7 +104,7 @@ test('can configure a collection for a media type', async ({umbracoApi, umbracoU // Act await umbracoUi.mediaType.goToMediaType(mediaTypeName); await umbracoUi.mediaType.clickStructureTab(); - await umbracoUi.mediaType.clickConfigureAsACollectionButton(); + await umbracoUi.mediaType.clickAddCollectionButton(); await umbracoUi.mediaType.clickTextButtonWithName(collectionDataTypeName); await umbracoUi.mediaType.clickSaveButton(); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/PartialView/PartialView.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/PartialView/PartialView.spec.ts index 6edddfed0794..ff81a9899486 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/PartialView/PartialView.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/PartialView/PartialView.spec.ts @@ -111,7 +111,8 @@ test('can update a partial view content', {tag: '@smoke'}, async ({umbracoApi, u expect(updatedPartialView.content).toBe(updatedPartialViewContent); }); -test('can use query builder with Order By statement for a partial view', async ({umbracoApi, umbracoUi}) => { +// Remove .fixme when the issue is fixed: https://github.com/umbraco/Umbraco-CMS/issues/18536 +test.fixme('can use query builder with Order By statement for a partial view', async ({umbracoApi, umbracoUi}) => { //Arrange const propertyAliasValue = 'UpdateDate'; const isAscending = true; @@ -150,7 +151,8 @@ test('can use query builder with Order By statement for a partial view', async ( expect(updatedPartialView.content).toBe(expectedTemplateContent); }); -test('can use query builder with Where statement for a partial view', async ({umbracoApi, umbracoUi}) => { +// Remove .fixme when the issue is fixed: https://github.com/umbraco/Umbraco-CMS/issues/18536 +test.fixme('can use query builder with Where statement for a partial view', async ({umbracoApi, umbracoUi}) => { //Arrange const propertyAliasValue = 'Name'; const operatorValue = 'is'; diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/Template/Templates.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/Template/Templates.spec.ts index cfd34fa291ad..0a19f29864da 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/Template/Templates.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/Template/Templates.spec.ts @@ -175,7 +175,8 @@ test.skip('can use query builder with Order By statement for a template', async expect(templateData.content).toBe(expectedTemplateContent); }); -test('can use query builder with Where statement for a template', async ({umbracoApi, umbracoUi}) => { +// Remove .fixme when the issue is fixed: https://github.com/umbraco/Umbraco-CMS/issues/18536 +test.fixme('can use query builder with Where statement for a template', async ({umbracoApi, umbracoUi}) => { // Arrange const propertyAliasValue = 'Name'; const operatorValue = 'is'; diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/User/ContentStartNodes.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/User/ContentStartNodes.spec.ts index f59ae83870b0..70bd76fcca00 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/User/ContentStartNodes.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/User/ContentStartNodes.spec.ts @@ -69,7 +69,9 @@ test('can see parent of start node but not access it', async ({umbracoApi, umbra // Assert await umbracoUi.content.isContentInTreeVisible(rootDocumentName); await umbracoUi.content.goToContentWithName(rootDocumentName); - await umbracoUi.content.doesErrorNotificationHaveText(NotificationConstantHelper.error.noAccessToResource); + await umbracoUi.content.isErrorNotificationVisible(); + // TODO: Uncomment this when this issue is fixed https://github.com/umbraco/Umbraco-CMS/issues/18533 + //await umbracoUi.content.doesErrorNotificationHaveText(NotificationConstantHelper.error.noAccessToResource); await umbracoUi.content.clickCaretButtonForContentName(rootDocumentName); await umbracoUi.content.isChildContentInTreeVisible(rootDocumentName, childDocumentOneName); await umbracoUi.content.isChildContentInTreeVisible(rootDocumentName, childDocumentTwoName, false); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/User/MediaStartNodes.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/User/MediaStartNodes.spec.ts index 81cdeb84ed70..bc9637b05ab6 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/User/MediaStartNodes.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/User/MediaStartNodes.spec.ts @@ -63,7 +63,9 @@ test('can see parent of start node but not access it', async ({umbracoApi, umbra await umbracoUi.media.isMediaTreeItemVisible(rootFolderName); await umbracoUi.waitForTimeout(500); await umbracoUi.media.goToMediaWithName(rootFolderName); - await umbracoUi.media.doesErrorNotificationHaveText(NotificationConstantHelper.error.noAccessToResource); + await umbracoUi.content.isErrorNotificationVisible(); + // TODO: Uncomment this when this issue is fixed https://github.com/umbraco/Umbraco-CMS/issues/18533 + //await umbracoUi.content.doesErrorNotificationHaveText(NotificationConstantHelper.error.noAccessToResource); await umbracoUi.media.clickCaretButtonForMediaName(rootFolderName); await umbracoUi.media.isChildMediaVisible(rootFolderName, childFolderOneName); await umbracoUi.media.isChildMediaVisible(rootFolderName, childFolderTwoName, false); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/UserGroup/ContentStartNodes.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/UserGroup/ContentStartNodes.spec.ts index 7c7c48d18661..09c8ed8e396f 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/UserGroup/ContentStartNodes.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/UserGroup/ContentStartNodes.spec.ts @@ -71,7 +71,9 @@ test('can see parent of start node but not access it', async ({umbracoApi, umbra // Assert await umbracoUi.content.isContentInTreeVisible(rootDocumentName); await umbracoUi.content.goToContentWithName(rootDocumentName); - await umbracoUi.content.doesErrorNotificationHaveText(NotificationConstantHelper.error.noAccessToResource); + await umbracoUi.content.isErrorNotificationVisible(); + // TODO: Uncomment this when this issue is fixed https://github.com/umbraco/Umbraco-CMS/issues/18533 + //await umbracoUi.content.doesErrorNotificationHaveText(NotificationConstantHelper.error.noAccessToResource); await umbracoUi.content.clickCaretButtonForContentName(rootDocumentName); await umbracoUi.content.isChildContentInTreeVisible(rootDocumentName, childDocumentOneName); await umbracoUi.content.isChildContentInTreeVisible(rootDocumentName, childDocumentTwoName, false); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/UserGroup/DefaultPermissionsInContent.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/UserGroup/DefaultPermissionsInContent.spec.ts index 12f167ec02f4..d3ac9aeadb00 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/UserGroup/DefaultPermissionsInContent.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/UserGroup/DefaultPermissionsInContent.spec.ts @@ -76,7 +76,9 @@ test('can not browse content node with permission disabled', async ({umbracoApi, await umbracoUi.content.goToContentWithName(rootDocumentName); // Assert - await umbracoUi.content.doesErrorNotificationHaveText(NotificationConstantHelper.error.noAccessToResource); + await umbracoUi.content.isErrorNotificationVisible(); + // TODO: Uncomment this when this issue is fixed https://github.com/umbraco/Umbraco-CMS/issues/18533 + //await umbracoUi.content.doesErrorNotificationHaveText(NotificationConstantHelper.error.noAccessToResource); }); test('can create document blueprint with permission enabled', async ({umbracoApi, umbracoUi}) => { @@ -613,6 +615,6 @@ test('can not see delete button in content for userGroup with delete permission await umbracoUi.content.clickActionsMenuForContent(rootDocumentName); // Assert - await umbracoUi.content.isPermissionInActionsMenuVisible('Delete...', false); - await umbracoUi.content.isPermissionInActionsMenuVisible('Create...', true); + await umbracoUi.content.isPermissionInActionsMenuVisible('Delete…', false); + await umbracoUi.content.isPermissionInActionsMenuVisible('Create…', true); }); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/UserGroup/Languages.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/UserGroup/Languages.spec.ts index 7b535fb067bc..34966f24667e 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/UserGroup/Languages.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/UserGroup/Languages.spec.ts @@ -62,7 +62,7 @@ test.afterEach(async ({umbracoApi}) => { await umbracoApi.documentType.ensureNameNotExists(documentTypeName); }); -test('can rename content with language set in userGroup', async ({umbracoApi, umbracoUi}) => { +test.fixme('can rename content with language set in userGroup', async ({umbracoApi, umbracoUi}) => { // Arrange const updatedContentName = 'UpdatedContentName'; userGroupId = await umbracoApi.userGroup.createUserGroupWithLanguageAndContentSection(userGroupName, englishIsoCode); @@ -75,6 +75,7 @@ test('can rename content with language set in userGroup', async ({umbracoApi, um // Act await umbracoUi.content.isDocumentReadOnly(false); await umbracoUi.content.enterContentName(updatedContentName); + // Fix this later. Currently the "Save" button changed to "Save..." button await umbracoUi.content.clickSaveButton(); await umbracoUi.content.clickSaveAndCloseButton(); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/UserGroup/MediaStartNodes.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/UserGroup/MediaStartNodes.spec.ts index b129c3979e56..1a72772e3b62 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/UserGroup/MediaStartNodes.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/UserGroup/MediaStartNodes.spec.ts @@ -64,7 +64,9 @@ test('can see parent of start node but not access it', async ({umbracoApi, umbra await umbracoUi.media.isMediaTreeItemVisible(rootFolderName); await umbracoUi.waitForTimeout(500); await umbracoUi.media.goToMediaWithName(rootFolderName); - await umbracoUi.media.doesErrorNotificationHaveText(NotificationConstantHelper.error.noAccessToResource); + await umbracoUi.content.isErrorNotificationVisible(); + // TODO: Uncomment this when this issue is fixed https://github.com/umbraco/Umbraco-CMS/issues/18533 + //await umbracoUi.content.doesErrorNotificationHaveText(NotificationConstantHelper.error.noAccessToResource); await umbracoUi.media.clickCaretButtonForMediaName(rootFolderName); await umbracoUi.media.isChildMediaVisible(rootFolderName, childFolderOneName); await umbracoUi.media.isChildMediaVisible(rootFolderName, childFolderTwoName, false); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/User.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/User.spec.ts index c385b95c8d45..85b103634dcb 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/User.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/User.spec.ts @@ -319,7 +319,7 @@ test('can allow access to all documents for a user', async ({umbracoApi, umbraco // Act await umbracoUi.user.clickUserWithName(nameOfTheUser); - await umbracoUi.user.clickAllowAccessToAllDocumentsSlider(); + await umbracoUi.user.clickAllowAccessToAllDocumentsToggle(); await umbracoUi.user.clickSaveButton(); // Assert @@ -336,7 +336,7 @@ test('can allow access to all media for a user', async ({umbracoApi, umbracoUi}) // Act await umbracoUi.user.clickUserWithName(nameOfTheUser); - await umbracoUi.user.clickAllowAccessToAllMediaSlider(); + await umbracoUi.user.clickAllowAccessToAllMediaToggle(); await umbracoUi.user.clickSaveButton(); // Assert diff --git a/tests/Umbraco.Tests.Integration/CompatibilitySuppressions.xml b/tests/Umbraco.Tests.Integration/CompatibilitySuppressions.xml index 9241ec135ede..7c6ea3140b7f 100644 --- a/tests/Umbraco.Tests.Integration/CompatibilitySuppressions.xml +++ b/tests/Umbraco.Tests.Integration/CompatibilitySuppressions.xml @@ -64,6 +64,13 @@ <Right>lib/net9.0/Umbraco.Tests.Integration.dll</Right> <IsBaselineSuppression>true</IsBaselineSuppression> </Suppression> + <Suppression> + <DiagnosticId>CP0002</DiagnosticId> + <Target>M:Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services.ContentPublishingServiceTests.Publish_Branch_Does_Not_Publish_Unpublished_Children_Unless_Explicitly_Instructed_To(System.Boolean)</Target> + <Left>lib/net9.0/Umbraco.Tests.Integration.dll</Left> + <Right>lib/net9.0/Umbraco.Tests.Integration.dll</Right> + <IsBaselineSuppression>true</IsBaselineSuppression> + </Suppression> <Suppression> <DiagnosticId>CP0002</DiagnosticId> <Target>M:Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services.TemplateServiceTests.Deleting_Master_Template_Also_Deletes_Children</Target> diff --git a/tests/Umbraco.Tests.Integration/DependencyInjection/UmbracoBuilderExtensions.cs b/tests/Umbraco.Tests.Integration/DependencyInjection/UmbracoBuilderExtensions.cs index f99e6dfbf52f..c03512b4830d 100644 --- a/tests/Umbraco.Tests.Integration/DependencyInjection/UmbracoBuilderExtensions.cs +++ b/tests/Umbraco.Tests.Integration/DependencyInjection/UmbracoBuilderExtensions.cs @@ -21,6 +21,7 @@ using Umbraco.Cms.Core.Sync; using Umbraco.Cms.Infrastructure.Examine; using Umbraco.Cms.Infrastructure.HostedServices; +using Umbraco.Cms.Infrastructure.PublishedCache; using Umbraco.Cms.Persistence.EFCore.Locking; using Umbraco.Cms.Persistence.EFCore.Scoping; using Umbraco.Cms.Tests.Common.TestHelpers.Stubs; @@ -95,6 +96,8 @@ public static IUmbracoBuilder AddTestServices(this IUmbracoBuilder builder, Test builder.Services.AddSingleton<IDistributedLockingMechanism, SqliteEFCoreDistributedLockingMechanism<TestUmbracoDbContext>>(); builder.Services.AddSingleton<IDistributedLockingMechanism, SqlServerEFCoreDistributedLockingMechanism<TestUmbracoDbContext>>(); + builder.Services.AddSingleton<IReservedFieldNamesService, ReservedFieldNamesService>(); + return builder; } @@ -105,7 +108,6 @@ public static IUmbracoBuilder AddTestServices(this IUmbracoBuilder builder, Test /// </summary> private static ILocalizedTextService GetLocalizedTextService(IServiceProvider factory) { - var globalSettings = factory.GetRequiredService<IOptions<GlobalSettings>>(); var loggerFactory = factory.GetRequiredService<ILoggerFactory>(); var appCaches = factory.GetRequiredService<AppCaches>(); @@ -130,7 +132,7 @@ private static ILocalizedTextService GetLocalizedTextService(IServiceProvider fa uiProject.Create(); } - var mainLangFolder = new DirectoryInfo(Path.Combine(uiProject.FullName, Constants.System.DefaultUmbracoPath.TrimStart("~/"), "config", "lang")); + var mainLangFolder = new DirectoryInfo(Path.Combine(uiProject.FullName, Constants.System.DefaultUmbracoPath.TrimStart(Constants.CharArrays.TildeForwardSlash), "config", "lang")); return new LocalizedTextServiceFileSources( loggerFactory.CreateLogger<LocalizedTextServiceFileSources>(), diff --git a/tests/Umbraco.Tests.Integration/ManagementApi/ManagementApiTest.cs b/tests/Umbraco.Tests.Integration/ManagementApi/ManagementApiTest.cs index 9e07d1f64d52..259eb92938ee 100644 --- a/tests/Umbraco.Tests.Integration/ManagementApi/ManagementApiTest.cs +++ b/tests/Umbraco.Tests.Integration/ManagementApi/ManagementApiTest.cs @@ -31,11 +31,12 @@ public abstract class ManagementApiTest<T> : UmbracoTestServerTestBase where T : ManagementApiControllerBase { [SetUp] - public async Task Setup() + public Task Setup() { Client.DefaultRequestHeaders .Accept .Add(new MediaTypeWithQualityHeaderValue(MediaTypeNames.Application.Json)); + return Task.CompletedTask; } protected override void CustomTestAuthSetup(IServiceCollection services) diff --git a/tests/Umbraco.Tests.Integration/NewBackoffice/OpenAPIContractTest.cs b/tests/Umbraco.Tests.Integration/NewBackoffice/OpenAPIContractTest.cs index cbaa22ec495c..2b17a43105aa 100644 --- a/tests/Umbraco.Tests.Integration/NewBackoffice/OpenAPIContractTest.cs +++ b/tests/Umbraco.Tests.Integration/NewBackoffice/OpenAPIContractTest.cs @@ -40,10 +40,10 @@ protected override void CustomTestSetup(IUmbracoBuilder builder) public async Task Validate_OpenApi_Contract_is_implemented() { string[] keysToIgnore = { "servers", "x-generator" }; - var officePath = GlobalSettings.GetBackOfficePath(HostingEnvironment); + var backOfficePath = HostingEnvironment.GetBackOfficePath(); - var urlToContract = $"{officePath}/management/api/openapi.json"; - var swaggerPath = $"{officePath}/swagger/management/swagger.json"; + var urlToContract = $"{backOfficePath}/management/api/openapi.json"; + var swaggerPath = $"{backOfficePath}/swagger/management/swagger.json"; var apiContract = JsonNode.Parse(await Client.GetStringAsync(urlToContract)).AsObject(); var generatedJsonString = await Client.GetStringAsync(swaggerPath); diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Core/DeliveryApi/OpenApiContractTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Core/DeliveryApi/OpenApiContractTest.cs index 704b28c6aad1..c34c0f9d724c 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Core/DeliveryApi/OpenApiContractTest.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Core/DeliveryApi/OpenApiContractTest.cs @@ -1,9 +1,7 @@ -using System.Text.Json.Nodes; +using System.Text.Json.Nodes; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Options; using NUnit.Framework; using Umbraco.Cms.Api.Delivery.Controllers; -using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Hosting; using Umbraco.Cms.Tests.Integration.TestServerTest; @@ -12,8 +10,6 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.DeliveryApi; [TestFixture] public class OpenApiContractTest : UmbracoTestServerTestBase { - private GlobalSettings GlobalSettings => GetRequiredService<IOptions<GlobalSettings>>().Value; - private IHostingEnvironment HostingEnvironment => GetRequiredService<IHostingEnvironment>(); protected override void CustomTestSetup(IUmbracoBuilder builder) @@ -26,7 +22,7 @@ protected override void CustomTestSetup(IUmbracoBuilder builder) [Test] public async Task Validate_OpenApi_Contract() { - var backOfficePath = GlobalSettings.GetBackOfficePath(HostingEnvironment); + var backOfficePath = HostingEnvironment.GetBackOfficePath(); var swaggerPath = $"{backOfficePath}/swagger/delivery/swagger.json"; diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Core/RuntimeStateTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Core/RuntimeStateTests.cs index 208b7b11e4cd..09408824a39b 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Core/RuntimeStateTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Core/RuntimeStateTests.cs @@ -79,7 +79,7 @@ public TestMigrationPlan() : base(TestMigrationPlanName) protected override void DefinePlan() => To<TestMigration>(TestMigrationFinalState); } - private class TestMigration : PackageMigrationBase + private sealed class TestMigration : AsyncPackageMigrationBase { public TestMigration( IPackagingService packagingService, @@ -91,9 +91,13 @@ public TestMigration( IMigrationContext context, IOptions<PackageMigrationSettings> options) : base(packagingService, mediaService, mediaFileManager, mediaUrlGenerators, shortStringHelper, contentTypeBaseServiceProvider, context, options) + { } + + protected override Task MigrateAsync() { - } + ImportPackage.FromEmbeddedResource<TestMigration>().Do(); - protected override void Migrate() => ImportPackage.FromEmbeddedResource<TestMigration>().Do(); + return Task.CompletedTask; + } } } diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/ContentTypeEditingServiceModelsBuilderDisabledTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/ContentTypeEditingServiceModelsBuilderDisabledTests.cs new file mode 100644 index 000000000000..fa9dbbeddb35 --- /dev/null +++ b/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/ContentTypeEditingServiceModelsBuilderDisabledTests.cs @@ -0,0 +1,35 @@ +using NUnit.Framework; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.Services.OperationStatus; + +namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Services; + +public class + ContentTypeEditingServiceModelsBuilderDisabledTests : ContentTypeEditingServiceModelsBuilderDisabledTestsBase +{ + // test some properties from IPublishedContent + [TestCaseSource(nameof(DifferentCapitalizedAlias), new object[] { nameof(IPublishedContent.Id) })] + [TestCaseSource(nameof(DifferentCapitalizedAlias), new object[] { nameof(IPublishedContent.Name) })] + [TestCaseSource(nameof(DifferentCapitalizedAlias), new object[] { nameof(IPublishedContent.SortOrder) })] + // test some properties from IPublishedElement + [TestCaseSource(nameof(DifferentCapitalizedAlias), new object[] { nameof(IPublishedContent.Properties) })] + [TestCaseSource(nameof(DifferentCapitalizedAlias), new object[] { nameof(IPublishedContent.ContentType) })] + [TestCaseSource(nameof(DifferentCapitalizedAlias), new object[] { nameof(IPublishedContent.Key) })] + // test some methods from IPublishedContent + [TestCaseSource(nameof(DifferentCapitalizedAlias), new object[] { nameof(IPublishedContent.IsDraft) })] + [TestCaseSource(nameof(DifferentCapitalizedAlias), new object[] { nameof(IPublishedContent.IsPublished) })] + public async Task Can_Use_Invalid_ModelsBuilder_PropertyType_Alias_When_ModelsBuilderIsDisabled( + string propertyTypeAlias) + { + var propertyType = ContentTypePropertyTypeModel("Test Property", propertyTypeAlias); + var createModel = ContentTypeCreateModel("Test", propertyTypes: new[] { propertyType }); + + var result = await ContentTypeEditingService.CreateAsync(createModel, Constants.Security.SuperUserKey); + Assert.Multiple(() => + { + Assert.IsTrue(result.Success); + Assert.AreEqual(ContentTypeOperationStatus.Success, result.Status); + }); + } +} diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/ContentTypeEditingServiceModelsBuilderDisabledTestsBase.cs b/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/ContentTypeEditingServiceModelsBuilderDisabledTestsBase.cs new file mode 100644 index 000000000000..55e632ba8a6b --- /dev/null +++ b/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/ContentTypeEditingServiceModelsBuilderDisabledTestsBase.cs @@ -0,0 +1,11 @@ +using Umbraco.Cms.Infrastructure.ModelsBuilder.Options; + +namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Services; + +/// <summary> +/// Unlike <see cref="ContentTypeEditingServiceModelsBuilderEnabledTestsBase"/> this testbase does not configure the modelsbuilder based <see cref="ConfigurePropertySettingsOptions"/> +/// which has the same effect as disabling it completely as <see cref="ContentTypeEditingServiceModelsBuilderEnabledTestsBase"/> only loads in that part anyway. +/// </summary> +public class ContentTypeEditingServiceModelsBuilderDisabledTestsBase : ContentTypeEditingServiceTestsBase +{ +} diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/ContentTypeEditingServiceModelsBuilderEnabledTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/ContentTypeEditingServiceModelsBuilderEnabledTests.cs new file mode 100644 index 000000000000..88be628734bb --- /dev/null +++ b/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/ContentTypeEditingServiceModelsBuilderEnabledTests.cs @@ -0,0 +1,34 @@ +using NUnit.Framework; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.Services.OperationStatus; + +namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Services; + +public class ContentTypeEditingServiceModelsBuilderEnabledTests : ContentTypeEditingServiceModelsBuilderEnabledTestsBase +{ + // test some properties from IPublishedContent + [TestCaseSource(nameof(DifferentCapitalizedAlias), new object[] { nameof(IPublishedContent.Id) })] + [TestCaseSource(nameof(DifferentCapitalizedAlias), new object[] { nameof(IPublishedContent.Name) })] + [TestCaseSource(nameof(DifferentCapitalizedAlias), new object[] { nameof(IPublishedContent.SortOrder) })] + // test some properties from IPublishedElement + [TestCaseSource(nameof(DifferentCapitalizedAlias), new object[] { nameof(IPublishedContent.Properties) })] + [TestCaseSource(nameof(DifferentCapitalizedAlias), new object[] { nameof(IPublishedContent.ContentType) })] + [TestCaseSource(nameof(DifferentCapitalizedAlias), new object[] { nameof(IPublishedContent.Key) })] + // test some methods from IPublishedContent + [TestCaseSource(nameof(DifferentCapitalizedAlias), new object[] { nameof(IPublishedContent.IsDraft) })] + [TestCaseSource(nameof(DifferentCapitalizedAlias), new object[] { nameof(IPublishedContent.IsPublished) })] + public async Task Cannot_Use_Invalid_ModelsBuilder_PropertyType_Alias_When_ModelsBuilderIsEnabled( + string propertyTypeAlias) + { + var propertyType = ContentTypePropertyTypeModel("Test Property", propertyTypeAlias); + var createModel = ContentTypeCreateModel("Test", propertyTypes: new[] { propertyType }); + + var result = await ContentTypeEditingService.CreateAsync(createModel, Constants.Security.SuperUserKey); + Assert.Multiple(() => + { + Assert.IsFalse(result.Success); + Assert.AreEqual(ContentTypeOperationStatus.InvalidPropertyTypeAlias, result.Status); + }); + } +} diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/ContentTypeEditingServiceModelsBuilderEnabledTestsBase.cs b/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/ContentTypeEditingServiceModelsBuilderEnabledTestsBase.cs new file mode 100644 index 000000000000..5709bec3c7ad --- /dev/null +++ b/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/ContentTypeEditingServiceModelsBuilderEnabledTestsBase.cs @@ -0,0 +1,15 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Infrastructure.ModelsBuilder.Options; + +namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Services; + +public class ContentTypeEditingServiceModelsBuilderEnabledTestsBase : ContentTypeEditingServiceTestsBase +{ + protected override void CustomTestSetup(IUmbracoBuilder builder) + { + builder.Services.ConfigureOptions<ConfigurePropertySettingsOptions>(); + } +} diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/ContentTypeEditingServiceTests.Create.cs b/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/ContentTypeEditingServiceTests.Create.cs index da23e8785794..bac636136ff8 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/ContentTypeEditingServiceTests.Create.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/ContentTypeEditingServiceTests.Create.cs @@ -708,17 +708,6 @@ public async Task Cannot_Use_As_ParentKey() Assert.AreEqual(ContentTypeOperationStatus.InvalidParent, result.Status); } - // test some properties from IPublishedContent - [TestCase(nameof(IPublishedContent.Id))] - [TestCase(nameof(IPublishedContent.Name))] - [TestCase(nameof(IPublishedContent.SortOrder))] - // test some properties from IPublishedElement - [TestCase(nameof(IPublishedElement.Properties))] - [TestCase(nameof(IPublishedElement.ContentType))] - [TestCase(nameof(IPublishedElement.Key))] - // test some methods from IPublishedContent - [TestCase(nameof(IPublishedContent.IsDraft))] - [TestCase(nameof(IPublishedContent.IsPublished))] [TestCase("")] [TestCase(" ")] [TestCase(" ")] @@ -727,21 +716,12 @@ public async Task Cannot_Use_As_ParentKey() [TestCase("!\"#¤%&/()=)?`")] public async Task Cannot_Use_Invalid_PropertyType_Alias(string propertyTypeAlias) { - // ensure that property casing is ignored when handling reserved property aliases - var propertyTypeAliases = new[] - { - propertyTypeAlias, propertyTypeAlias.ToLowerInvariant(), propertyTypeAlias.ToUpperInvariant() - }; - - foreach (var alias in propertyTypeAliases) - { - var propertyType = ContentTypePropertyTypeModel("Test Property", alias); - var createModel = ContentTypeCreateModel("Test", propertyTypes: new[] { propertyType }); + var propertyType = ContentTypePropertyTypeModel("Test Property", propertyTypeAlias); + var createModel = ContentTypeCreateModel("Test", propertyTypes: new[] { propertyType }); - var result = await ContentTypeEditingService.CreateAsync(createModel, Constants.Security.SuperUserKey); - Assert.IsFalse(result.Success); - Assert.AreEqual(ContentTypeOperationStatus.InvalidPropertyTypeAlias, result.Status); - } + var result = await ContentTypeEditingService.CreateAsync(createModel, Constants.Security.SuperUserKey); + Assert.IsFalse(result.Success); + Assert.AreEqual(ContentTypeOperationStatus.InvalidPropertyTypeAlias, result.Status); } [TestCase("testProperty", "testProperty")] @@ -816,9 +796,7 @@ public async Task Cannot_Use_Empty_Name_For_PropertyType_Container() [TestCase(".")] [TestCase("-")] [TestCase("!\"#¤%&/()=)?`")] - [TestCase("system")] - [TestCase("System")] - [TestCase("SYSTEM")] + [TestCaseSource(nameof(DifferentCapitalizedAlias), new object[] { "System"})] public async Task Cannot_Use_Invalid_Alias(string contentTypeAlias) { var createModel = ContentTypeCreateModel("Test", contentTypeAlias); @@ -1095,4 +1073,32 @@ public async Task Cannot_Create_Container_With_Unknown_Type(string containerType Assert.IsFalse(result.Success); Assert.AreEqual(ContentTypeOperationStatus.InvalidContainerType, result.Status); } + + [TestCase(false, true)] + [TestCase(true, false)] + public async Task Cannot_Have_Element_Type_Mismatched_Inheritance(bool parentIsElement, bool childIsElement) + { + var parentModel = ContentTypeCreateModel("Parent1", isElement: parentIsElement); + + var parentKey = (await ContentTypeEditingService.CreateAsync(parentModel, Constants.Security.SuperUserKey)).Result?.Key; + Assert.IsTrue(parentKey.HasValue); + + Composition[] composition = + { + new() + { + CompositionType = CompositionType.Inheritance, Key = parentKey.Value, + } + }; + + var childModel = ContentTypeCreateModel( + "Child", + compositions: composition, + isElement: childIsElement); + + var result = await ContentTypeEditingService.CreateAsync(childModel, Constants.Security.SuperUserKey); + + Assert.IsFalse(result.Success); + Assert.AreEqual(ContentTypeOperationStatus.InvalidElementFlagComparedToParent, result.Status); + } } diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/ContentTypeEditingServiceTestsBase.cs b/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/ContentTypeEditingServiceTestsBase.cs index 1068f4b9a1a5..f478904684be 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/ContentTypeEditingServiceTestsBase.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/ContentTypeEditingServiceTestsBase.cs @@ -180,4 +180,11 @@ protected TModel CreateContainer<TModel>( Type = type, Key = key ?? Guid.NewGuid(), }; + + protected static IEnumerable<string> DifferentCapitalizedAlias(string baseAlias) + { + yield return baseAlias; + yield return baseAlias.ToLowerInvariant(); + yield return baseAlias.ToUpperInvariant(); + } } diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/ExamineBaseTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/ExamineBaseTest.cs index 4d2fb1530b34..fe5c459c7c35 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/ExamineBaseTest.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/ExamineBaseTest.cs @@ -18,6 +18,7 @@ using Umbraco.Cms.Infrastructure.Persistence; using Umbraco.Cms.Tests.Integration.Testing; using Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services; +using IScopeProvider = Umbraco.Cms.Infrastructure.Scoping.IScopeProvider; namespace Umbraco.Cms.Tests.Integration.Umbraco.Examine.Lucene.UmbracoExamine; @@ -113,7 +114,9 @@ protected IDisposable GetSynchronousContentIndex( false, publicAccessServiceMock.Object, scopeProviderMock.Object, - parentId); + parentId, + null, + null); } else { diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Migrations/AdvancedMigrationTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Migrations/AdvancedMigrationTests.cs index 35194277c28b..bf17957aad3e 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Migrations/AdvancedMigrationTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Migrations/AdvancedMigrationTests.cs @@ -47,10 +47,11 @@ public class AdvancedMigrationTests : UmbracoIntegrationTest DatabaseCacheRebuilder, DistributedCache, Mock.Of<IKeyValueService>(), - ServiceScopeFactory); + ServiceScopeFactory, + AppCaches.NoCache); [Test] - public void CreateTableOfTDto() + public async Task CreateTableOfTDtoAsync() { var builder = Mock.Of<IMigrationBuilder>(); Mock.Get(builder) @@ -72,7 +73,7 @@ public void CreateTableOfTDto() .From(string.Empty) .To<CreateTableOfTDtoMigration>("done")); - upgrader.Execute(MigrationPlanExecutor, ScopeProvider, Mock.Of<IKeyValueService>()); + await upgrader.ExecuteAsync(MigrationPlanExecutor, ScopeProvider, Mock.Of<IKeyValueService>()).ConfigureAwait(false); var db = ScopeAccessor.AmbientScope.Database; var exists = ScopeAccessor.AmbientScope.SqlContext.SqlSyntax.DoesTableExist(db, "umbracoUser"); @@ -82,7 +83,7 @@ public void CreateTableOfTDto() } [Test] - public void DeleteKeysAndIndexesOfTDto() + public async Task DeleteKeysAndIndexesOfTDtoAsync() { var builder = Mock.Of<IMigrationBuilder>(); Mock.Get(builder) @@ -108,13 +109,13 @@ public void DeleteKeysAndIndexesOfTDto() .To<CreateTableOfTDtoMigration>("a") .To<DeleteKeysAndIndexesMigration>("done")); - upgrader.Execute(MigrationPlanExecutor, ScopeProvider, Mock.Of<IKeyValueService>()); + await upgrader.ExecuteAsync(MigrationPlanExecutor, ScopeProvider, Mock.Of<IKeyValueService>()).ConfigureAwait(false); scope.Complete(); } } [Test] - public void CreateKeysAndIndexesOfTDto() + public async Task CreateKeysAndIndexesOfTDtoAsync() { if (BaseTestDatabase.IsSqlite()) { @@ -150,13 +151,13 @@ public void CreateKeysAndIndexesOfTDto() .To<DeleteKeysAndIndexesMigration>("b") .To<CreateKeysAndIndexesOfTDtoMigration>("done")); - upgrader.Execute(MigrationPlanExecutor, ScopeProvider, Mock.Of<IKeyValueService>()); + await upgrader.ExecuteAsync(MigrationPlanExecutor, ScopeProvider, Mock.Of<IKeyValueService>()).ConfigureAwait(false); scope.Complete(); } } [Test] - public void CreateKeysAndIndexes() + public async Task CreateKeysAndIndexesAsync() { if (BaseTestDatabase.IsSqlite()) { @@ -192,13 +193,13 @@ public void CreateKeysAndIndexes() .To<DeleteKeysAndIndexesMigration>("b") .To<CreateKeysAndIndexesMigration>("done")); - upgrader.Execute(MigrationPlanExecutor, ScopeProvider, Mock.Of<IKeyValueService>()); + await upgrader.ExecuteAsync(MigrationPlanExecutor, ScopeProvider, Mock.Of<IKeyValueService>()).ConfigureAwait(false); scope.Complete(); } } [Test] - public void AddColumn() + public async Task AddColumnAsync() { var builder = Mock.Of<IMigrationBuilder>(); Mock.Get(builder) @@ -224,7 +225,7 @@ public void AddColumn() .To<CreateTableOfTDtoMigration>("a") .To<AddColumnMigration>("done")); - upgrader.Execute(MigrationPlanExecutor, ScopeProvider, Mock.Of<IKeyValueService>()); + await upgrader.ExecuteAsync(MigrationPlanExecutor, ScopeProvider, Mock.Of<IKeyValueService>()).ConfigureAwait(false); var db = ScopeAccessor.AmbientScope.Database; diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Migrations/PartialMigrationsTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Migrations/PartialMigrationsTests.cs index 1fb0e784176c..dc0d4b0a4d2f 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Migrations/PartialMigrationsTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Migrations/PartialMigrationsTests.cs @@ -1,4 +1,4 @@ -using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using NPoco; using NUnit.Framework; @@ -42,7 +42,7 @@ protected override void ConfigureTestServices(IServiceCollection services) => services.AddNotificationHandler<UmbracoPlanExecutedNotification, UmbracoPlanExecutedTestNotificationHandler>(); [Test] - public void CanRerunPartiallyCompletedMigration() + public async Task CanRerunPartiallyCompletedMigration() { var plan = new MigrationPlan("test") .From(string.Empty) @@ -52,7 +52,7 @@ public void CanRerunPartiallyCompletedMigration() var upgrader = new Upgrader(plan); - var result = upgrader.Execute(MigrationPlanExecutor, ScopeProvider, KeyValueService); + var result = await upgrader.ExecuteAsync(MigrationPlanExecutor, ScopeProvider, KeyValueService).ConfigureAwait(false); Assert.Multiple(() => { @@ -74,7 +74,7 @@ public void CanRerunPartiallyCompletedMigration() // Now let's simulate that someone came along and fixed the broken migration and we'll now try and rerun ErrorMigration.ShouldExplode = false; upgrader = new Upgrader(plan); - result = upgrader.Execute(MigrationPlanExecutor, ScopeProvider, KeyValueService); + result = await upgrader.ExecuteAsync(MigrationPlanExecutor, ScopeProvider, KeyValueService).ConfigureAwait(false); Assert.Multiple(() => { @@ -93,12 +93,12 @@ public void CanRerunPartiallyCompletedMigration() } [Test] - public void CanRunMigrationTwice() + public async Task CanRunMigrationTwice() { Upgrader? upgrader = new(new SimpleMigrationPlan()); Upgrader? upgrader2 = new(new SimpleMigrationPlan()); - var result = upgrader.Execute(MigrationPlanExecutor, ScopeProvider, KeyValueService); - var result2 = upgrader2.Execute(MigrationPlanExecutor, ScopeProvider, KeyValueService); + var result = await upgrader.ExecuteAsync(MigrationPlanExecutor, ScopeProvider, KeyValueService).ConfigureAwait(false); + var result2 = await upgrader2.ExecuteAsync(MigrationPlanExecutor, ScopeProvider, KeyValueService).ConfigureAwait(false); Assert.Multiple(() => { @@ -113,7 +113,7 @@ public void CanRunMigrationTwice() } [Test] - public void StateIsOnlySavedIfAMigrationSucceeds() + public async Task StateIsOnlySavedIfAMigrationSucceeds() { var plan = new MigrationPlan("test") .From(string.Empty) @@ -121,7 +121,7 @@ public void StateIsOnlySavedIfAMigrationSucceeds() .To<CreateTableMigration>("b"); var upgrader = new Upgrader(plan); - var result = upgrader.Execute(MigrationPlanExecutor, ScopeProvider, KeyValueService); + var result = await upgrader.ExecuteAsync(MigrationPlanExecutor, ScopeProvider, KeyValueService).ConfigureAwait(false); Assert.Multiple(() => { @@ -138,16 +138,16 @@ public void StateIsOnlySavedIfAMigrationSucceeds() } [Test] - public void ScopesAreCreatedIfNecessary() + public async Task ScopesAreCreatedIfNecessary() { - // The migrations have assert to esnure scopes + // The migrations have assert to ensure scopes var plan = new MigrationPlan("test") .From(string.Empty) .To<AsserScopeScopedTestMigration>("a") .To<AssertScopeUnscopedTestMigration>("b"); var upgrader = new Upgrader(plan); - var result = upgrader.Execute(MigrationPlanExecutor, ScopeProvider, KeyValueService); + var result = await upgrader.ExecuteAsync(MigrationPlanExecutor, ScopeProvider, KeyValueService).ConfigureAwait(false); Assert.IsTrue(result.Successful); Assert.AreEqual(2, result.CompletedTransitions.Count); @@ -157,7 +157,7 @@ public void ScopesAreCreatedIfNecessary() [Test] [TestCase(true)] [TestCase(false)] - public void UmbracoPlanExecutedNotificationIsAlwaysPublished(bool shouldSucceed) + public async Task UmbracoPlanExecutedNotificationIsAlwaysPublished(bool shouldSucceed) { var notificationPublished = false; ErrorMigration.ShouldExplode = shouldSucceed is false; @@ -190,7 +190,7 @@ public void UmbracoPlanExecutedNotificationIsAlwaysPublished(bool shouldSucceed) // We have to use the DatabaseBuilder otherwise the notification isn't published var databaseBuilder = GetRequiredService<DatabaseBuilder>(); var plan = new TestUmbracoPlan(null!); - databaseBuilder.UpgradeSchemaAndData(plan); + await databaseBuilder.UpgradeSchemaAndDataAsync(plan).ConfigureAwait(false); Assert.IsTrue(notificationPublished); } @@ -242,7 +242,7 @@ protected override void Migrate() => Create .Do(); } -internal class AssertScopeUnscopedTestMigration : UnscopedMigrationBase +internal class AssertScopeUnscopedTestMigration : UnscopedAsyncMigrationBase { private readonly IScopeProvider _scopeProvider; private readonly IScopeAccessor _scopeAccessor; @@ -256,7 +256,7 @@ public AssertScopeUnscopedTestMigration( _scopeAccessor = scopeAccessor; } - protected override void Migrate() + protected override Task MigrateAsync() { // Since this is a scopeless migration both ambient scope and the parent scope should be null Assert.IsNull(_scopeAccessor.AmbientScope); @@ -265,6 +265,8 @@ protected override void Migrate() Assert.IsNull(((Scope)scope).ParentScope); Context.Complete(); + + return Task.CompletedTask; } } diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/SyntaxProvider/SqlServerSyntaxProviderTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/SyntaxProvider/SqlServerSyntaxProviderTests.cs index 7e1d1f163f8f..3d419ff95579 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/SyntaxProvider/SqlServerSyntaxProviderTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/SyntaxProvider/SqlServerSyntaxProviderTests.cs @@ -18,7 +18,6 @@ using Umbraco.Cms.Tests.Common.TestHelpers; using Umbraco.Cms.Tests.Common.Testing; using Umbraco.Cms.Tests.Integration.Testing; -using Umbraco.Extensions; namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.SyntaxProvider; diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentPublishingServiceTests.Publish.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentPublishingServiceTests.Publish.cs index db0bf3658d49..a8abd95f987d 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentPublishingServiceTests.Publish.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentPublishingServiceTests.Publish.cs @@ -42,12 +42,6 @@ public async Task Can_Publish_Child_Of_Root() VerifyIsPublished(Subpage.Key); } - [Obsolete("Replaced by Publish_Branch_Does_Not_Publish_Unpublished_Children_Unless_Instructed_To. This will be removed in Umbraco 16.")] - public Task Publish_Branch_Does_Not_Publish_Unpublished_Children_Unless_Explicitly_Instructed_To(bool force) - { - return Task.CompletedTask; - } - [TestCase(PublishBranchFilter.Default)] [TestCase(PublishBranchFilter.IncludeUnpublished)] [TestCase(PublishBranchFilter.ForceRepublish)] diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/UserIdKeyResolverTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/UserIdKeyResolverTests.cs index cfcc52fa8fa6..e66d4414a451 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/UserIdKeyResolverTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/UserIdKeyResolverTests.cs @@ -75,14 +75,16 @@ public async Task Can_Resolve_Super_User_Id_To_Key() } [Test] - public async Task Unknown_Key_Throws() + public Task Unknown_Key_Throws() { Assert.ThrowsAsync<InvalidOperationException>(async () => await UserIdKeyResolver.GetAsync(Guid.NewGuid())); + return Task.CompletedTask; } [Test] - public async Task Unknown_Id_Throws() + public Task Unknown_Id_Throws() { - Assert.ThrowsAsync<InvalidOperationException>(async () => await UserIdKeyResolver.GetAsync(1234567890)); + Assert.ThrowsAsync<InvalidOperationException>(async () => await UserIdKeyResolver.GetAsync(1234567890)); + return Task.CompletedTask; } } diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Persistence.EFCore/Scoping/EFCoreScopeTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Persistence.EFCore/Scoping/EFCoreScopeTest.cs index deefb0d99b09..0d027fb6e02a 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Persistence.EFCore/Scoping/EFCoreScopeTest.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Persistence.EFCore/Scoping/EFCoreScopeTest.cs @@ -81,7 +81,7 @@ public async Task NestedCreateScopeInnerException() using (IEfCoreScope<TestUmbracoDbContext> scope = EfCoreScopeProvider.CreateScope()) { // scopeProvider.Context.Enlist("test", completed => scopeCompleted = completed); - await scope.ExecuteWithContextAsync(async database => + await scope.ExecuteWithContextAsync(database => { scope.ScopeContext!.Enlist("test", completed => scopeCompleted = completed); Assert.IsInstanceOf<EFCoreScope<TestUmbracoDbContext>>(scope); @@ -97,7 +97,7 @@ await scope.ExecuteWithContextAsync(async database => throw new Exception("bang!"); } - return true; + return Task.FromResult(true); }); scope.Complete(); diff --git a/tests/Umbraco.Tests.UnitTests/AutoFixture/Customizations/UmbracoCustomizations.cs b/tests/Umbraco.Tests.UnitTests/AutoFixture/Customizations/UmbracoCustomizations.cs index eb2963316608..1d1c25f8df3b 100644 --- a/tests/Umbraco.Tests.UnitTests/AutoFixture/Customizations/UmbracoCustomizations.cs +++ b/tests/Umbraco.Tests.UnitTests/AutoFixture/Customizations/UmbracoCustomizations.cs @@ -50,19 +50,10 @@ public void Customize(IFixture fixture) x.With(settings => settings.ApplicationVirtualPath, string.Empty)); fixture.Customize<BackOfficeAreaRoutes>(u => u.FromFactory( - () => new BackOfficeAreaRoutes( - Options.Create(new GlobalSettings()), - Mock.Of<IHostingEnvironment>(x => - x.ToAbsolute(It.IsAny<string>()) == "/umbraco" && x.ApplicationVirtualPath == string.Empty), - Mock.Of<IRuntimeState>(x => x.Level == RuntimeLevel.Run), - new UmbracoApiControllerTypeCollection(Enumerable.Empty<Type>)))); + () => new BackOfficeAreaRoutes(Mock.Of<IRuntimeState>(x => x.Level == RuntimeLevel.Run)))); fixture.Customize<PreviewRoutes>(u => u.FromFactory( - () => new PreviewRoutes( - Options.Create(new GlobalSettings()), - Mock.Of<IHostingEnvironment>(x => - x.ToAbsolute(It.IsAny<string>()) == "/umbraco" && x.ApplicationVirtualPath == string.Empty), - Mock.Of<IRuntimeState>(x => x.Level == RuntimeLevel.Run)))); + () => new PreviewRoutes(Mock.Of<IRuntimeState>(x => x.Level == RuntimeLevel.Run)))); var httpContextAccessor = new HttpContextAccessor { HttpContext = new DefaultHttpContext() }; fixture.Customize<HttpContext>(x => x.FromFactory(() => httpContextAccessor.HttpContext)); diff --git a/tests/Umbraco.Tests.UnitTests/TestHelpers/Objects/TestUmbracoContextFactory.cs b/tests/Umbraco.Tests.UnitTests/TestHelpers/Objects/TestUmbracoContextFactory.cs index 16b38cdd7823..a6000ad044c2 100644 --- a/tests/Umbraco.Tests.UnitTests/TestHelpers/Objects/TestUmbracoContextFactory.cs +++ b/tests/Umbraco.Tests.UnitTests/TestHelpers/Objects/TestUmbracoContextFactory.cs @@ -21,36 +21,15 @@ namespace Umbraco.Cms.Tests.UnitTests.TestHelpers.Objects; public class TestUmbracoContextFactory { public static IUmbracoContextFactory Create( - GlobalSettings globalSettings = null, IUmbracoContextAccessor umbracoContextAccessor = null, IHttpContextAccessor httpContextAccessor = null, IPublishedUrlProvider publishedUrlProvider = null, UmbracoRequestPathsOptions umbracoRequestPathsOptions = null) { - if (globalSettings == null) - { - globalSettings = new GlobalSettings(); - } - - if (umbracoContextAccessor == null) - { - umbracoContextAccessor = new TestUmbracoContextAccessor(); - } - - if (httpContextAccessor == null) - { - httpContextAccessor = Mock.Of<IHttpContextAccessor>(); - } - - if (publishedUrlProvider == null) - { - publishedUrlProvider = Mock.Of<IPublishedUrlProvider>(); - } - - if (umbracoRequestPathsOptions == null) - { - umbracoRequestPathsOptions = new UmbracoRequestPathsOptions(); - } + umbracoContextAccessor ??= new TestUmbracoContextAccessor(); + httpContextAccessor ??= Mock.Of<IHttpContextAccessor>(); + publishedUrlProvider ??= Mock.Of<IPublishedUrlProvider>(); + umbracoRequestPathsOptions ??= new UmbracoRequestPathsOptions(); var contentCache = new Mock<IPublishedContentCache>(); var mediaCache = new Mock<IPublishedMediaCache>(); @@ -62,7 +41,7 @@ public static IUmbracoContextFactory Create( var umbracoContextFactory = new UmbracoContextFactory( umbracoContextAccessor, - new UmbracoRequestPaths(Options.Create(globalSettings), hostingEnvironment, Options.Create(umbracoRequestPathsOptions)), + new UmbracoRequestPaths(hostingEnvironment, Options.Create(umbracoRequestPathsOptions)), hostingEnvironment, new UriUtility(hostingEnvironment), new AspNetCoreCookieManager(httpContextAccessor), diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Configuration/Models/GlobalSettingsTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Configuration/Models/GlobalSettingsTests.cs deleted file mode 100644 index fbf1ea75ca20..000000000000 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Configuration/Models/GlobalSettingsTests.cs +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (c) Umbraco. -// See LICENSE for more details. - -using AutoFixture.NUnit3; -using Microsoft.Extensions.Options; -using NUnit.Framework; -using Umbraco.Cms.Core.Configuration.Models; -using Umbraco.Cms.Tests.UnitTests.AutoFixture; -using Umbraco.Cms.Web.Common.AspNetCore; -using Umbraco.Extensions; - -namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Configuration.Models; - -[TestFixture] -public class GlobalSettingsTests -{ - [InlineAutoMoqData("~/umbraco", "/", "umbraco")] - [InlineAutoMoqData("~/umbraco", "/MyVirtualDir", "umbraco")] - [InlineAutoMoqData("~/customPath", "/MyVirtualDir/", "umbraco")] - [InlineAutoMoqData("~/some-wacky/nestedPath", "/MyVirtualDir", "umbraco")] - [InlineAutoMoqData("~/some-wacky/nestedPath", "/MyVirtualDir/NestedVDir/", "umbraco")] - public void Umbraco_Mvc_Area( - string path, - string rootPath, - string outcome, - [Frozen] IOptionsMonitor<HostingSettings> hostingSettings, - AspNetCoreHostingEnvironment hostingEnvironment) - { - hostingSettings.CurrentValue.ApplicationVirtualPath = rootPath; - - var globalSettings = new GlobalSettings(); - - Assert.AreEqual(outcome, globalSettings.GetUmbracoMvcAreaNoCache(hostingEnvironment)); - } -} diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Configuration/Models/RequestHandlerSettingsTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Configuration/Models/RequestHandlerSettingsTests.cs index 10a0df465a14..0bf9ed29714c 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Configuration/Models/RequestHandlerSettingsTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Configuration/Models/RequestHandlerSettingsTests.cs @@ -11,17 +11,18 @@ public class RequestHandlerSettingsTests [Test] public void Given_CharCollection_With_DefaultEnabled_MergesCollection() { - var userCollection = new CharItem[] + var settings = new RequestHandlerSettings { - new() { Char = "test", Replacement = "replace" }, - new() { Char = "test2", Replacement = "replace2" }, + UserDefinedCharCollection = + { + new() { Char = "test", Replacement = "replace" }, + new() { Char = "test2", Replacement = "replace2" }, + } }; - - var settings = new RequestHandlerSettings { UserDefinedCharCollection = userCollection }; var actual = settings.GetCharReplacements().ToList(); var expectedCollection = RequestHandlerSettings.DefaultCharCollection.ToList(); - expectedCollection.AddRange(userCollection); + expectedCollection.AddRange(settings.UserDefinedCharCollection); Assert.AreEqual(expectedCollection.Count, actual.Count); Assert.That(actual, Is.EquivalentTo(expectedCollection)); @@ -30,33 +31,32 @@ public void Given_CharCollection_With_DefaultEnabled_MergesCollection() [Test] public void Given_CharCollection_With_DefaultDisabled_ReturnsUserCollection() { - var userCollection = new CharItem[] - { - new() { Char = "test", Replacement = "replace" }, - new() { Char = "test2", Replacement = "replace2" }, - }; - var settings = new RequestHandlerSettings { - UserDefinedCharCollection = userCollection, + UserDefinedCharCollection = + { + new() { Char = "test", Replacement = "replace" }, + new() { Char = "test2", Replacement = "replace2" }, + }, EnableDefaultCharReplacements = false, }; var actual = settings.GetCharReplacements().ToList(); - Assert.AreEqual(userCollection.Length, actual.Count); - Assert.That(actual, Is.EquivalentTo(userCollection)); + Assert.AreEqual(settings.UserDefinedCharCollection.Count, actual.Count); + Assert.That(actual, Is.EquivalentTo(settings.UserDefinedCharCollection)); } [Test] public void Given_CharCollection_That_OverridesDefaultValues_ReturnsReplacements() { - var userCollection = new CharItem[] + var settings = new RequestHandlerSettings { - new() { Char = "%", Replacement = "percent" }, - new() { Char = ".", Replacement = "dot" }, + UserDefinedCharCollection = + { + new() { Char = "%", Replacement = "percent" }, + new() { Char = ".", Replacement = "dot" }, + } }; - - var settings = new RequestHandlerSettings { UserDefinedCharCollection = userCollection }; var actual = settings.GetCharReplacements().ToList(); Assert.AreEqual(RequestHandlerSettings.DefaultCharCollection.Length, actual.Count); @@ -70,14 +70,15 @@ public void Given_CharCollection_That_OverridesDefaultValues_ReturnsReplacements [Test] public void Given_CharCollection_That_OverridesDefaultValues_And_ContainsNew_ReturnsMergedWithReplacements() { - var userCollection = new CharItem[] + var settings = new RequestHandlerSettings { - new() { Char = "%", Replacement = "percent" }, - new() { Char = ".", Replacement = "dot" }, - new() { Char = "new", Replacement = "new" }, + UserDefinedCharCollection = + { + new() { Char = "%", Replacement = "percent" }, + new() { Char = ".", Replacement = "dot" }, + new() { Char = "new", Replacement = "new" }, + } }; - - var settings = new RequestHandlerSettings { UserDefinedCharCollection = userCollection }; var actual = settings.GetCharReplacements().ToList(); // Add 1 to the length, because we're expecting to only add one new one diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Configuration/Models/Validation/ContentSettingsValidatorTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Configuration/Models/Validation/ContentSettingsValidatorTests.cs index c195c63268cf..bea57e67863e 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Configuration/Models/Validation/ContentSettingsValidatorTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Configuration/Models/Validation/ContentSettingsValidatorTests.cs @@ -41,16 +41,16 @@ public void Returns_Fail_For_Configuration_With_Invalid_AutoFillImageProperties_ private static ContentSettings BuildContentSettings(string culture = "en-US", string autoFillImagePropertyAlias = "testAlias") => new ContentSettings { - Error404Collection = new ContentErrorPage[] + Error404Collection = + { + new() { Culture = culture, ContentId = 1 }, + }, + Imaging = + { + AutoFillImageProperties = { - new() { Culture = culture, ContentId = 1 }, + new() { Alias = autoFillImagePropertyAlias, WidthFieldAlias = "w", HeightFieldAlias = "h", LengthFieldAlias = "l", ExtensionFieldAlias = "e" }, }, - Imaging = new ContentImagingSettings - { - AutoFillImageProperties = new ImagingAutoFillUploadField[] - { - new() { Alias = autoFillImagePropertyAlias, WidthFieldAlias = "w", HeightFieldAlias = "h", LengthFieldAlias = "l", ExtensionFieldAlias = "e" }, - }, }, }; } diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/DeliveryApi/PublishedContentCacheTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/DeliveryApi/PublishedContentCacheTests.cs index 5075f48a52d2..405ff1416211 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/DeliveryApi/PublishedContentCacheTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/DeliveryApi/PublishedContentCacheTests.cs @@ -1,4 +1,4 @@ -using Microsoft.Extensions.Options; +using Microsoft.Extensions.Options; using Moq; using NUnit.Framework; using Umbraco.Cms.Core.Configuration.Models; @@ -255,7 +255,10 @@ private IRequestPreviewService CreateRequestPreviewService(bool isPreview = fals private IOptionsMonitor<DeliveryApiSettings> CreateDeliveryApiSettings(string[]? disallowedContentTypeAliases = null) { - var deliveryApiSettings = new DeliveryApiSettings { DisallowedContentTypeAliases = disallowedContentTypeAliases ?? Array.Empty<string>() }; + var deliveryApiSettings = new DeliveryApiSettings + { + DisallowedContentTypeAliases = new HashSet<string>(disallowedContentTypeAliases ?? Array.Empty<string>()) + }; var deliveryApiOptionsMonitorMock = new Mock<IOptionsMonitor<DeliveryApiSettings>>(); deliveryApiOptionsMonitorMock.SetupGet(s => s.CurrentValue).Returns(deliveryApiSettings); return deliveryApiOptionsMonitorMock.Object; diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Models/VariationTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Models/VariationTests.cs index a9ff943921f0..ef8b8733c726 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Models/VariationTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Models/VariationTests.cs @@ -632,6 +632,7 @@ private static PropertyValidationService GetPropertyValidationService() var dataValueEditorFactory = Mock.Of<IDataValueEditorFactory>(x => x.Create<TextOnlyValueEditor>(It.IsAny<DataEditorAttribute>()) == new TextOnlyValueEditor( attribute, + Mock.Of<ILocalizedTextService>(), Mock.Of<IShortStringHelper>(), new SystemTextJsonSerializer(), Mock.Of<IIOHelper>())); diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/BlockListEditorPropertyValueEditorTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/BlockListEditorPropertyValueEditorTests.cs new file mode 100644 index 000000000000..9edc858532fe --- /dev/null +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/BlockListEditorPropertyValueEditorTests.cs @@ -0,0 +1,165 @@ +using System.Globalization; +using System.Text.Json.Nodes; +using Microsoft.Extensions.Logging.Abstractions; +using Moq; +using NUnit.Framework; +using Umbraco.Cms.Core.Cache; +using Umbraco.Cms.Core.Cache.PropertyEditors; +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.Models.Blocks; +using Umbraco.Cms.Core.Models.Validation; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Core.PropertyEditors.ValueConverters; +using Umbraco.Cms.Core.Serialization; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Strings; +using Umbraco.Cms.Infrastructure.Serialization; +using static Umbraco.Cms.Core.PropertyEditors.BlockListPropertyEditorBase; + +namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.PropertyEditors; + +[TestFixture] +public class BlockListEditorPropertyValueEditorTests +{ + [Test] + public void Validates_Null_As_Below_Configured_Min() + { + var editor = CreateValueEditor(); + var result = editor.Validate(null, false, null, PropertyValidationContext.Empty()); + Assert.AreEqual(1, result.Count()); + + var validationResult = result.First(); + Assert.AreEqual($"validation_entriesShort", validationResult.ErrorMessage); + } + + [TestCase(0, false)] + [TestCase(1, false)] + [TestCase(2, true)] + [TestCase(3, true)] + public void Validates_Number_Of_Items_Is_Greater_Than_Or_Equal_To_Configured_Min(int numberOfBlocks, bool expectedSuccess) + { + var value = CreateBlocksJson(numberOfBlocks); + var editor = CreateValueEditor(); + var result = editor.Validate(value, false, null, PropertyValidationContext.Empty()); + if (expectedSuccess) + { + Assert.IsEmpty(result); + } + else + { + Assert.AreEqual(1, result.Count()); + + var validationResult = result.First(); + Assert.AreEqual("validation_entriesShort", validationResult.ErrorMessage); + } + } + + [TestCase(3, true)] + [TestCase(4, true)] + [TestCase(5, false)] + public void Validates_Number_Of_Items_Is_Less_Than_Or_Equal_To_Configured_Max(int numberOfBlocks, bool expectedSuccess) + { + var value = CreateBlocksJson(numberOfBlocks); + var editor = CreateValueEditor(); + var result = editor.Validate(value, false, null, PropertyValidationContext.Empty()); + if (expectedSuccess) + { + Assert.IsEmpty(result); + } + else + { + Assert.AreEqual(1, result.Count()); + + var validationResult = result.First(); + Assert.AreEqual("validation_entriesExceed", validationResult.ErrorMessage); + } + } + + private static JsonObject CreateBlocksJson(int numberOfBlocks) + { + var layoutItems = new JsonArray(); + var contentData = new JsonArray(); + for (int i = 0; i < numberOfBlocks; i++) + { + layoutItems.Add(CreateLayoutBlockJson()); + contentData.Add(CreateContentDataBlockJson()); + } + + return new JsonObject + { + { + "layout", new JsonObject + { + { "Umbraco.BlockList", layoutItems }, + } + }, + { "contentData", contentData }, + }; + } + + private static JsonObject CreateLayoutBlockJson() => + new() + { + { "$type", "BlockListLayoutItem" }, + { "contentKey", Guid.NewGuid() }, + }; + + private static JsonObject CreateContentDataBlockJson() => + new() + { + { "contentTypeKey", Guid.Parse("01935a73-c86b-4521-9dcb-ad7cea402215") }, + { "key", Guid.NewGuid() }, + { + "values", + new JsonArray + { + new JsonObject + { + { "editorAlias", "Umbraco.TextBox" }, + { "alias", "message" }, + { "value", "Hello" }, + }, + } + } + }; + + private static BlockListEditorPropertyValueEditor CreateValueEditor() + { + var localizedTextServiceMock = new Mock<ILocalizedTextService>(); + localizedTextServiceMock.Setup(x => x.Localize( + It.IsAny<string>(), + It.IsAny<string>(), + It.IsAny<CultureInfo>(), + It.IsAny<IDictionary<string, string>>())) + .Returns((string key, string alias, CultureInfo culture, IDictionary<string, string> args) => $"{key}_{alias}"); + + var jsonSerializer = new SystemTextJsonSerializer(); + var languageService = Mock.Of<ILanguageService>(); + + return new BlockListEditorPropertyValueEditor( + new DataEditorAttribute("alias"), + new BlockListEditorDataConverter(jsonSerializer), + new(new DataEditorCollection(() => [])), + new DataValueReferenceFactoryCollection(Enumerable.Empty<IDataValueReferenceFactory>), + Mock.Of<IDataTypeConfigurationCache>(), + Mock.Of<IBlockEditorElementTypeCache>(), + localizedTextServiceMock.Object, + new NullLogger<BlockListEditorPropertyValueEditor>(), + Mock.Of<IShortStringHelper>(), + jsonSerializer, + Mock.Of<IPropertyValidationService>(), + new BlockEditorVarianceHandler(languageService, Mock.Of<IContentTypeService>()), + languageService, + Mock.Of<IIOHelper>()) + { + ConfigurationObject = new BlockListConfiguration + { + ValidationLimit = new BlockListConfiguration.NumberRange + { + Min = 2, + Max = 4 + }, + }, + }; + } +} diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/ColorPickerPropertyValueEditorTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/ColorPickerPropertyValueEditorTests.cs new file mode 100644 index 000000000000..17b49f6b13cf --- /dev/null +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/ColorPickerPropertyValueEditorTests.cs @@ -0,0 +1,63 @@ +using System.Globalization; +using Humanizer; +using System.Text.Json.Nodes; +using Moq; +using NUnit.Framework; +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.Models.Validation; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Core.Serialization; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Strings; + +namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.PropertyEditors; + +[TestFixture] +public class ColorPickerPropertyValueEditorTests +{ + [TestCase("#ffffff", true)] + [TestCase("#f0f0f0", false)] + public void Validates_Is_Configured_Color(string color, bool expectedSuccess) + { + var value = JsonNode.Parse($"{{\"label\": \"\", \"value\": \"{color}\"}}"); + var editor = CreateValueEditor(); + var result = editor.Validate(value, false, null, PropertyValidationContext.Empty()); + if (expectedSuccess) + { + Assert.IsEmpty(result); + } + else + { + Assert.AreEqual(1, result.Count()); + + var validationResult = result.First(); + Assert.AreEqual(validationResult.ErrorMessage, "validation_invalidColor"); + } + } + + private static ColorPickerPropertyEditor.ColorPickerPropertyValueEditor CreateValueEditor() + { + var localizedTextServiceMock = new Mock<ILocalizedTextService>(); + localizedTextServiceMock.Setup(x => x.Localize( + It.IsAny<string>(), + It.IsAny<string>(), + It.IsAny<CultureInfo>(), + It.IsAny<IDictionary<string, string>>())) + .Returns((string key, string alias, CultureInfo culture, IDictionary<string, string> args) => $"{key}_{alias}"); + return new ColorPickerPropertyEditor.ColorPickerPropertyValueEditor( + Mock.Of<IShortStringHelper>(), + Mock.Of<IJsonSerializer>(), + Mock.Of<IIOHelper>(), + new DataEditorAttribute("alias"), + localizedTextServiceMock.Object) + { + ConfigurationObject = new ColorPickerConfiguration + { + Items = [ + new ColorPickerConfiguration.ColorPickerItem { Value = "ffffff", Label = "White" }, + new ColorPickerConfiguration.ColorPickerItem { Value = "000000", Label = "Black" } + ] + } + }; + } +} diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/DataValueEditorReuseTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/DataValueEditorReuseTests.cs index 217b820c78aa..16ea5c11f9a2 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/DataValueEditorReuseTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/DataValueEditorReuseTests.cs @@ -29,6 +29,7 @@ public void SetUp() .Setup(m => m.Create<TextOnlyValueEditor>(It.IsAny<DataEditorAttribute>())) .Returns(() => new TextOnlyValueEditor( new DataEditorAttribute("a"), + Mock.Of<ILocalizedTextService>(), Mock.Of<IShortStringHelper>(), Mock.Of<IJsonSerializer>(), Mock.Of<IIOHelper>())); diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/DecimalValueEditorTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/DecimalPropertyValueEditorTests.cs similarity index 70% rename from tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/DecimalValueEditorTests.cs rename to tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/DecimalPropertyValueEditorTests.cs index 59569c7583a4..92b4f8579aeb 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/DecimalValueEditorTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/DecimalPropertyValueEditorTests.cs @@ -14,7 +14,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.PropertyEditors; [TestFixture] -public class DecimalValueEditorTests +public class DecimalPropertyValueEditorTests { // annoyingly we can't use decimals etc. in attributes, so we can't turn these into test cases :( private Dictionary<object?,object?> _valuesAndExpectedResults = new(); @@ -88,7 +88,7 @@ public void Validates_Is_Decimal(object value, bool expectedSuccess) Assert.AreEqual(1, result.Count()); var validationResult = result.First(); - Assert.AreEqual(validationResult.ErrorMessage, $"The value {value} is not a valid decimal"); + Assert.AreEqual($"The value {value} is not a valid decimal", validationResult.ErrorMessage); } } @@ -108,7 +108,7 @@ public void Validates_Is_Greater_Than_Or_Equal_To_Configured_Min(object value, b Assert.AreEqual(1, result.Count()); var validationResult = result.First(); - Assert.AreEqual(validationResult.ErrorMessage, "validation_outOfRangeMinimum"); + Assert.AreEqual("validation_outOfRangeMinimum", validationResult.ErrorMessage); } } @@ -127,16 +127,36 @@ public void Validates_Is_Less_Than_Or_Equal_To_Configured_Max(object value, bool { Assert.AreEqual(1, result.Count()); + var validationResult = result.First(); + Assert.AreEqual("validation_outOfRangeMaximum", validationResult.ErrorMessage); + } + } + + [TestCase(1.8, true)] + [TestCase(2.2, false)] + public void Validates_Is_Less_Than_Or_Equal_To_Configured_Max_With_Configured_Whole_Numbers(object value, bool expectedSuccess) + { + var editor = CreateValueEditor(min: 1, max: 2); + var result = editor.Validate(value, false, null, PropertyValidationContext.Empty()); + if (expectedSuccess) + { + Assert.IsEmpty(result); + } + else + { + Assert.AreEqual(1, result.Count()); + var validationResult = result.First(); Assert.AreEqual(validationResult.ErrorMessage, "validation_outOfRangeMaximum"); } } - [TestCase(1.4, false)] - [TestCase(1.5, true)] - public void Validates_Matches_Configured_Step(object value, bool expectedSuccess) + [TestCase(0.2, 1.4, false)] + [TestCase(0.2, 1.5, true)] + [TestCase(0.0, 1.4, true)] // A step of zero would trigger a divide by zero error in evaluating. So we always pass validation for zero, as effectively any step value is valid. + public void Validates_Matches_Configured_Step(double step, object value, bool expectedSuccess) { - var editor = CreateValueEditor(); + var editor = CreateValueEditor(step: step); var result = editor.Validate(value, false, null, PropertyValidationContext.Empty()); if (expectedSuccess) { @@ -147,7 +167,7 @@ public void Validates_Matches_Configured_Step(object value, bool expectedSuccess Assert.AreEqual(1, result.Count()); var validationResult = result.First(); - Assert.AreEqual(validationResult.ErrorMessage, "validation_invalidStep"); + Assert.AreEqual("validation_invalidStep", validationResult.ErrorMessage); } } @@ -164,7 +184,7 @@ public void Validates_Matches_Configured_Step(object value, bool expectedSuccess return CreateValueEditor().ToEditor(property.Object); } - private static DecimalPropertyEditor.DecimalPropertyValueEditor CreateValueEditor() + private static DecimalPropertyEditor.DecimalPropertyValueEditor CreateValueEditor(double min = 1.1, double max = 1.9, double step = 0.2) { var localizedTextServiceMock = new Mock<ILocalizedTextService>(); localizedTextServiceMock.Setup(x => x.Localize( @@ -173,6 +193,37 @@ private static DecimalPropertyEditor.DecimalPropertyValueEditor CreateValueEdito It.IsAny<CultureInfo>(), It.IsAny<IDictionary<string, string>>())) .Returns((string key, string alias, CultureInfo culture, IDictionary<string, string> args) => $"{key}_{alias}"); + + // When configuration is populated from the deserialized JSON, whole number values are deserialized as integers. + // So we want to replicate that in our tests. + var configuration = new Dictionary<string, object>(); + if (min % 1 == 0) + { + configuration.Add("min", (int)min); + } + else + { + configuration.Add("min", min); + } + + if (max % 1 == 0) + { + configuration.Add("max", (int)max); + } + else + { + configuration.Add("max", max); + } + + if (step % 1 == 0) + { + configuration.Add("step", (int)step); + } + else + { + configuration.Add("step", step); + } + return new DecimalPropertyEditor.DecimalPropertyValueEditor( Mock.Of<IShortStringHelper>(), Mock.Of<IJsonSerializer>(), @@ -180,12 +231,7 @@ private static DecimalPropertyEditor.DecimalPropertyValueEditor CreateValueEdito new DataEditorAttribute("alias"), localizedTextServiceMock.Object) { - ConfigurationObject = new Dictionary<string, object> - { - { "min", 1.1 }, - { "max", 1.9 }, - { "step", 0.2 } - } + ConfigurationObject = configuration }; } } diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/IntegerValueEditorTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/IntegerPropertyValueEditorTests.cs similarity index 92% rename from tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/IntegerValueEditorTests.cs rename to tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/IntegerPropertyValueEditorTests.cs index d9545b8126b4..a0c0a38ab368 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/IntegerValueEditorTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/IntegerPropertyValueEditorTests.cs @@ -14,7 +14,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.PropertyEditors; [TestFixture] -public class IntegerValueEditorTests +public class IntegerPropertyValueEditorTests { // annoyingly we can't use decimals etc. in attributes, so we can't turn these into test cases :( private Dictionary<object?,object?> _valuesAndExpectedResults = new(); @@ -132,11 +132,12 @@ public void Validates_Is_Less_Than_Or_Equal_To_Configured_Max(object value, bool } } - [TestCase(17, false)] - [TestCase(18, true)] - public void Validates_Matches_Configured_Step(object value, bool expectedSuccess) + [TestCase(2, 17, false)] + [TestCase(2, 18, true)] + [TestCase(0, 17, true)] // A step of zero would trigger a divide by zero error in evaluating. So we always pass validation for zero, as effectively any step value is valid. + public void Validates_Matches_Configured_Step(int step, object value, bool expectedSuccess) { - var editor = CreateValueEditor(); + var editor = CreateValueEditor(step: step); var result = editor.Validate(value, false, null, PropertyValidationContext.Empty()); if (expectedSuccess) { @@ -164,7 +165,7 @@ public void Validates_Matches_Configured_Step(object value, bool expectedSuccess return CreateValueEditor().ToEditor(property.Object); } - private static IntegerPropertyEditor.IntegerPropertyValueEditor CreateValueEditor() + private static IntegerPropertyEditor.IntegerPropertyValueEditor CreateValueEditor(int step = 2) { var localizedTextServiceMock = new Mock<ILocalizedTextService>(); localizedTextServiceMock.Setup(x => x.Localize( @@ -184,7 +185,7 @@ private static IntegerPropertyEditor.IntegerPropertyValueEditor CreateValueEdito { { "min", 10 }, { "max", 20 }, - { "step", 2 } + { "step", step } } }; } diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/MediaPicker3ValueEditorValidationTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/MediaPicker3ValueEditorValidationTests.cs index 4f6ee99ad485..ad82ce3179b0 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/MediaPicker3ValueEditorValidationTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/MediaPicker3ValueEditorValidationTests.cs @@ -22,7 +22,7 @@ internal class MediaPicker3ValueEditorValidationTests [TestCase(false, false)] public void Validates_Start_Node_Immediate_Parent(bool shouldSucceed, bool hasValidParentKey) { - var (valueEditor, mediaTypeServiceMock, mediaNavigationQueryServiceMock) = CreateValueEditor(); + var (valueEditor, mediaTypeServiceMock, _, mediaNavigationQueryServiceMock) = CreateValueEditor(); Guid? validParentKey = Guid.NewGuid(); var mediaKey = Guid.NewGuid(); @@ -49,7 +49,7 @@ public void Validates_Start_Node_Immediate_Parent(bool shouldSucceed, bool hasVa [Test] public void Validates_Start_Node_Parent_Not_Found() { - var (valueEditor, mediaTypeServiceMock, mediaNavigationQueryServiceMock) = CreateValueEditor(); + var (valueEditor, mediaTypeServiceMock, _, mediaNavigationQueryServiceMock) = CreateValueEditor(); Guid? parentKey = null; var mediaKey = Guid.NewGuid(); @@ -71,7 +71,7 @@ public void Validates_Start_Node_Parent_Not_Found() [TestCase(false, true, false)] public void Validates_Start_Node_Ancestor(bool shouldSucceed, bool findsAncestor, bool hasValidAncestorKey) { - var (valueEditor, mediaTypeServiceMock, mediaNavigationQueryServiceMock) = CreateValueEditor(); + var (valueEditor, mediaTypeServiceMock, _, mediaNavigationQueryServiceMock) = CreateValueEditor(); Guid ancestorKey = Guid.NewGuid(); Guid? parentKey = Guid.NewGuid(); @@ -90,26 +90,32 @@ public void Validates_Start_Node_Ancestor(bool shouldSucceed, bool findsAncestor ValidateResult(shouldSucceed, result); } - [TestCase(true, true, true)] - [TestCase(false, true, false)] - [TestCase(false, false, true)] - public void Validates_Allowed_Type(bool shouldSucceed, bool hasAllowedType, bool findsMediaType) + [TestCase(true, true, true, false)] + [TestCase(false, true, false, false)] + [TestCase(false, false, true, false)] + [TestCase(true, true, true, true)] + [TestCase(false, true, false, true)] + [TestCase(false, false, true, true)] + public void Validates_Allowed_Type(bool shouldSucceed, bool hasAllowedType, bool findsMediaType, bool valueProvidesMediaTypeAlias) { - var (valueEditor, mediaTypeServiceMock, mediaNavigationQueryServiceMock) = CreateValueEditor(); + var (valueEditor, mediaTypeServiceMock, mediaServiceMock, mediaNavigationQueryServiceMock) = CreateValueEditor(); var mediaKey = Guid.NewGuid(); var mediaTypeKey = Guid.NewGuid(); var mediaTypeAlias = "Alias"; valueEditor.ConfigurationObject = new MediaPicker3Configuration() { Filter = $"{mediaTypeKey}" }; var mediaTypeMock = new Mock<IMediaType>(); + var mediaMock = new Mock<IMedia>(); if (hasAllowedType) { mediaTypeMock.Setup(x => x.Key).Returns(mediaTypeKey); + mediaMock.SetupGet(x => x.ContentType.Alias).Returns(mediaTypeAlias); } else { mediaTypeMock.Setup(x => x.Key).Returns(Guid.NewGuid()); + mediaMock.SetupGet(x => x.ContentType.Alias).Returns("AnotherAlias"); } if (findsMediaType) @@ -121,7 +127,13 @@ public void Validates_Allowed_Type(bool shouldSucceed, bool hasAllowedType, bool mediaTypeServiceMock.Setup(x => x.Get(It.IsAny<string>())).Returns((IMediaType)null); } - var value = "[ {\n \" key\" : \"20266ebe-1f7e-4cf3-a694-7a5fb210223b\",\n \"mediaKey\" : \"" + mediaKey + "\",\n \"mediaTypeAlias\" : \"" + mediaTypeAlias + "\",\n \"crops\" : [ ],\n \"focalPoint\" : null\n} ]"; + if (valueProvidesMediaTypeAlias is false) + { + mediaServiceMock.Setup(x => x.GetByIds(It.Is<IEnumerable<Guid>>(y => y.First() == mediaKey))).Returns([mediaMock.Object]); + } + + var providedMediaTypeAlias = valueProvidesMediaTypeAlias ? mediaTypeAlias : string.Empty; + var value = "[ {\n \" key\" : \"20266ebe-1f7e-4cf3-a694-7a5fb210223b\",\n \"mediaKey\" : \"" + mediaKey + "\",\n \"mediaTypeAlias\" : \"" + providedMediaTypeAlias + "\",\n \"crops\" : [ ],\n \"focalPoint\" : null\n} ]"; var result = valueEditor.Validate(value, false, null, PropertyValidationContext.Empty()); ValidateResult(shouldSucceed, result); @@ -134,7 +146,7 @@ public void Validates_Allowed_Type(bool shouldSucceed, bool hasAllowedType, bool [TestCase("[]", false, true)] public void Validates_Multiple(string value, bool multiple, bool succeed) { - var (valueEditor, mediaTypeServiceMock, mediaNavigationQueryServiceMock) = CreateValueEditor(); + var (valueEditor, mediaTypeServiceMock, _, mediaNavigationQueryServiceMock) = CreateValueEditor(); valueEditor.ConfigurationObject = new MediaPicker3Configuration() { Multiple = multiple }; @@ -150,7 +162,7 @@ public void Validates_Multiple(string value, bool multiple, bool succeed) [TestCase("[]", 0, true)] public void Validates_Min_Limit(string value, int min, bool succeed) { - var (valueEditor, mediaTypeServiceMock, mediaNavigationQueryServiceMock) = CreateValueEditor(); + var (valueEditor, mediaTypeServiceMock, _, mediaNavigationQueryServiceMock) = CreateValueEditor(); valueEditor.ConfigurationObject = new MediaPicker3Configuration() { Multiple = true, ValidationLimit = new MediaPicker3Configuration.NumberRange { Min = min } }; @@ -168,7 +180,7 @@ public void Validates_Min_Limit(string value, int min, bool succeed) [TestCase("[]", 0, true)] public void Validates_Max_Limit(string value, int max, bool succeed) { - var (valueEditor, mediaTypeServiceMock, mediaNavigationQueryServiceMock) = CreateValueEditor(); + var (valueEditor, mediaTypeServiceMock, _, mediaNavigationQueryServiceMock) = CreateValueEditor(); valueEditor.ConfigurationObject = new MediaPicker3Configuration() { Multiple = true, ValidationLimit = new MediaPicker3Configuration.NumberRange { Max = max } }; @@ -188,9 +200,10 @@ private static void ValidateResult(bool succeed, IEnumerable<ValidationResult> r } } - private static (MediaPicker3PropertyEditor.MediaPicker3PropertyValueEditor ValueEditor, Mock<IMediaTypeService> MediaTypeServiceMock, Mock<IMediaNavigationQueryService> MediaNavigationQueryServiceMock) CreateValueEditor() + private static (MediaPicker3PropertyEditor.MediaPicker3PropertyValueEditor ValueEditor, Mock<IMediaTypeService> MediaTypeServiceMock, Mock<IMediaService> MediaServiceMock, Mock<IMediaNavigationQueryService> MediaNavigationQueryServiceMock) CreateValueEditor() { var mediaTypeServiceMock = new Mock<IMediaTypeService>(); + var mediaServiceMock = new Mock<IMediaService>(); var mediaNavigationQueryServiceMock = new Mock<IMediaNavigationQueryService>(); var valueEditor = new MediaPicker3PropertyEditor.MediaPicker3PropertyValueEditor( Mock.Of<IShortStringHelper>(), @@ -198,7 +211,7 @@ private static (MediaPicker3PropertyEditor.MediaPicker3PropertyValueEditor Value Mock.Of<IIOHelper>(), new DataEditorAttribute("alias"), Mock.Of<IMediaImportService>(), - Mock.Of<IMediaService>(), + mediaServiceMock.Object, Mock.Of<ITemporaryFileService>(), Mock.Of<IScopeProvider>(), Mock.Of<IBackOfficeSecurityAccessor>(), @@ -210,6 +223,6 @@ private static (MediaPicker3PropertyEditor.MediaPicker3PropertyValueEditor Value ConfigurationObject = new MediaPicker3Configuration() }; - return (valueEditor, mediaTypeServiceMock, mediaNavigationQueryServiceMock); + return (valueEditor, mediaTypeServiceMock, mediaServiceMock, mediaNavigationQueryServiceMock); } } diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/MultiNodeTreePickerTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/MultiNodeTreePickerTests.cs index 1f84fb74005d..f381e845ec51 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/MultiNodeTreePickerTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/MultiNodeTreePickerTests.cs @@ -1,13 +1,16 @@ -using System.Text.Json.Nodes; +using System.Data; +using System.Text.Json.Nodes; using Moq; using NUnit.Framework; -using Org.BouncyCastle.Asn1.X500; using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Editors; using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Core.Serialization; +using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Strings; using Umbraco.Cms.Infrastructure.Serialization; @@ -244,11 +247,30 @@ public void Null_To_Editor_Yields_Null() private static MultiNodeTreePickerPropertyEditor.MultiNodeTreePickerPropertyValueEditor CreateValueEditor( IJsonSerializer? jsonSerializer = null) { + var mockScope = new Mock<IScope>(); + var mockScopeProvider = new Mock<ICoreScopeProvider>(); + mockScopeProvider + .Setup(x => x.CreateCoreScope( + It.IsAny<IsolationLevel>(), + It.IsAny<RepositoryCacheMode>(), + It.IsAny<IEventDispatcher>(), + It.IsAny<IScopedNotificationPublisher>(), + It.IsAny<bool?>(), + It.IsAny<bool>(), + It.IsAny<bool>())) + .Returns(mockScope.Object); + var valueEditor = new MultiNodeTreePickerPropertyEditor.MultiNodeTreePickerPropertyValueEditor( Mock.Of<IShortStringHelper>(), jsonSerializer ?? Mock.Of<IJsonSerializer>(), Mock.Of<IIOHelper>(), - new DataEditorAttribute("alias")); + new DataEditorAttribute("alias"), + Mock.Of<ILocalizedTextService>(), + Mock.Of<IEntityService>(), + mockScopeProvider.Object, + Mock.Of<IContentService>(), + Mock.Of<IMediaService>(), + Mock.Of<IMemberService>()); return valueEditor; } diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/MultiNodeTreePickerValidationTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/MultiNodeTreePickerValidationTests.cs new file mode 100644 index 000000000000..d9d4fda2c53a --- /dev/null +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/MultiNodeTreePickerValidationTests.cs @@ -0,0 +1,217 @@ +using System.ComponentModel.DataAnnotations; +using System.Data; +using Moq; +using NUnit.Framework; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.Entities; +using Umbraco.Cms.Core.Models.Validation; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Core.Scoping; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Strings; +using Umbraco.Cms.Infrastructure.Serialization; + +namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.PropertyEditors; + +[TestFixture] +public class MultiNodeTreePickerValidationTests +{ + // Remember 0 = no limit + [TestCase(0, true, "[{\"type\":\"document\",\"unique\":\"86eb02a7-793f-4406-9152-9736b6b64bee\"}]")] + [TestCase(1, true, "[{\"type\":\"document\",\"unique\":\"86eb02a7-793f-4406-9152-9736b6b64bee\"}]")] + [TestCase(2, true, "[{\"type\":\"document\",\"unique\":\"86eb02a7-793f-4406-9152-9736b6b64bee\"},{\"type\":\"document\",\"unique\":\"25ef6fd2-db48-450a-8c48-df3ad75adf4b\"}]")] + [TestCase(3, false, "[{\"type\":\"document\",\"unique\":\"86eb02a7-793f-4406-9152-9736b6b64bee\"},{\"type\":\"document\",\"unique\":\"86eb02a7-793f-4406-9152-9736b6b64bee\"}]")] + [TestCase(2, false, "[{\"type\":\"document\",\"unique\":\"86eb02a7-793f-4406-9152-9736b6b64bee\"}]")] + [TestCase(1, false, null)] + [TestCase(0, true, null)] + public void Validates_Minimum_Entries(int min, bool shouldSucceed, string? value) + { + var (valueEditor, _, _, _, _) = CreateValueEditor(); + valueEditor.ConfigurationObject = new MultiNodePickerConfiguration { MinNumber = min}; + + var result = valueEditor.Validate(value, false, null, PropertyValidationContext.Empty()); + + TestShouldSucceed(shouldSucceed, result); + } + + private static void TestShouldSucceed(bool shouldSucceed, IEnumerable<ValidationResult> result) + { + if (shouldSucceed) + { + Assert.IsEmpty(result); + } + else + { + Assert.IsNotEmpty(result); + } + } + + [TestCase(0, true, "[]")] + [TestCase(1, true, "[{\"type\":\"document\",\"unique\":\"86eb02a7-793f-4406-9152-9736b6b64bee\"}]")] + [TestCase(0, true, "[{\"type\":\"document\",\"unique\":\"86eb02a7-793f-4406-9152-9736b6b64bee\"}]")] + [TestCase(1, false, "[{\"type\":\"document\",\"unique\":\"86eb02a7-793f-4406-9152-9736b6b64bee\"},{\"type\":\"document\",\"unique\":\"25ef6fd2-db48-450a-8c48-df3ad75adf4b\"}]")] + [TestCase(3, true, "[{\"type\":\"document\",\"unique\":\"86eb02a7-793f-4406-9152-9736b6b64bee\"},{\"type\":\"document\",\"unique\":\"86eb02a7-793f-4406-9152-9736b6b64bee\"}]")] + [TestCase(2, true, "[{\"type\":\"document\",\"unique\":\"86eb02a7-793f-4406-9152-9736b6b64bee\"}]")] + public void Validates_Maximum_Entries(int max, bool shouldSucceed, string value) + { + var (valueEditor, _, _, _, _) = CreateValueEditor(); + valueEditor.ConfigurationObject = new MultiNodePickerConfiguration { MaxNumber = max }; + + var result = valueEditor.Validate(value, false, null, PropertyValidationContext.Empty()); + + TestShouldSucceed(shouldSucceed, result); + } + + private readonly Dictionary<Guid, Guid> _entityTypeMap = new() + { + { Constants.ObjectTypes.Document, Guid.Parse("08035A7E-AE9C-4D36-BA2E-63F639005758") }, + { Constants.ObjectTypes.Media, Guid.Parse("AAF97C7D-A586-45CC-AC7F-CE0A80BCFEE3") }, + { Constants.ObjectTypes.Member, Guid.Parse("E477804E-C903-470B-B7EC-67DCAF71E37C") }, + }; + + private class ObjectTypeTestSetup + { + public ObjectTypeTestSetup(string expectedObjectType, bool shouldSucceed, string value) + { + ExpectedObjectType = expectedObjectType; + ShouldSucceed = shouldSucceed; + Value = value; + } + + public string ExpectedObjectType { get; } + + public bool ShouldSucceed { get; } + + public string Value { get; } + } + + private void SetupEntityServiceForObjectTypeTest(Mock<IEntityService> entityServiceMock) + { + foreach (var objectTypeEntity in _entityTypeMap) + { + var entity = new Mock<IEntitySlim>(); + entity.Setup(x => x.NodeObjectType).Returns(objectTypeEntity.Key); + entityServiceMock.Setup(x => x.Get(objectTypeEntity.Value)).Returns(entity.Object); + } + } + + private IEnumerable<ObjectTypeTestSetup> GetObjectTypeTestSetup() => + [ + new(MultiNodeTreePickerPropertyEditor.MultiNodeTreePickerPropertyValueEditor.DocumentObjectType, true, "[]"), + new(MultiNodeTreePickerPropertyEditor.MultiNodeTreePickerPropertyValueEditor.DocumentObjectType, true, $"[{{\"type\":\"document\",\"unique\":\"{_entityTypeMap[Constants.ObjectTypes.Document]}\"}}]"), + new(MultiNodeTreePickerPropertyEditor.MultiNodeTreePickerPropertyValueEditor.DocumentObjectType, false, $"[{{\"type\":\"document\",\"unique\":\"{_entityTypeMap[Constants.ObjectTypes.Media]}\"}}]"), + new(MultiNodeTreePickerPropertyEditor.MultiNodeTreePickerPropertyValueEditor.DocumentObjectType, false, $"[{{\"type\":\"document\",\"unique\":\"{_entityTypeMap[Constants.ObjectTypes.Member]}\"}}]"), + new(MultiNodeTreePickerPropertyEditor.MultiNodeTreePickerPropertyValueEditor.MediaObjectType, false, $"[{{\"type\":\"document\",\"unique\":\"{_entityTypeMap[Constants.ObjectTypes.Document]}\"}}]"), + new(MultiNodeTreePickerPropertyEditor.MultiNodeTreePickerPropertyValueEditor.MemberObjectType, false, $"[{{\"type\":\"document\",\"unique\":\"{_entityTypeMap[Constants.ObjectTypes.Document]}\"}}]"), + new(MultiNodeTreePickerPropertyEditor.MultiNodeTreePickerPropertyValueEditor.DocumentObjectType, false, $"[{{\"type\":\"document\",\"unique\":\"{_entityTypeMap[Constants.ObjectTypes.Document]}\"}}, {{\"type\":\"document\",\"unique\":\"{_entityTypeMap[Constants.ObjectTypes.Media]}\"}}]"), + new(MultiNodeTreePickerPropertyEditor.MultiNodeTreePickerPropertyValueEditor.MediaObjectType, false, $"[{{\"type\":\"document\",\"unique\":\"{_entityTypeMap[Constants.ObjectTypes.Document]}\"}}]"), + new(MultiNodeTreePickerPropertyEditor.MultiNodeTreePickerPropertyValueEditor.MediaObjectType, false, $"[{{\"type\":\"document\",\"unique\":\"{_entityTypeMap[Constants.ObjectTypes.Member]}\"}}]"), + new(MultiNodeTreePickerPropertyEditor.MultiNodeTreePickerPropertyValueEditor.MemberObjectType, false, $"[{{\"type\":\"document\",\"unique\":\"{_entityTypeMap[Constants.ObjectTypes.Document]}\"}}]"), + new(MultiNodeTreePickerPropertyEditor.MultiNodeTreePickerPropertyValueEditor.MemberObjectType, false, $"[{{\"type\":\"document\",\"unique\":\"{_entityTypeMap[Constants.ObjectTypes.Media]}\"}}]"), + ]; + + [Test] + public void Validates_Object_Type() + { + var setups = GetObjectTypeTestSetup(); + + foreach (var setup in setups) + { + var (valueEditor, entityServiceMock, _, _, _) = CreateValueEditor(); + SetupEntityServiceForObjectTypeTest(entityServiceMock); + valueEditor.ConfigurationObject = new MultiNodePickerConfiguration { TreeSource = new MultiNodePickerConfigurationTreeSource() { ObjectType = setup.ExpectedObjectType } }; + var result = valueEditor.Validate(setup.Value, false, null, PropertyValidationContext.Empty()); + + TestShouldSucceed(setup.ShouldSucceed, result); + } + } + + [TestCase(true, true, true, MultiNodeTreePickerPropertyEditor.MultiNodeTreePickerPropertyValueEditor.DocumentObjectType)] + [TestCase(true, true, true, MultiNodeTreePickerPropertyEditor.MultiNodeTreePickerPropertyValueEditor.MediaObjectType)] + [TestCase(true, true, true, MultiNodeTreePickerPropertyEditor.MultiNodeTreePickerPropertyValueEditor.MemberObjectType)] + [TestCase(false, false, true, MultiNodeTreePickerPropertyEditor.MultiNodeTreePickerPropertyValueEditor.DocumentObjectType)] + [TestCase(false, false, true, MultiNodeTreePickerPropertyEditor.MultiNodeTreePickerPropertyValueEditor.MediaObjectType)] + [TestCase(false, false, true, MultiNodeTreePickerPropertyEditor.MultiNodeTreePickerPropertyValueEditor.MemberObjectType)] + [TestCase(false, true, false, MultiNodeTreePickerPropertyEditor.MultiNodeTreePickerPropertyValueEditor.DocumentObjectType)] + [TestCase(false, true, false, MultiNodeTreePickerPropertyEditor.MultiNodeTreePickerPropertyValueEditor.MediaObjectType)] + [TestCase(false, true, false, MultiNodeTreePickerPropertyEditor.MultiNodeTreePickerPropertyValueEditor.MemberObjectType)] + public void Validates_Allowed_Type(bool shouldSucceed, bool hasAllowedType, bool findsContent, string objectType) + { + var (valueEditor, _, contentService, mediaService, memberService) = CreateValueEditor(); + + var expectedEntityKey = Guid.NewGuid(); + var allowedTypeKey = Guid.NewGuid(); + valueEditor.ConfigurationObject = new MultiNodePickerConfiguration() + { + Filter = $"{allowedTypeKey}", + TreeSource = new MultiNodePickerConfigurationTreeSource { ObjectType = objectType }, + }; + + var contentTypeMock = new Mock<ISimpleContentType>(); + contentTypeMock.Setup(x => x.Key).Returns(() => hasAllowedType ? allowedTypeKey : Guid.NewGuid()); + + var contentMock = new Mock<IContent>(); + contentMock.Setup(x => x.ContentType).Returns(contentTypeMock.Object); + contentService.Setup(x => x.GetById(expectedEntityKey)).Returns(contentMock.Object); + + var mediaMock = new Mock<IMedia>(); + mediaMock.Setup(x => x.ContentType).Returns(contentTypeMock.Object); + mediaService.Setup(x => x.GetById(expectedEntityKey)).Returns(mediaMock.Object); + + var memberMock = new Mock<IMember>(); + memberMock.Setup(x => x.ContentType).Returns(contentTypeMock.Object); + memberService.Setup(x => x.GetById(expectedEntityKey)).Returns(memberMock.Object); + + var actualkey = findsContent ? expectedEntityKey : Guid.NewGuid(); + var value = $"[{{\"type\":\"document\",\"unique\":\"{actualkey}\"}}]"; + + var result = valueEditor.Validate(value, false, null, PropertyValidationContext.Empty()); + TestShouldSucceed(shouldSucceed, result); + + } + + private static (MultiNodeTreePickerPropertyEditor.MultiNodeTreePickerPropertyValueEditor ValueEditor, + Mock<IEntityService> EntityService, + Mock<IContentService> ContentService, + Mock<IMediaService> MediaService, + Mock<IMemberService> MemberService) CreateValueEditor() + { + var entityServiceMock = new Mock<IEntityService>(); + var contentServiceMock = new Mock<IContentService>(); + var mediaServiceMock = new Mock<IMediaService>(); + var memberServiceMock = new Mock<IMemberService>(); + + var mockScope = new Mock<ICoreScope>(); + var mockScopeProvider = new Mock<ICoreScopeProvider>(); + mockScopeProvider + .Setup(x => x.CreateCoreScope( + It.IsAny<IsolationLevel>(), + It.IsAny<RepositoryCacheMode>(), + It.IsAny<IEventDispatcher>(), + It.IsAny<IScopedNotificationPublisher>(), + It.IsAny<bool?>(), + It.IsAny<bool>(), + It.IsAny<bool>())) + .Returns(mockScope.Object); + + var valueEditor = new MultiNodeTreePickerPropertyEditor.MultiNodeTreePickerPropertyValueEditor( + Mock.Of<IShortStringHelper>(), + new SystemTextJsonSerializer(), + Mock.Of<IIOHelper>(), + new DataEditorAttribute("alias"), + Mock.Of<ILocalizedTextService>(), + entityServiceMock.Object, + mockScopeProvider.Object, + contentServiceMock.Object, + mediaServiceMock.Object, + memberServiceMock.Object) + { + ConfigurationObject = new MultiNodePickerConfiguration(), + }; + + return (valueEditor, entityServiceMock, contentServiceMock, mediaServiceMock, memberServiceMock); + } +} diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/MultiValuePropertyEditorTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/MultiValuePropertyEditorTests.cs index 5b76313c2a66..1075510c9132 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/MultiValuePropertyEditorTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/MultiValuePropertyEditorTests.cs @@ -1,11 +1,13 @@ // Copyright (c) Umbraco. // See LICENSE for more details. +using System.Globalization; using Moq; using NUnit.Framework; using Umbraco.Cms.Core; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.Validation; using Umbraco.Cms.Core.PropertyEditors; using Umbraco.Cms.Core.Serialization; using Umbraco.Cms.Core.Services; @@ -19,31 +21,22 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.PropertyEditors; /// multiple values such as the drop down list, check box list, color picker, etc.... /// </summary> /// <remarks> -/// Mostly this used to test the we'd store INT Ids in the Db but publish STRING values or sometimes the INT values +/// Some of these tests are to verify that the we'd store INT Ids in the Db but publish STRING values or sometimes the INT values /// to cache. Now we always just deal with strings and we'll keep the tests that show that. /// </remarks> [TestFixture] public class MultiValuePropertyEditorTests { [Test] - public void DropDownMultipleValueEditor_Format_Data_For_Cache() + public void MultipleValueEditor_WithMultipleValues_Format_Data_For_Cache() { var dataValueEditorFactoryMock = new Mock<IDataValueEditorFactory>(); var serializer = new SystemTextConfigurationEditorJsonSerializer(); var checkBoxListPropertyEditor = new CheckBoxListPropertyEditor( dataValueEditorFactoryMock.Object, - Mock.Of<IIOHelper>(), serializer); - var dataType = new DataType(checkBoxListPropertyEditor, serializer) - { - Id = 1, - }; - dataType.ConfigurationData = dataType.Editor!.GetConfigurationEditor() - .FromConfigurationObject( - new ValueListConfiguration - { - Items = ["Value 1", "Value 2", "Value 3"] - }, - serializer); + Mock.Of<IIOHelper>(), + serializer); + var dataType = CreateAndConfigureDataType(serializer, checkBoxListPropertyEditor); var configuration = dataType.ConfigurationObject as ValueListConfiguration; Assert.NotNull(configuration); @@ -54,13 +47,7 @@ public void DropDownMultipleValueEditor_Format_Data_For_Cache() .Setup(x => x.GetDataType(It.IsAny<int>())) .Returns(dataType); - // TODO use builders instead of this mess - var multipleValueEditor = new MultipleValueEditor( - Mock.Of<ILocalizedTextService>(), - Mock.Of<IShortStringHelper>(), - Mock.Of<IJsonSerializer>(), - Mock.Of<IIOHelper>(), - new DataEditorAttribute(Constants.PropertyEditors.Aliases.TextBox)); + var multipleValueEditor = CreateValueEditor(); dataValueEditorFactoryMock .Setup(x => x.Create<MultipleValueEditor>(It.IsAny<DataEditorAttribute>())) .Returns(multipleValueEditor); @@ -76,25 +63,15 @@ public void DropDownMultipleValueEditor_Format_Data_For_Cache() } [Test] - public void DropDownValueEditor_Format_Data_For_Cache() + public void MultipleValueEditor_WithSingleValue_Format_Data_For_Cache() { var dataValueEditorFactoryMock = new Mock<IDataValueEditorFactory>(); - var serializer = new SystemTextConfigurationEditorJsonSerializer(); var checkBoxListPropertyEditor = new CheckBoxListPropertyEditor( dataValueEditorFactoryMock.Object, - Mock.Of<IIOHelper>(), serializer); - var dataType = new DataType(checkBoxListPropertyEditor, serializer) - { - Id = 1, - }; - dataType.ConfigurationData = dataType.Editor!.GetConfigurationEditor() - .FromConfigurationObject( - new ValueListConfiguration - { - Items = ["Value 1", "Value 2", "Value 3"] - }, - serializer); + Mock.Of<IIOHelper>(), + serializer); + var dataType = CreateAndConfigureDataType(serializer, checkBoxListPropertyEditor); var configuration = dataType.ConfigurationObject as ValueListConfiguration; Assert.NotNull(configuration); @@ -105,13 +82,7 @@ public void DropDownValueEditor_Format_Data_For_Cache() .Setup(x => x.GetDataType(It.IsAny<int>())) .Returns(dataType); - // TODO use builders instead of this mess - var multipleValueEditor = new MultipleValueEditor( - Mock.Of<ILocalizedTextService>(), - Mock.Of<IShortStringHelper>(), - Mock.Of<IJsonSerializer>(), - Mock.Of<IIOHelper>(), - new DataEditorAttribute(Constants.PropertyEditors.Aliases.TextBox)); + var multipleValueEditor = CreateValueEditor(); dataValueEditorFactoryMock .Setup(x => x.Create<MultipleValueEditor>(It.IsAny<DataEditorAttribute>())) .Returns(multipleValueEditor); @@ -125,14 +96,15 @@ public void DropDownValueEditor_Format_Data_For_Cache() } [Test] - public void DropDownPreValueEditor_Format_Data_For_Editor() + public void MultipleValueEditor_Format_Data_For_Editor() { var dataValueEditorFactoryMock = new Mock<IDataValueEditorFactory>(); var serializer = new SystemTextConfigurationEditorJsonSerializer(); var checkBoxListPropertyEditor = new CheckBoxListPropertyEditor( dataValueEditorFactoryMock.Object, - Mock.Of<IIOHelper>(), serializer); + Mock.Of<IIOHelper>(), + serializer); var dataType = new DataType(checkBoxListPropertyEditor, serializer) { Id = 1, @@ -155,4 +127,62 @@ public void DropDownPreValueEditor_Format_Data_For_Editor() Assert.AreEqual("Item 2", result.Items[1]); Assert.AreEqual("Item 3", result.Items[2]); } + + [TestCase("Red", true, "")] + [TestCase("Yellow", false, "notOneOfOptions")] + [TestCase("Red,Green", true, "")] + [TestCase("Red,Yellow,Purple", false, "multipleNotOneOfOptions")] + public void MultipleValueEditor_Validates_Single_Value(string values, bool expectedSuccess, string expectedValidationMessageKey) + { + var editor = CreateValueEditor(); + editor.ConfigurationObject = new ValueListConfiguration + { + Items = ["Red", "Green", "Blue"], + }; + var result = editor.Validate(values.Split(','), false, null, PropertyValidationContext.Empty()); + if (expectedSuccess) + { + Assert.IsEmpty(result); + } + else + { + Assert.AreEqual(1, result.Count()); + + var validationResult = result.First(); + Assert.AreEqual($"validation_{expectedValidationMessageKey}", validationResult.ErrorMessage); + } + } + + private static MultipleValueEditor CreateValueEditor() + { + var localizedTextServiceMock = new Mock<ILocalizedTextService>(); + localizedTextServiceMock.Setup(x => x.Localize( + It.IsAny<string>(), + It.IsAny<string>(), + It.IsAny<CultureInfo>(), + It.IsAny<IDictionary<string, string>>())) + .Returns((string key, string alias, CultureInfo culture, IDictionary<string, string> args) => $"{key}_{alias}"); + return new( + localizedTextServiceMock.Object, + Mock.Of<IShortStringHelper>(), + Mock.Of<IJsonSerializer>(), + Mock.Of<IIOHelper>(), + new DataEditorAttribute(Constants.PropertyEditors.Aliases.CheckBoxList)); + } + + private static DataType CreateAndConfigureDataType(SystemTextConfigurationEditorJsonSerializer serializer, CheckBoxListPropertyEditor checkBoxListPropertyEditor) + { + var dataType = new DataType(checkBoxListPropertyEditor, serializer) + { + Id = 1, + }; + dataType.ConfigurationData = dataType.Editor!.GetConfigurationEditor() + .FromConfigurationObject( + new ValueListConfiguration + { + Items = ["Value 1", "Value 2", "Value 3"] + }, + serializer); + return dataType; + } } diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/MultipleTextStringValueEditorTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/MultipleTextStringPropertyValueEditorTests.cs similarity index 55% rename from tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/MultipleTextStringValueEditorTests.cs rename to tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/MultipleTextStringPropertyValueEditorTests.cs index 94452a2ec46b..c3a814075a54 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/MultipleTextStringValueEditorTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/MultipleTextStringPropertyValueEditorTests.cs @@ -1,9 +1,12 @@ -using Moq; +using System.Globalization; +using System.Text.Json.Nodes; +using Moq; using NUnit.Framework; using Umbraco.Cms.Core; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Editors; +using Umbraco.Cms.Core.Models.Validation; using Umbraco.Cms.Core.PropertyEditors; using Umbraco.Cms.Core.Serialization; using Umbraco.Cms.Core.Services; @@ -12,7 +15,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.PropertyEditors; [TestFixture] -public class MultipleTextStringValueEditorTests +public class MultipleTextStringPropertyValueEditorTests { [Test] public void Can_Handle_Invalid_Values_From_Editor() @@ -114,6 +117,71 @@ public void Null_To_Editor_Yields_Empty_Collection() Assert.IsEmpty(result); } + [Test] + public void Validates_Null_As_Below_Configured_Min() + { + var editor = CreateValueEditor(); + var result = editor.Validate(null, false, null, PropertyValidationContext.Empty()); + Assert.AreEqual(1, result.Count()); + + var validationResult = result.First(); + Assert.AreEqual($"validation_outOfRangeMultipleItemsMinimum", validationResult.ErrorMessage); + } + + [TestCase(0, false, "outOfRangeMultipleItemsMinimum")] + [TestCase(1, false, "outOfRangeSingleItemMinimum")] + [TestCase(2, true, "")] + [TestCase(3, true, "")] + public void Validates_Number_Of_Items_Is_Greater_Than_Or_Equal_To_Configured_Min(int numberOfStrings, bool expectedSuccess, string expectedValidationMessageKey) + { + var value = Enumerable.Range(1, numberOfStrings).Select(x => x.ToString()); + var editor = CreateValueEditor(); + var result = editor.Validate(value, false, null, PropertyValidationContext.Empty()); + if (expectedSuccess) + { + Assert.IsEmpty(result); + } + else + { + Assert.AreEqual(1, result.Count()); + + var validationResult = result.First(); + Assert.AreEqual($"validation_{expectedValidationMessageKey}", validationResult.ErrorMessage); + } + } + + [TestCase(3, true)] + [TestCase(4, true)] + [TestCase(5, false)] + public void Validates_Number_Of_Items_Is_Less_Than_Or_Equal_To_Configured_Max(int numberOfStrings, bool expectedSuccess) + { + var value = Enumerable.Range(1, numberOfStrings).Select(x => x.ToString()); + var editor = CreateValueEditor(); + var result = editor.Validate(value, false, null, PropertyValidationContext.Empty()); + if (expectedSuccess) + { + Assert.IsEmpty(result); + } + else + { + Assert.AreEqual(1, result.Count()); + + var validationResult = result.First(); + Assert.AreEqual("validation_outOfRangeMultipleItemsMaximum", validationResult.ErrorMessage); + } + } + + [Test] + public void Max_Item_Validation_Respects_0_As_Unlimited() + { + var value = Enumerable.Range(1, 100).Select(x => x.ToString()); + var editor = CreateValueEditor(); + editor.ConfigurationObject = new MultipleTextStringConfiguration(); + + var result = editor.Validate(value, false, null, PropertyValidationContext.Empty()); + Assert.IsEmpty(result); + } + private static object? FromEditor(object? value, int max = 0) => CreateValueEditor().FromEditor(new ContentPropertyData(value, new MultipleTextStringConfiguration { Max = max }), null); @@ -129,11 +197,25 @@ public void Null_To_Editor_Yields_Empty_Collection() private static MultipleTextStringPropertyEditor.MultipleTextStringPropertyValueEditor CreateValueEditor() { - var valueEditor = new MultipleTextStringPropertyEditor.MultipleTextStringPropertyValueEditor( + var localizedTextServiceMock = new Mock<ILocalizedTextService>(); + localizedTextServiceMock.Setup(x => x.Localize( + It.IsAny<string>(), + It.IsAny<string>(), + It.IsAny<CultureInfo>(), + It.IsAny<IDictionary<string, string>>())) + .Returns((string key, string alias, CultureInfo culture, IDictionary<string, string> args) => $"{key}_{alias}"); + return new MultipleTextStringPropertyEditor.MultipleTextStringPropertyValueEditor( Mock.Of<IShortStringHelper>(), Mock.Of<IJsonSerializer>(), Mock.Of<IIOHelper>(), - new DataEditorAttribute("alias")); - return valueEditor; + new DataEditorAttribute("alias"), + localizedTextServiceMock.Object) + { + ConfigurationObject = new MultipleTextStringConfiguration + { + Min = 2, + Max = 4 + }, + }; } } diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/RadioButtonsPropertyValueEditorTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/RadioButtonsPropertyValueEditorTests.cs index a94655b6bf94..4b36a8cc73b7 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/RadioButtonsPropertyValueEditorTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/RadioButtonsPropertyValueEditorTests.cs @@ -28,7 +28,7 @@ public void Validates_Is_One_Of_Options(object value, bool expectedSuccess) Assert.AreEqual(1, result.Count()); var validationResult = result.First(); - Assert.AreEqual(validationResult.ErrorMessage, "validation_notOneOfOptions"); + Assert.AreEqual("validation_notOneOfOptions", validationResult.ErrorMessage); } } diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/SliderValueEditorTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/SliderPropertyValueEditorTests.cs similarity index 84% rename from tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/SliderValueEditorTests.cs rename to tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/SliderPropertyValueEditorTests.cs index 7adda7a525d3..e677fb16c71f 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/SliderValueEditorTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/SliderPropertyValueEditorTests.cs @@ -8,7 +8,6 @@ using Umbraco.Cms.Core.Models.Editors; using Umbraco.Cms.Core.Models.Validation; using Umbraco.Cms.Core.PropertyEditors; -using Umbraco.Cms.Core.Serialization; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Strings; using Umbraco.Cms.Infrastructure.Serialization; @@ -16,7 +15,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.PropertyEditors; [TestFixture] -public class SliderValueEditorTests +public class SliderPropertyValueEditorTests { #pragma warning disable IDE1006 // Naming Styles public static object[] InvalidCaseData = new object[] @@ -127,7 +126,7 @@ public void Validates_Contains_Range_Only_When_Enabled(bool enableRange, decimal Assert.AreEqual(1, result.Count()); var validationResult = result.First(); - Assert.AreEqual(validationResult.ErrorMessage, "validation_unexpectedRange"); + Assert.AreEqual("validation_unexpectedRange", validationResult.ErrorMessage); } } @@ -152,7 +151,7 @@ public void Validates_Contains_Valid_Range_Only_When_Enabled(decimal from, decim Assert.AreEqual(1, result.Count()); var validationResult = result.First(); - Assert.AreEqual(validationResult.ErrorMessage, "validation_invalidRange"); + Assert.AreEqual("validation_invalidRange", validationResult.ErrorMessage); } } @@ -177,7 +176,7 @@ public void Validates_Is_Greater_Than_Or_Equal_To_Configured_Min(decimal from, d Assert.AreEqual(1, result.Count()); var validationResult = result.First(); - Assert.AreEqual(validationResult.ErrorMessage, "validation_outOfRangeMinimum"); + Assert.AreEqual("validation_outOfRangeMinimum", validationResult.ErrorMessage); } } @@ -202,21 +201,37 @@ public void Validates_Is_Less_Than_Or_Equal_To_Configured_Max(decimal from, deci Assert.AreEqual(1, result.Count()); var validationResult = result.First(); - Assert.AreEqual(validationResult.ErrorMessage, "validation_outOfRangeMaximum"); + Assert.AreEqual("validation_outOfRangeMaximum", validationResult.ErrorMessage); } } - [TestCase(1.3, 1.7, true)] - [TestCase(1.4, 1.7, false)] - [TestCase(1.3, 1.6, false)] - public void Validates_Matches_Configured_Step(decimal from, decimal to, bool expectedSuccess) + [Test] + public void Max_Item_Validation_Respects_0_As_Unlimited() + { + var value = new JsonObject + { + { "from", 1.0m }, + { "to", 1.0m }, + }; + var editor = CreateValueEditor(); + editor.ConfigurationObject = new SliderConfiguration(); + + var result = editor.Validate(value, false, null, PropertyValidationContext.Empty()); + Assert.IsEmpty(result); + } + + [TestCase(0.2, 1.3, 1.7, true)] + [TestCase(0.2, 1.4, 1.7, false)] + [TestCase(0.2, 1.3, 1.6, false)] + [TestCase(0.0, 1.4, 1.7, true)] // A step of zero would trigger a divide by zero error in evaluating. So we always pass validation for zero, as effectively any step value is valid. + public void Validates_Matches_Configured_Step(decimal step, decimal from, decimal to, bool expectedSuccess) { var value = new JsonObject { { "from", from }, { "to", to }, }; - var editor = CreateValueEditor(); + var editor = CreateValueEditor(step: step); var result = editor.Validate(value, false, null, PropertyValidationContext.Empty()); if (expectedSuccess) { @@ -227,7 +242,7 @@ public void Validates_Matches_Configured_Step(decimal from, decimal to, bool exp Assert.AreEqual(1, result.Count()); var validationResult = result.First(); - Assert.AreEqual(validationResult.ErrorMessage, "validation_invalidStep"); + Assert.AreEqual("validation_invalidStep", validationResult.ErrorMessage); } } @@ -244,7 +259,7 @@ public void Validates_Matches_Configured_Step(decimal from, decimal to, bool exp return CreateValueEditor().ToEditor(property.Object); } - private static SliderPropertyEditor.SliderPropertyValueEditor CreateValueEditor(bool enableRange = true) + private static SliderPropertyEditor.SliderPropertyValueEditor CreateValueEditor(bool enableRange = true, decimal step = 0.2m) { var localizedTextServiceMock = new Mock<ILocalizedTextService>(); localizedTextServiceMock.Setup(x => x.Localize( @@ -265,7 +280,7 @@ private static SliderPropertyEditor.SliderPropertyValueEditor CreateValueEditor( EnableRange = enableRange, MinimumValue = 1.1m, MaximumValue = 1.9m, - Step = 0.2m + Step = step }, }; } diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/Validators/BlockListValueRequiredValidatorTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/Validators/BlockListValueRequiredValidatorTests.cs new file mode 100644 index 000000000000..3256f9d15db9 --- /dev/null +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/Validators/BlockListValueRequiredValidatorTests.cs @@ -0,0 +1,35 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Text.Json.Nodes; +using NUnit.Framework; +using Umbraco.Cms.Core.Models.Blocks; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Infrastructure.PropertyEditors.Validators; +using Umbraco.Cms.Infrastructure.Serialization; + +namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.PropertyEditors; + +[TestFixture] +public class BlockListValueRequiredValidatorTests +{ + [Test] + public void Validates_Empty_Block_List_As_Not_Provided() + { + var validator = new BlockListValueRequiredValidator(new SystemTextJsonSerializer()); + + var value = JsonNode.Parse("{ \"contentData\": [], \"settingsData\": [] }"); + var result = validator.ValidateRequired(value, ValueTypes.Json); + Assert.AreEqual(1, result.Count()); + } + + [Test] + public void Validates_Populated_Block_List_As_Provided() + { + var validator = new BlockListValueRequiredValidator(new SystemTextJsonSerializer()); + + var value = JsonNode.Parse("{ \"contentData\": [ {} ], \"settingsData\": [] }"); + var result = validator.ValidateRequired(value, ValueTypes.Json); + Assert.IsEmpty(result); + } +} diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/Validators/MultiUrlPickerValueEditorValidationTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/Validators/MultiUrlPickerValueEditorValidationTests.cs new file mode 100644 index 000000000000..c2d4120ef3a6 --- /dev/null +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/Validators/MultiUrlPickerValueEditorValidationTests.cs @@ -0,0 +1,75 @@ +using System.ComponentModel.DataAnnotations; +using Microsoft.Extensions.Logging; +using Moq; +using NUnit.Framework; +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.Models.Validation; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Core.Routing; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Strings; +using Umbraco.Cms.Infrastructure.Serialization; + +namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.PropertyEditors.Validators; + +[TestFixture] +internal class MultiUrlPickerValueEditorValidationTests +{ + [TestCase(1, true, "[{\"icon\":\"icon-document\",\"name\":\"Page 1\",\"published\":true,\"queryString\":null,\"target\":null,\"trashed\":false,\"type\":\"document\",\"unique\":\"7d285be2-7cd5-4c7b-a252-b064e31f049f\",\"url\":\"/\"}]")] + [TestCase(2, false, "[{\"icon\":\"icon-document\",\"name\":\"Page 1\",\"published\":true,\"queryString\":null,\"target\":null,\"trashed\":false,\"type\":\"document\",\"unique\":\"7d285be2-7cd5-4c7b-a252-b064e31f049f\",\"url\":\"/\"}]")] + [TestCase(1, true, "[{\"icon\":\"icon-document\",\"name\":\"Page 1\",\"published\":true,\"queryString\":null,\"target\":null,\"trashed\":false,\"type\":\"document\",\"unique\":\"7d285be2-7cd5-4c7b-a252-b064e31f049f\",\"url\":\"/\"},{\"icon\":\"icon-document\",\"name\":\"Page 1\",\"published\":true,\"queryString\":null,\"target\":null,\"trashed\":false,\"type\":\"document\",\"unique\":\"7d285be2-7cd5-4c7b-a252-b064e31f049f\",\"url\":\"/\"}]")] + [TestCase(3, false, "[{\"icon\":\"icon-document\",\"name\":\"Page 1\",\"published\":true,\"queryString\":null,\"target\":null,\"trashed\":false,\"type\":\"document\",\"unique\":\"7d285be2-7cd5-4c7b-a252-b064e31f049f\",\"url\":\"/\"},{\"icon\":\"icon-document\",\"name\":\"Page 1\",\"published\":true,\"queryString\":null,\"target\":null,\"trashed\":false,\"type\":\"document\",\"unique\":\"7d285be2-7cd5-4c7b-a252-b064e31f049f\",\"url\":\"/\"}]")] + [TestCase(1, false, "[]")] + [TestCase(1, false, null)] + public void Validates_Min_Limit(int min, bool succeed, string? value) + { + var picker = CreateValueEditor(); + + picker.ConfigurationObject = new MultiUrlPickerConfiguration() { MinNumber = min }; + + var result = picker.Validate(value, false, null, PropertyValidationContext.Empty()); + ValidateResult(succeed, result); + } + + [TestCase(1, true, "[{\"icon\":\"icon-document\",\"name\":\"Page 1\",\"published\":true,\"queryString\":null,\"target\":null,\"trashed\":false,\"type\":\"document\",\"unique\":\"7d285be2-7cd5-4c7b-a252-b064e31f049f\",\"url\":\"/\"}]")] + [TestCase(1, false, "[{\"icon\":\"icon-document\",\"name\":\"Page 1\",\"published\":true,\"queryString\":null,\"target\":null,\"trashed\":false,\"type\":\"document\",\"unique\":\"7d285be2-7cd5-4c7b-a252-b064e31f049f\",\"url\":\"/\"},{\"icon\":\"icon-document\",\"name\":\"Page 1\",\"published\":true,\"queryString\":null,\"target\":null,\"trashed\":false,\"type\":\"document\",\"unique\":\"7d285be2-7cd5-4c7b-a252-b064e31f049f\",\"url\":\"/\"}]")] + [TestCase(3, true, "[{\"icon\":\"icon-document\",\"name\":\"Page 1\",\"published\":true,\"queryString\":null,\"target\":null,\"trashed\":false,\"type\":\"document\",\"unique\":\"7d285be2-7cd5-4c7b-a252-b064e31f049f\",\"url\":\"/\"},{\"icon\":\"icon-document\",\"name\":\"Page 1\",\"published\":true,\"queryString\":null,\"target\":null,\"trashed\":false,\"type\":\"document\",\"unique\":\"7d285be2-7cd5-4c7b-a252-b064e31f049f\",\"url\":\"/\"}]")] + [TestCase(1, true, "[]")] + [TestCase(1, true, null)] + public void Validates_Max_Limit(int max, bool succeed, string? value) + { + var picker = CreateValueEditor(); + + picker.ConfigurationObject = new MultiUrlPickerConfiguration() { MaxNumber = max }; + + var result = picker.Validate(value, false, null, PropertyValidationContext.Empty()); + ValidateResult(succeed, result); + } + + private static void ValidateResult(bool succeed, IEnumerable<ValidationResult> result) + { + if (succeed) + { + Assert.IsEmpty(result); + } + else + { + Assert.That(result.Count(), Is.EqualTo(1)); + } + } + + private static MultiUrlPickerValueEditor CreateValueEditor() => + new( + Mock.Of<ILogger<MultiUrlPickerValueEditor>>(), + Mock.Of<ILocalizedTextService>(), + Mock.Of<IShortStringHelper>(), + new DataEditorAttribute("alias"), + Mock.Of<IPublishedUrlProvider>(), + new SystemTextJsonSerializer(), + Mock.Of<IIOHelper>(), + Mock.Of<IContentService>(), + Mock.Of<IMediaService>()) + { + ConfigurationObject = new MultiUrlPickerConfiguration(), + }; +} diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/Validators/RequiredValidatorTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/Validators/RequiredValidatorTests.cs new file mode 100644 index 000000000000..d2071472abe1 --- /dev/null +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/Validators/RequiredValidatorTests.cs @@ -0,0 +1,66 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.ComponentModel.DataAnnotations; +using NUnit.Framework; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Core.PropertyEditors.Validators; + +namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.PropertyEditors.Validators; + +[TestFixture] +public class RequiredValidatorTests +{ + [Test] + public void Validates_Null() + { + var validator = new RequiredValidator(); + var result = validator.ValidateRequired(null, ValueTypes.String); + AssertValidationFailed(result, expectedMessage: Constants.Validation.ErrorMessages.Properties.Missing); + } + + [TestCase("", false)] + [TestCase(" ", false)] + [TestCase("a", true)] + public void Validates_Strings(string value, bool expectedSuccess) + { + var validator = new RequiredValidator(); + var result = validator.ValidateRequired(value, ValueTypes.String); + if (expectedSuccess) + { + Assert.IsEmpty(result); + } + else + { + AssertValidationFailed(result); + } + } + + [TestCase("{}", false)] + [TestCase("[]", false)] + [TestCase("{ }", false)] + [TestCase("[ ]", false)] + [TestCase(" { } ", false)] + [TestCase(" [ ] ", false)] + [TestCase(" { \"foo\": \"bar\" } ", true)] + public void Validates_Json(string value, bool expectedSuccess) + { + var validator = new RequiredValidator(); + var result = validator.ValidateRequired(value, ValueTypes.Json); + if (expectedSuccess) + { + Assert.IsEmpty(result); + } + else + { + AssertValidationFailed(result); + } + } + + private static void AssertValidationFailed(IEnumerable<ValidationResult> result, string expectedMessage = Constants.Validation.ErrorMessages.Properties.Empty) + { + Assert.AreEqual(1, result.Count()); + Assert.AreEqual(expectedMessage, result.First().ErrorMessage); + } +} diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/Validators/TextOnlyValueEditorValidatorTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/Validators/TextOnlyValueEditorValidatorTests.cs new file mode 100644 index 000000000000..14fa669b720b --- /dev/null +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/Validators/TextOnlyValueEditorValidatorTests.cs @@ -0,0 +1,66 @@ +using System.ComponentModel.DataAnnotations; +using Moq; +using NUnit.Framework; +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.Models.Validation; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Strings; +using Umbraco.Cms.Infrastructure.Serialization; + +namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.PropertyEditors.Validators; + +[TestFixture] +internal class TextOnlyValueEditorValidatorTests +{ + internal enum ConfigurationType + { + TextAreaConfiguration, + TextboxConfiguration, + } + + [TestCase(true, ConfigurationType.TextboxConfiguration, null, "123")] + [TestCase(true, ConfigurationType.TextAreaConfiguration, null, "123")] + [TestCase(false, ConfigurationType.TextAreaConfiguration, 2, "123")] + [TestCase(false, ConfigurationType.TextboxConfiguration, 2, "123")] + [TestCase(true, ConfigurationType.TextboxConfiguration, 10, "123")] + [TestCase(true, ConfigurationType.TextAreaConfiguration, 10, "123")] + public void Validates_String_Length(bool shouldSucceed, ConfigurationType configurationType, int? maxChars, string value) + { + var editor = CreateValueEditor(); + + editor.ConfigurationObject = CreateConfiguration(configurationType, maxChars); + + var results = editor.Validate(value, false, null, PropertyValidationContext.Empty()); + + ValidateResult(shouldSucceed, results); + } + + private static object CreateConfiguration(ConfigurationType type, int? maxChars) => + type switch + { + ConfigurationType.TextboxConfiguration => new TextboxConfiguration { MaxChars = maxChars }, + ConfigurationType.TextAreaConfiguration => new TextAreaConfiguration { MaxChars = maxChars }, + _ => throw new InvalidOperationException(), + }; + + private static void ValidateResult(bool succeed, IEnumerable<ValidationResult> result) + { + if (succeed) + { + Assert.IsEmpty(result); + } + else + { + Assert.IsNotEmpty(result); + } + } + + private TextOnlyValueEditor CreateValueEditor() => + new( + new DataEditorAttribute("alias"), + Mock.Of<ILocalizedTextService>(), + Mock.Of<IShortStringHelper>(), + new SystemTextJsonSerializer(), + Mock.Of<IIOHelper>()); +} diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/Validators/TrueFalseValueRequiredValidatorTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/Validators/TrueFalseValueRequiredValidatorTests.cs new file mode 100644 index 000000000000..a7dfa7fe7b47 --- /dev/null +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/Validators/TrueFalseValueRequiredValidatorTests.cs @@ -0,0 +1,39 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using NUnit.Framework; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Infrastructure.PropertyEditors.Validators; + +namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.PropertyEditors; + +[TestFixture] +public class TrueFalseValueRequiredValidatorTests +{ + [Test] + public void Validates_Null_Value_As_Not_Provided() + { + var validator = new TrueFalseValueRequiredValidator(); + + var result = validator.ValidateRequired(null, ValueTypes.Integer); + Assert.AreEqual(1, result.Count()); + } + + [Test] + public void Validates_False_Value_As_Not_Provided() + { + var validator = new TrueFalseValueRequiredValidator(); + + var result = validator.ValidateRequired(false, ValueTypes.Integer); + Assert.AreEqual(1, result.Count()); + } + + [Test] + public void Validates_True_Value_As_Provided() + { + var validator = new TrueFalseValueRequiredValidator(); + + var result = validator.ValidateRequired(true, ValueTypes.Integer); + Assert.IsEmpty(result); + } +} diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/UmbracoRequestPathsTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/UmbracoRequestPathsTests.cs index 3d17117924d9..213b527b81d0 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/UmbracoRequestPathsTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/UmbracoRequestPathsTests.cs @@ -12,26 +12,22 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Routing; [TestFixture] public class UmbracoRequestPathsTests { + private IWebHostEnvironment _hostEnvironment; + private UmbracoRequestPathsOptions _umbracoRequestPathsOptions; + [OneTimeSetUp] public void Setup() { _hostEnvironment = Mock.Of<IWebHostEnvironment>(); - _globalSettings = new GlobalSettings(); _umbracoRequestPathsOptions = new UmbracoRequestPathsOptions(); } - private IWebHostEnvironment _hostEnvironment; - private GlobalSettings _globalSettings; - private UmbracoRequestPathsOptions _umbracoRequestPathsOptions; - private IHostingEnvironment CreateHostingEnvironment(string virtualPath = "") { var hostingSettings = new HostingSettings { ApplicationVirtualPath = virtualPath }; var webRoutingSettings = new WebRoutingSettings(); - var mockedOptionsMonitorOfHostingSettings = - Mock.Of<IOptionsMonitor<HostingSettings>>(x => x.CurrentValue == hostingSettings); - var mockedOptionsMonitorOfWebRoutingSettings = - Mock.Of<IOptionsMonitor<WebRoutingSettings>>(x => x.CurrentValue == webRoutingSettings); + var mockedOptionsMonitorOfHostingSettings = Mock.Of<IOptionsMonitor<HostingSettings>>(x => x.CurrentValue == hostingSettings); + var mockedOptionsMonitorOfWebRoutingSettings = Mock.Of<IOptionsMonitor<WebRoutingSettings>>(x => x.CurrentValue == webRoutingSettings); return new TestHostingEnvironment( mockedOptionsMonitorOfHostingSettings, @@ -50,7 +46,7 @@ private IHostingEnvironment CreateHostingEnvironment(string virtualPath = "") public void Is_Client_Side_Request(string url, bool assert) { var hostingEnvironment = CreateHostingEnvironment(); - var umbracoRequestPaths = new UmbracoRequestPaths(Options.Create(_globalSettings), hostingEnvironment, Options.Create(_umbracoRequestPathsOptions)); + var umbracoRequestPaths = new UmbracoRequestPaths(hostingEnvironment, Options.Create(_umbracoRequestPathsOptions)); var uri = new Uri("http://test.com" + url); var result = umbracoRequestPaths.IsClientSideRequest(uri.AbsolutePath); @@ -61,7 +57,7 @@ public void Is_Client_Side_Request(string url, bool assert) public void Is_Client_Side_Request_InvalidPath_ReturnFalse() { var hostingEnvironment = CreateHostingEnvironment(); - var umbracoRequestPaths = new UmbracoRequestPaths(Options.Create(_globalSettings), hostingEnvironment, Options.Create(_umbracoRequestPathsOptions)); + var umbracoRequestPaths = new UmbracoRequestPaths(hostingEnvironment, Options.Create(_umbracoRequestPathsOptions)); // This URL is invalid. Default to false when the extension cannot be determined var uri = new Uri("http://test.com/installing-modules+foobar+\"yipee\""); @@ -93,7 +89,7 @@ public void Is_Back_Office_Request(string input, string virtualPath, bool expect { var source = new Uri(input); var hostingEnvironment = CreateHostingEnvironment(virtualPath); - var umbracoRequestPaths = new UmbracoRequestPaths(Options.Create(_globalSettings), hostingEnvironment, Options.Create(_umbracoRequestPathsOptions)); + var umbracoRequestPaths = new UmbracoRequestPaths(hostingEnvironment, Options.Create(_umbracoRequestPathsOptions)); Assert.AreEqual(expected, umbracoRequestPaths.IsBackOfficeRequest(source.AbsolutePath)); } @@ -105,9 +101,11 @@ public void Force_Back_Office_Request_With_Request_Paths_Options(string input, b { var source = new Uri(input); var hostingEnvironment = CreateHostingEnvironment(); - var umbracoRequestPathsOptions = new UmbracoRequestPathsOptions(); - umbracoRequestPathsOptions.IsBackOfficeRequest = _ => true; - var umbracoRequestPaths = new UmbracoRequestPaths(Options.Create(_globalSettings), hostingEnvironment, Options.Create(umbracoRequestPathsOptions)); + var umbracoRequestPathsOptions = new UmbracoRequestPathsOptions + { + IsBackOfficeRequest = _ => true + }; + var umbracoRequestPaths = new UmbracoRequestPaths(hostingEnvironment, Options.Create(umbracoRequestPathsOptions)); Assert.AreEqual(expected, umbracoRequestPaths.IsBackOfficeRequest(source.AbsolutePath)); } } diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Services/BasicAuthServiceTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Services/BasicAuthServiceTests.cs index 115d830040b5..798814eb2a32 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Services/BasicAuthServiceTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Services/BasicAuthServiceTests.cs @@ -32,10 +32,12 @@ public bool IsBasicAuthEnabled(bool enabled) [TestCase("125.125.124.1", "125.125.125.0/24", ExpectedResult = false)] public bool IsIpAllowListed(string clientIpAddress, string commaSeperatedAllowlist) { - var allowedIPs = commaSeperatedAllowlist.Split(",").Select(x => x.Trim()).ToArray(); var sut = new BasicAuthService( - Mock.Of<IOptionsMonitor<BasicAuthSettings>>(_ => - _.CurrentValue == new BasicAuthSettings { AllowedIPs = allowedIPs }), + Mock.Of<IOptionsMonitor<BasicAuthSettings>>(x => + x.CurrentValue == new BasicAuthSettings + { + AllowedIPs = new HashSet<string>(commaSeperatedAllowlist.Split(',', StringSplitOptions.TrimEntries)) + }), new IpAddressUtilities()); return sut.IsIpAllowListed(IPAddress.Parse(clientIpAddress)); diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/ShortStringHelper/DefaultShortStringHelperTestsWithoutSetup.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/ShortStringHelper/DefaultShortStringHelperTestsWithoutSetup.cs index 61da325e5b09..30ab94f3f24b 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/ShortStringHelper/DefaultShortStringHelperTestsWithoutSetup.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/ShortStringHelper/DefaultShortStringHelperTestsWithoutSetup.cs @@ -17,7 +17,6 @@ public void U4_4056() { var requestHandlerSettings = new RequestHandlerSettings { - UserDefinedCharCollection = Array.Empty<CharItem>(), EnableDefaultCharReplacements = false, ConvertUrlsToAscii = "false", }; @@ -46,7 +45,6 @@ public void U4_4056_TryAscii() { var requestHandlerSettings = new RequestHandlerSettings { - UserDefinedCharCollection = Array.Empty<CharItem>(), EnableDefaultCharReplacements = false, ConvertUrlsToAscii = "false", }; @@ -382,7 +380,6 @@ public void CleanStringDefaultConfig() { var requestHandlerSettings = new RequestHandlerSettings { - UserDefinedCharCollection = Array.Empty<CharItem>(), EnableDefaultCharReplacements = false, ConvertUrlsToAscii = "false", }; diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/BackgroundJobs/Jobs/HealthCheckNotifierJobTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/BackgroundJobs/Jobs/HealthCheckNotifierJobTests.cs index 9eb6862bfc52..701f5c1d4380 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/BackgroundJobs/Jobs/HealthCheckNotifierJobTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/BackgroundJobs/Jobs/HealthCheckNotifierJobTests.cs @@ -136,6 +136,6 @@ private class TestHealthCheck : HealthCheck { public override HealthCheckStatus ExecuteAction(HealthCheckAction action) => new("Check message"); - public override async Task<IEnumerable<HealthCheckStatus>> GetStatusAsync() => Enumerable.Empty<HealthCheckStatus>(); + public override Task<IEnumerable<HealthCheckStatus>> GetStatusAsync() => Task.FromResult(Enumerable.Empty<HealthCheckStatus>()); } } diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Examine/UmbracoContentValueSetValidatorTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Examine/UmbracoContentValueSetValidatorTests.cs index 9bfa09218011..97c866a604dd 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Examine/UmbracoContentValueSetValidatorTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Examine/UmbracoContentValueSetValidatorTests.cs @@ -1,16 +1,14 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Collections.Generic; -using System.Linq; using Examine; using Moq; using NUnit.Framework; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Infrastructure.Examine; +using Umbraco.Cms.Infrastructure.Scoping; namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Examine; @@ -24,7 +22,10 @@ public void Invalid_Category() false, true, Mock.Of<IPublicAccessService>(), - Mock.Of<IScopeProvider>()); + Mock.Of<IScopeProvider>(), + null, + null, + null); var result = validator.Validate(ValueSet.FromObject("555", IndexTypes.Content, new { hello = "world", path = "-1,555" })); @@ -48,7 +49,10 @@ public void Must_Have_Path() false, true, Mock.Of<IPublicAccessService>(), - Mock.Of<IScopeProvider>()); + Mock.Of<IScopeProvider>(), + null, + null, + null); var result = validator.Validate(ValueSet.FromObject("555", IndexTypes.Content, new { hello = "world" })); Assert.AreEqual(ValueSetValidationStatus.Failed, result.Status); @@ -68,7 +72,9 @@ public void Parent_Id() true, Mock.Of<IPublicAccessService>(), Mock.Of<IScopeProvider>(), - 555); + 555, + null, + null); var result = validator.Validate(ValueSet.FromObject("555", IndexTypes.Content, new { hello = "world", path = "-1,555" })); @@ -170,7 +176,9 @@ public void Inclusion_Type_List() true, Mock.Of<IPublicAccessService>(), Mock.Of<IScopeProvider>(), - includeItemTypes: new List<string> { "include-content" }); + null, + new List<string> { "include-content" }, + null); var result = validator.Validate(ValueSet.FromObject( "555", @@ -201,7 +209,9 @@ public void Exclusion_Type_List() true, Mock.Of<IPublicAccessService>(), Mock.Of<IScopeProvider>(), - excludeItemTypes: new List<string> { "exclude-content" }); + null, + null, + new List<string> { "exclude-content" }); var result = validator.Validate(ValueSet.FromObject( "555", @@ -232,8 +242,9 @@ public void Inclusion_Exclusion_Type_List() true, Mock.Of<IPublicAccessService>(), Mock.Of<IScopeProvider>(), - includeItemTypes: new List<string> { "include-content", "exclude-content" }, - excludeItemTypes: new List<string> { "exclude-content" }); + null, + new List<string> { "include-content", "exclude-content" }, + new List<string> { "exclude-content" }); var result = validator.Validate(ValueSet.FromObject( "555", @@ -270,7 +281,10 @@ public void Recycle_Bin_Content() true, false, Mock.Of<IPublicAccessService>(), - Mock.Of<IScopeProvider>()); + Mock.Of<IScopeProvider>(), + null, + null, + null); var result = validator.Validate(ValueSet.FromObject( @@ -310,7 +324,10 @@ public void Recycle_Bin_Media() true, false, Mock.Of<IPublicAccessService>(), - Mock.Of<IScopeProvider>()); + Mock.Of<IScopeProvider>(), + null, + null, + null); var result = validator.Validate(ValueSet.FromObject( @@ -337,7 +354,10 @@ public void Published_Only() true, true, Mock.Of<IPublicAccessService>(), - Mock.Of<IScopeProvider>()); + Mock.Of<IScopeProvider>(), + null, + null, + null); var result = validator.Validate(ValueSet.FromObject("555", IndexTypes.Content, new { hello = "world", path = "-1,555" })); @@ -373,7 +393,10 @@ public void Published_Only_With_Variants() true, true, Mock.Of<IPublicAccessService>(), - Mock.Of<IScopeProvider>()); + Mock.Of<IScopeProvider>(), + null, + null, + null); var result = validator.Validate(new ValueSet( "555", @@ -443,7 +466,10 @@ public void Non_Protected() false, false, publicAccessService.Object, - Mock.Of<IScopeProvider>()); + Mock.Of<IScopeProvider>(), + null, + null, + null); var result = validator.Validate(ValueSet.FromObject("555", IndexTypes.Content, new { hello = "world", path = "-1,555" })); diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HealthChecks/HealthCheckResultsTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HealthChecks/HealthCheckResultsTests.cs index 7e986237079f..85169ceedb4b 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HealthChecks/HealthCheckResultsTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HealthChecks/HealthCheckResultsTests.cs @@ -26,8 +26,8 @@ public StubHealthCheck(StatusResultType resultType, string message) public override HealthCheckStatus ExecuteAction(HealthCheckAction action) => throw new NotImplementedException(); - public override async Task<IEnumerable<HealthCheckStatus>> GetStatusAsync() => - new List<HealthCheckStatus> { new(_message) { ResultType = _resultType } }; + public override Task<IEnumerable<HealthCheckStatus>> GetStatusAsync() => + Task.FromResult<IEnumerable<HealthCheckStatus>>(new List<HealthCheckStatus> { new(_message) { ResultType = _resultType } }); } [HealthCheck("CFD6FC34-59C9-4402-B55F-C8BC96B628A1", "Stub check 1")] @@ -56,8 +56,8 @@ public StubHealthCheck3(StatusResultType resultType, string message) { } - public override async Task<IEnumerable<HealthCheckStatus>> GetStatusAsync() => - throw new Exception("Check threw exception"); + public override Task<IEnumerable<HealthCheckStatus>> GetStatusAsync() + => throw new Exception("Check threw exception"); } [Test] diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/AlterMigrationTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/AlterMigrationTests.cs index 516e7f80c146..e8f79d7da7e7 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/AlterMigrationTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/AlterMigrationTests.cs @@ -32,14 +32,14 @@ private MigrationContext GetMigrationContext(out TestDatabase db) } [Test] - public void Drop_Foreign_Key() + public async Task Drop_Foreign_Key() { // Arrange var context = GetMigrationContext(out var database); var stub = new DropForeignKeyMigrationStub(context); // Act - stub.Run(); + await stub.RunAsync().ConfigureAwait(false); foreach (var op in database.Operations) { @@ -48,18 +48,16 @@ public void Drop_Foreign_Key() // Assert Assert.That(database.Operations.Count, Is.EqualTo(1)); - Assert.That( - database.Operations[0].Sql, - Is.EqualTo("ALTER TABLE [umbracoUser2app] DROP CONSTRAINT [FK_umbracoUser2app_umbracoUser_id]")); + Assert.That(database.Operations[0].Sql, Is.EqualTo("ALTER TABLE [umbracoUser2app] DROP CONSTRAINT [FK_umbracoUser2app_umbracoUser_id]")); } [Test] - public void CreateColumn() + public async Task CreateColumn() { var context = GetMigrationContext(out var database); var migration = new CreateColumnMigration(context); - migration.Run(); + await migration.RunAsync().ConfigureAwait(false); foreach (var op in database.Operations) { @@ -83,12 +81,12 @@ public CreateColumnMigration(IMigrationContext context) } [Test] - public void AlterColumn() + public async Task AlterColumn() { var context = GetMigrationContext(out var database); var migration = new AlterColumnMigration(context); - migration.Run(); + await migration.RunAsync().ConfigureAwait(false); foreach (var op in database.Operations) { @@ -117,14 +115,14 @@ protected override void Migrate() => [Ignore("this doesn't actually test anything")] [Test] - public void Can_Get_Up_Migration_From_MigrationStub() + public async Task Can_Get_Up_Migration_From_MigrationStub() { // Arrange var context = GetMigrationContext(out var database); var stub = new AlterUserTableMigrationStub(context); // Act - stub.Run(); + await stub.RunAsync().ConfigureAwait(false); // Assert Assert.That(database.Operations.Any(), Is.True); diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/MigrationPlanTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/MigrationPlanTests.cs index 449272fbf740..261d41128d6d 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/MigrationPlanTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/MigrationPlanTests.cs @@ -30,7 +30,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Migrations; public class MigrationPlanTests { [Test] - public void CanExecute() + public async Task CanExecute() { var loggerFactory = NullLoggerFactory.Instance; @@ -81,7 +81,11 @@ public void CanExecute() loggerFactory, migrationBuilder, databaseFactory, - Mock.Of<IDatabaseCacheRebuilder>(), distributedCache, Mock.Of<IKeyValueService>(), Mock.Of<IServiceScopeFactory>(), appCaches); + Mock.Of<IDatabaseCacheRebuilder>(), + distributedCache, + Mock.Of<IKeyValueService>(), + Mock.Of<IServiceScopeFactory>(), + appCaches); var plan = new MigrationPlan("default") .From(string.Empty) @@ -99,7 +103,7 @@ public void CanExecute() var sourceState = kvs.GetValue("Umbraco.Tests.MigrationPlan") ?? string.Empty; // execute plan - var result = executor.ExecutePlan(plan, sourceState); + var result = await executor.ExecutePlanAsync(plan, sourceState).ConfigureAwait(false); state = result.FinalState; // save new state diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/MigrationTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/MigrationTests.cs index f3ea130a68ac..135e5406eb68 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/MigrationTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/MigrationTests.cs @@ -77,11 +77,11 @@ private MigrationContext GetMigrationContext() => Mock.Of<ILogger<MigrationContext>>()); [Test] - public void RunGoodMigration() + public async Task RunGoodMigration() { var migrationContext = GetMigrationContext(); MigrationBase migration = new GoodMigration(migrationContext); - migration.Run(); + await migration.RunAsync(); } [Test] @@ -89,7 +89,7 @@ public void DetectBadMigration1() { var migrationContext = GetMigrationContext(); MigrationBase migration = new BadMigration1(migrationContext); - Assert.Throws<IncompleteMigrationExpressionException>(() => migration.Run()); + Assert.ThrowsAsync<IncompleteMigrationExpressionException>(migration.RunAsync); } [Test] @@ -97,7 +97,7 @@ public void DetectBadMigration2() { var migrationContext = GetMigrationContext(); MigrationBase migration = new BadMigration2(migrationContext); - Assert.Throws<IncompleteMigrationExpressionException>(() => migration.Run()); + Assert.ThrowsAsync<IncompleteMigrationExpressionException>(migration.RunAsync); } public class GoodMigration : MigrationBase diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Security/MemberManagerTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Security/MemberManagerTests.cs index 4de1ce23470b..7ee021947f67 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Security/MemberManagerTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Security/MemberManagerTests.cs @@ -113,7 +113,7 @@ public async Task GivenICreateUser_AndTheIdentityResultFailed_ThenIShouldGetAFai } [Test] - public async Task GivenICreateUser_AndTheUserIsNull_ThenIShouldGetAFailedResultAsync() + public Task GivenICreateUser_AndTheUserIsNull_ThenIShouldGetAFailedResultAsync() { // arrange var sut = CreateSut(); @@ -124,6 +124,7 @@ public async Task GivenICreateUser_AndTheUserIsNull_ThenIShouldGetAFailedResultA // act Assert.ThrowsAsync<ArgumentNullException>(async () => await sut.CreateAsync(null)); + return Task.CompletedTask; } [Test] diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Security/MemberRoleStoreTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Security/MemberRoleStoreTests.cs index e97b438c052d..d5f34f3a65bd 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Security/MemberRoleStoreTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Security/MemberRoleStoreTests.cs @@ -201,7 +201,7 @@ public async Task } [Test] - public async Task + public Task GivenIDeleteAMemberRole_AndTheIdCannotBeParsedToAnInt_ThenTheMemberGroupShouldNotBeDeleted_AndIShouldGetAnArgumentException() { // arrange @@ -214,6 +214,7 @@ public async Task // act Assert.ThrowsAsync<ArgumentException>(async () => await sut.DeleteAsync(fakeRole, fakeCancellationToken)); + return Task.CompletedTask; } [Test] @@ -263,7 +264,7 @@ public async Task GivenIFindAMemberRoleByRoleKey_AndRoleKeyExists_ThenIShouldGet } [Test] - public async Task GivenIFindAMemberRoleByRoleId_AndIdCannotBeParsedToAnIntOrGuid_ThenIShouldGetAFailureResultAsync() + public Task GivenIFindAMemberRoleByRoleId_AndIdCannotBeParsedToAnIntOrGuid_ThenIShouldGetAFailureResultAsync() { // arrange var sut = CreateSut(); @@ -276,6 +277,7 @@ public async Task GivenIFindAMemberRoleByRoleId_AndIdCannotBeParsedToAnIntOrGuid // assert Assert.That(actual, Throws.TypeOf<ArgumentOutOfRangeException>()); _mockMemberGroupService.VerifyNoOtherCalls(); + return Task.CompletedTask; } [Test] diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Services/PropertyValidationServiceTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Services/PropertyValidationServiceTests.cs index 70c6c95ebeb3..097a0495e5b9 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Services/PropertyValidationServiceTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Services/PropertyValidationServiceTests.cs @@ -279,7 +279,7 @@ public CustomTextOnlyValueEditor( IShortStringHelper shortStringHelper, IJsonSerializer jsonSerializer, IIOHelper ioHelper) - : base(attribute, shortStringHelper, jsonSerializer, ioHelper) + : base(attribute, Mock.Of<ILocalizedTextService>(), shortStringHelper, jsonSerializer, ioHelper) { } diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.ModelsBuilder.Embedded/BuilderTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.ModelsBuilder.Embedded/BuilderTests.cs index 6f3d2a735656..baa49a763243 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.ModelsBuilder.Embedded/BuilderTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.ModelsBuilder.Embedded/BuilderTests.cs @@ -1,8 +1,6 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Collections.Generic; -using System.Linq; using System.Text; using NUnit.Framework; using Umbraco.Cms.Core.Configuration.Models; @@ -118,6 +116,102 @@ public Type1(IPublishedContent content, IPublishedValueFallback publishedValueFa Assert.AreEqual(expected.ClearLf(), gen); } + [Test] + public void GenerateSimpleType_WithoutVersion() + { + // Umbraco returns nice, pascal-cased names. + var type1 = new TypeModel + { + Id = 1, + Alias = "type1", + ClrName = "Type1", + Name = "type1Name", + ParentId = 0, + BaseType = null, + ItemType = TypeModel.ItemTypes.Content, + }; + type1.Properties.Add(new PropertyModel + { + Alias = "prop1", + ClrName = "Prop1", + Name = "prop1Name", + ModelClrType = typeof(string), + }); + + TypeModel[] types = { type1 }; + + var modelsBuilderConfig = new ModelsBuilderSettings { IncludeVersionNumberInGeneratedModels = false }; + var builder = new TextBuilder(modelsBuilderConfig, types); + + var sb = new StringBuilder(); + builder.Generate(sb, builder.GetModelsToGenerate().First()); + var gen = sb.ToString(); + + var expected = @"//------------------------------------------------------------------------------ +// <auto-generated> +// This code was generated by a tool. +// +// Umbraco.ModelsBuilder.Embedded +// +// Changes to this file will be lost if the code is regenerated. +// </auto-generated> +//------------------------------------------------------------------------------ + +using System; +using System.Linq.Expressions; +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.PublishedCache; +using Umbraco.Cms.Infrastructure.ModelsBuilder; +using Umbraco.Cms.Core; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Web.Common.PublishedModels +{ + /// <summary>type1Name</summary> + [PublishedModel(""type1"")] + public partial class Type1 : PublishedContentModel + { + // helpers +#pragma warning disable 0109 // new is redundant + [global::System.CodeDom.Compiler.GeneratedCodeAttribute(""Umbraco.ModelsBuilder.Embedded"", """")] + public new const string ModelTypeAlias = ""type1""; + [global::System.CodeDom.Compiler.GeneratedCodeAttribute(""Umbraco.ModelsBuilder.Embedded"", """")] + public new const PublishedItemType ModelItemType = PublishedItemType.Content; + [global::System.CodeDom.Compiler.GeneratedCodeAttribute(""Umbraco.ModelsBuilder.Embedded"", """")] + [return: global::System.Diagnostics.CodeAnalysis.MaybeNull] + public new static IPublishedContentType GetModelContentType(IPublishedContentTypeCache contentTypeCache) + => PublishedModelUtility.GetModelContentType(contentTypeCache, ModelItemType, ModelTypeAlias); + [global::System.CodeDom.Compiler.GeneratedCodeAttribute(""Umbraco.ModelsBuilder.Embedded"", """")] + [return: global::System.Diagnostics.CodeAnalysis.MaybeNull] + public static IPublishedPropertyType GetModelPropertyType<TValue>(IPublishedContentTypeCache contentTypeCache, Expression<Func<Type1, TValue>> selector) + => PublishedModelUtility.GetModelPropertyType(GetModelContentType(contentTypeCache), selector); +#pragma warning restore 0109 + + private IPublishedValueFallback _publishedValueFallback; + + // ctor + public Type1(IPublishedContent content, IPublishedValueFallback publishedValueFallback) + : base(content, publishedValueFallback) + { + _publishedValueFallback = publishedValueFallback; + } + + // properties + + ///<summary> + /// prop1Name + ///</summary> + [global::System.CodeDom.Compiler.GeneratedCodeAttribute(""Umbraco.ModelsBuilder.Embedded"", """")] + [global::System.Diagnostics.CodeAnalysis.MaybeNull] + [ImplementPropertyType(""prop1"")] + public virtual string Prop1 => this.Value<string>(_publishedValueFallback, ""prop1""); + } +} +"; + Console.WriteLine(gen); + Assert.AreEqual(expected.ClearLf(), gen); + } + [Test] public void GenerateSimpleType_Ambiguous_Issue() { diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Routing/BackOfficeAreaRoutesTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Routing/BackOfficeAreaRoutesTests.cs index e1a0638fc602..c374d07ae842 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Routing/BackOfficeAreaRoutesTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Routing/BackOfficeAreaRoutesTests.cs @@ -1,17 +1,12 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Linq; -using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Routing; -using Microsoft.Extensions.Options; using Moq; using NUnit.Framework; using Umbraco.Cms.Api.Management.Controllers.Security; using Umbraco.Cms.Api.Management.Routing; using Umbraco.Cms.Core; -using Umbraco.Cms.Core.Configuration.Models; -using Umbraco.Cms.Core.Hosting; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Web.Common.Attributes; using Umbraco.Cms.Web.Common.Controllers; @@ -61,20 +56,9 @@ private void AssertMinimalBackOfficeRoutes(EndpointDataSource route) } private BackOfficeAreaRoutes GetBackOfficeAreaRoutes(RuntimeLevel level) - { - var globalSettings = new GlobalSettings(); - var routes = new BackOfficeAreaRoutes( - Options.Create(globalSettings), - Mock.Of<IHostingEnvironment>(x => - x.ToAbsolute(It.IsAny<string>()) == "/umbraco" && x.ApplicationVirtualPath == string.Empty), - Mock.Of<IRuntimeState>(x => x.Level == level), - new UmbracoApiControllerTypeCollection(() => new[] { typeof(Testing1Controller) })); - - return routes; - } + => new BackOfficeAreaRoutes(Mock.Of<IRuntimeState>(x => x.Level == level)); [IsBackOffice] private class Testing1Controller : UmbracoApiController - { - } + { } } diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Routing/PreviewRoutesTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Routing/PreviewRoutesTests.cs index e3502f6ada19..c72c82d7926f 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Routing/PreviewRoutesTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Routing/PreviewRoutesTests.cs @@ -52,13 +52,5 @@ public void RuntimeState_All_Routes(RuntimeLevel level) } private PreviewRoutes GetRoutes(RuntimeLevel level) - { - var globalSettings = new GlobalSettings(); - var routes = new PreviewRoutes( - Options.Create(globalSettings), - Mock.Of<IHostingEnvironment>(x => - x.ToAbsolute(It.IsAny<string>()) == "/umbraco" && x.ApplicationVirtualPath == string.Empty), - Mock.Of<IRuntimeState>(x => x.Level == level)); - return routes; - } + => new PreviewRoutes(Mock.Of<IRuntimeState>(x => x.Level == level)); } diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Controllers/RenderNoContentControllerTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Controllers/RenderNoContentControllerTests.cs index 90f5774a1be3..649ee47bde5e 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Controllers/RenderNoContentControllerTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Controllers/RenderNoContentControllerTests.cs @@ -26,8 +26,8 @@ public void Redirects_To_Root_When_Content_Published() var mockHostingEnvironment = new Mock<IHostingEnvironment>(); var controller = new RenderNoContentController( new TestUmbracoContextAccessor(mockUmbracoContext.Object), - new TestOptionsSnapshot<GlobalSettings>(new GlobalSettings()), - mockHostingEnvironment.Object); + mockHostingEnvironment.Object, + new TestOptionsSnapshot<GlobalSettings>(new GlobalSettings())); var result = controller.Index() as RedirectResult; @@ -53,7 +53,7 @@ public void Renders_View_When_No_Content_Published() { NoNodesViewPath = viewPath, }); - var controller = new RenderNoContentController(new TestUmbracoContextAccessor(mockUmbracoContext.Object), globalSettings, mockHostingEnvironment.Object); + var controller = new RenderNoContentController(new TestUmbracoContextAccessor(mockUmbracoContext.Object), mockHostingEnvironment.Object, globalSettings); var result = controller.Index() as ViewResult; Assert.IsNotNull(result); diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Controllers/SurfaceControllerTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Controllers/SurfaceControllerTests.cs index fab432c6e1be..f94a79f6ff00 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Controllers/SurfaceControllerTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Controllers/SurfaceControllerTests.cs @@ -38,9 +38,8 @@ public void Can_Construct_And_Get_Result() { var backofficeSecurityAccessor = Mock.Of<IBackOfficeSecurityAccessor>(); Mock.Get(backofficeSecurityAccessor).Setup(x => x.BackOfficeSecurity).Returns(Mock.Of<IBackOfficeSecurity>()); - var globalSettings = new GlobalSettings(); - var umbracoContextFactory = TestUmbracoContextFactory.Create(globalSettings, _umbracoContextAccessor); + var umbracoContextFactory = TestUmbracoContextFactory.Create(_umbracoContextAccessor); var umbracoContextReference = umbracoContextFactory.EnsureUmbracoContext(); var umbracoContext = umbracoContextReference.UmbracoContext; @@ -57,10 +56,9 @@ public void Can_Construct_And_Get_Result() [Test] public void Umbraco_Context_Not_Null() { - var globalSettings = new GlobalSettings(); var backofficeSecurityAccessor = Mock.Of<IBackOfficeSecurityAccessor>(); Mock.Get(backofficeSecurityAccessor).Setup(x => x.BackOfficeSecurity).Returns(Mock.Of<IBackOfficeSecurity>()); - var umbracoContextFactory = TestUmbracoContextFactory.Create(globalSettings, _umbracoContextAccessor); + var umbracoContextFactory = TestUmbracoContextFactory.Create(_umbracoContextAccessor); var umbracoContextReference = umbracoContextFactory.EnsureUmbracoContext(); var umbCtx = umbracoContextReference.UmbracoContext; @@ -79,9 +77,8 @@ public void Can_Lookup_Content() content.Setup(x => x.Id).Returns(2); var backofficeSecurityAccessor = Mock.Of<IBackOfficeSecurityAccessor>(); Mock.Get(backofficeSecurityAccessor).Setup(x => x.BackOfficeSecurity).Returns(Mock.Of<IBackOfficeSecurity>()); - var globalSettings = new GlobalSettings(); - var umbracoContextFactory = TestUmbracoContextFactory.Create(globalSettings, _umbracoContextAccessor); + var umbracoContextFactory = TestUmbracoContextFactory.Create(_umbracoContextAccessor); var umbracoContextReference = umbracoContextFactory.EnsureUmbracoContext(); var umbracoContext = umbracoContextReference.UmbracoContext; @@ -101,10 +98,9 @@ public void Can_Lookup_Content() [Test] public void Mock_Current_Page() { - var globalSettings = new GlobalSettings(); var backofficeSecurityAccessor = Mock.Of<IBackOfficeSecurityAccessor>(); Mock.Get(backofficeSecurityAccessor).Setup(x => x.BackOfficeSecurity).Returns(Mock.Of<IBackOfficeSecurity>()); - var umbracoContextFactory = TestUmbracoContextFactory.Create(globalSettings, _umbracoContextAccessor); + var umbracoContextFactory = TestUmbracoContextFactory.Create(_umbracoContextAccessor); var umbracoContextReference = umbracoContextFactory.EnsureUmbracoContext(); var umbracoContext = umbracoContextReference.UmbracoContext; diff --git a/version.json b/version.json index dded7533aa30..79d4288a12b8 100644 --- a/version.json +++ b/version.json @@ -1,6 +1,6 @@ { "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/main/src/NerdBank.GitVersioning/version.schema.json", - "version": "15.3.0-rc", + "version": "16.0.0-rc", "assemblyVersion": { "precision": "build" },