From 1e64c8d5af8d1483ef62c554160f8c26e8bc6ba8 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Thu, 20 Nov 2025 08:11:41 +0100 Subject: [PATCH 1/6] bump version to 17.0.0-rc4 --- src/Umbraco.Web.UI.Client/package-lock.json | 4 ++-- src/Umbraco.Web.UI.Client/package.json | 2 +- version.json | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/package-lock.json b/src/Umbraco.Web.UI.Client/package-lock.json index 0fba41feef..62ce4b15aa 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": "17.0.0-rc3", + "version": "17.0.0-rc4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@umbraco-cms/backoffice", - "version": "17.0.0-rc3", + "version": "17.0.0-rc4", "license": "MIT", "workspaces": [ "./src/packages/*", diff --git a/src/Umbraco.Web.UI.Client/package.json b/src/Umbraco.Web.UI.Client/package.json index 33376593d3..03dfc1203c 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": "17.0.0-rc3", + "version": "17.0.0-rc4", "type": "module", "exports": { ".": null, diff --git a/version.json b/version.json index 534b8cada0..3dc9031bc0 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": "17.0.0-rc3", + "version": "17.0.0-rc4", "assemblyVersion": { "precision": "build" }, From dc50a6e8c3977fdb0582980eafcf210befe09d5b Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Mon, 24 Nov 2025 10:42:35 +0100 Subject: [PATCH 2/6] Update further dependencies for 17 (#20935) Update further dependencies. --- Directory.Packages.props | 2 +- .../20251006140751_UpdateOpenIddictToV7.Designer.cs | 2 +- .../Migrations/UmbracoDbContextModelSnapshot.cs | 4 ++-- .../20251006140958_UpdateOpenIddictToV7.Designer.cs | 2 +- .../Migrations/UmbracoDbContextModelSnapshot.cs | 4 ++-- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 2 +- tests/Directory.Packages.props | 10 +++++----- 7 files changed, 13 insertions(+), 13 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 416a91cec3..e859c39080 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -33,7 +33,7 @@ - + diff --git a/src/Umbraco.Cms.Persistence.EFCore.SqlServer/Migrations/20251006140751_UpdateOpenIddictToV7.Designer.cs b/src/Umbraco.Cms.Persistence.EFCore.SqlServer/Migrations/20251006140751_UpdateOpenIddictToV7.Designer.cs index 4883257986..430ff75841 100644 --- a/src/Umbraco.Cms.Persistence.EFCore.SqlServer/Migrations/20251006140751_UpdateOpenIddictToV7.Designer.cs +++ b/src/Umbraco.Cms.Persistence.EFCore.SqlServer/Migrations/20251006140751_UpdateOpenIddictToV7.Designer.cs @@ -20,7 +20,7 @@ namespace Umbraco.Cms.Persistence.EFCore.SqlServer.Migrations { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "10.0.0-rc.2.25502.107") + .HasAnnotation("ProductVersion", "10.0.0") .HasAnnotation("Relational:MaxIdentifierLength", 128); SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); diff --git a/src/Umbraco.Cms.Persistence.EFCore.SqlServer/Migrations/UmbracoDbContextModelSnapshot.cs b/src/Umbraco.Cms.Persistence.EFCore.SqlServer/Migrations/UmbracoDbContextModelSnapshot.cs index 66c495fe60..3ab5eabcb6 100644 --- a/src/Umbraco.Cms.Persistence.EFCore.SqlServer/Migrations/UmbracoDbContextModelSnapshot.cs +++ b/src/Umbraco.Cms.Persistence.EFCore.SqlServer/Migrations/UmbracoDbContextModelSnapshot.cs @@ -1,4 +1,4 @@ -// +// using System; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; @@ -17,7 +17,7 @@ namespace Umbraco.Cms.Persistence.EFCore.SqlServer.Migrations { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "10.0.0-rc.2.25502.107") + .HasAnnotation("ProductVersion", "10.0.0") .HasAnnotation("Relational:MaxIdentifierLength", 128); SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); diff --git a/src/Umbraco.Cms.Persistence.EFCore.Sqlite/Migrations/20251006140958_UpdateOpenIddictToV7.Designer.cs b/src/Umbraco.Cms.Persistence.EFCore.Sqlite/Migrations/20251006140958_UpdateOpenIddictToV7.Designer.cs index 5f44ea2dae..d77d3a0fb6 100644 --- a/src/Umbraco.Cms.Persistence.EFCore.Sqlite/Migrations/20251006140958_UpdateOpenIddictToV7.Designer.cs +++ b/src/Umbraco.Cms.Persistence.EFCore.Sqlite/Migrations/20251006140958_UpdateOpenIddictToV7.Designer.cs @@ -18,7 +18,7 @@ namespace Umbraco.Cms.Persistence.EFCore.Sqlite.Migrations protected override void BuildTargetModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 - modelBuilder.HasAnnotation("ProductVersion", "10.0.0-rc.2.25502.107"); + modelBuilder.HasAnnotation("ProductVersion", "10.0.0"); modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreApplication", b => { diff --git a/src/Umbraco.Cms.Persistence.EFCore.Sqlite/Migrations/UmbracoDbContextModelSnapshot.cs b/src/Umbraco.Cms.Persistence.EFCore.Sqlite/Migrations/UmbracoDbContextModelSnapshot.cs index 7c3cf00576..392310d26a 100644 --- a/src/Umbraco.Cms.Persistence.EFCore.Sqlite/Migrations/UmbracoDbContextModelSnapshot.cs +++ b/src/Umbraco.Cms.Persistence.EFCore.Sqlite/Migrations/UmbracoDbContextModelSnapshot.cs @@ -1,4 +1,4 @@ -// +// using System; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; @@ -15,7 +15,7 @@ namespace Umbraco.Cms.Persistence.EFCore.Sqlite.Migrations protected override void BuildModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 - modelBuilder.HasAnnotation("ProductVersion", "10.0.0-rc.2.25502.107"); + modelBuilder.HasAnnotation("ProductVersion", "10.0.0"); modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreApplication", b => { diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index 2ade2ca396..485950fce1 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -25,7 +25,7 @@ - + - - + + - - + + @@ -22,4 +22,4 @@ - \ No newline at end of file + From f19aaaee52713920f1aa138183fa1978da4dd237 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Mon, 24 Nov 2025 11:07:45 +0100 Subject: [PATCH 3/6] build(deps): bump marked to 17.0.1 (#20933) --- src/Umbraco.Web.UI.Client/package-lock.json | 8 ++++---- .../src/external/marked/package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/package-lock.json b/src/Umbraco.Web.UI.Client/package-lock.json index 62ce4b15aa..1f67288e20 100644 --- a/src/Umbraco.Web.UI.Client/package-lock.json +++ b/src/Umbraco.Web.UI.Client/package-lock.json @@ -10613,9 +10613,9 @@ } }, "node_modules/marked": { - "version": "16.3.0", - "resolved": "https://registry.npmjs.org/marked/-/marked-16.3.0.tgz", - "integrity": "sha512-K3UxuKu6l6bmA5FUwYho8CfJBlsUWAooKtdGgMcERSpF7gcBUrCGsLH7wDaaNOzwq18JzSUDyoEb/YsrqMac3w==", + "version": "17.0.1", + "resolved": "https://registry.npmjs.org/marked/-/marked-17.0.1.tgz", + "integrity": "sha512-boeBdiS0ghpWcSwoNm/jJBwdpFaMnZWRzjA6SkUMYb40SVaN1x7mmfGKp0jvexGcx+7y2La5zRZsYFZI6Qpypg==", "license": "MIT", "bin": { "marked": "bin/marked.js" @@ -16961,7 +16961,7 @@ "src/external/marked": { "name": "@umbraco-backoffice/marked", "dependencies": { - "marked": "^16.3.0" + "marked": "^17.0.1" } }, "src/external/monaco-editor": { diff --git a/src/Umbraco.Web.UI.Client/src/external/marked/package.json b/src/Umbraco.Web.UI.Client/src/external/marked/package.json index aba2839f75..30e912cca4 100644 --- a/src/Umbraco.Web.UI.Client/src/external/marked/package.json +++ b/src/Umbraco.Web.UI.Client/src/external/marked/package.json @@ -6,6 +6,6 @@ "build": "vite build" }, "dependencies": { - "marked": "^16.3.0" + "marked": "^17.0.1" } } From 215c4dc5401c4bd7bfb20a25f0611c76cdcd71e5 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Mon, 24 Nov 2025 11:23:08 +0100 Subject: [PATCH 4/6] build(deps): bump @microsoft/signalr to 10.0.0 (#20932) --- src/Umbraco.Web.UI.Client/package-lock.json | 8 ++++---- .../src/external/signalr/package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/package-lock.json b/src/Umbraco.Web.UI.Client/package-lock.json index 1f67288e20..9c34890f9d 100644 --- a/src/Umbraco.Web.UI.Client/package-lock.json +++ b/src/Umbraco.Web.UI.Client/package-lock.json @@ -1334,9 +1334,9 @@ } }, "node_modules/@microsoft/signalr": { - "version": "9.0.6", - "resolved": "https://registry.npmjs.org/@microsoft/signalr/-/signalr-9.0.6.tgz", - "integrity": "sha512-DrhgzFWI9JE4RPTsHYRxh4yr+OhnwKz8bnJe7eIi7mLLjqhJpEb62CiUy/YbFvLqLzcGzlzz1QWgVAW0zyipMQ==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@microsoft/signalr/-/signalr-10.0.0.tgz", + "integrity": "sha512-0BRqz/uCx3JdrOqiqgFhih/+hfTERaUfCZXFB52uMaZJrKaPRzHzMuqVsJC/V3pt7NozcNXGspjKiQEK+X7P2w==", "license": "MIT", "dependencies": { "abort-controller": "^3.0.0", @@ -16982,7 +16982,7 @@ "src/external/signalr": { "name": "@umbraco-backoffice/signalr", "dependencies": { - "@microsoft/signalr": "9.0.6" + "@microsoft/signalr": "^10.0.0" } }, "src/external/uui": { diff --git a/src/Umbraco.Web.UI.Client/src/external/signalr/package.json b/src/Umbraco.Web.UI.Client/src/external/signalr/package.json index 08e7abbc27..6bbc565225 100644 --- a/src/Umbraco.Web.UI.Client/src/external/signalr/package.json +++ b/src/Umbraco.Web.UI.Client/src/external/signalr/package.json @@ -6,6 +6,6 @@ "build": "vite build" }, "dependencies": { - "@microsoft/signalr": "9.0.6" + "@microsoft/signalr": "^10.0.0" } } From 9beed532a9cdf291c48ecbb136a6ee95b899c84b Mon Sep 17 00:00:00 2001 From: Laura Neto <12862535+lauraneto@users.noreply.github.com> Date: Mon, 24 Nov 2025 12:06:03 +0100 Subject: [PATCH 5/6] Update Swashbuckle to v10 (#20925) * Update Swashbuckle to v10 * Regenerate backoffice api client * Add missing space for consistency * Simplify nullability check * Small improvement Didn't notice that these classes were internal, so tried keeping compatibility, but it wasn't needed. * Fix failing integration test * Apply suggestions from code review Co-authored-by: Andy Butland Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Remove unnecessary comma --------- Co-authored-by: Andy Butland Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- Directory.Packages.props | 2 +- .../ConfigureUmbracoSwaggerGenOptions.cs | 2 +- .../OpenApi/EnumSchemaFilter.cs | 26 +- .../OpenApi/MimeTypeDocumentFilter.cs | 21 +- .../RemoveSecuritySchemesDocumentFilter.cs | 4 +- .../SwaggerRouteTemplatePipelineFilter.cs | 3 +- ...gureUmbracoDeliveryApiSwaggerGenOptions.cs | 2 +- ...henticationDeliveryApiSwaggerGenOptions.cs | 30 +- .../SwaggerContentDocumentationFilter.cs | 151 +++------ .../Filters/SwaggerDocumentationFilterBase.cs | 152 +++++---- .../SwaggerMediaDocumentationFilter.cs | 83 +---- ...reUmbracoManagementApiSwaggerGenOptions.cs | 15 +- src/Umbraco.Cms.Api.Management/OpenApi.json | 306 +++++++++++++++--- ...SecurityRequirementsOperationFilterBase.cs | 43 ++- .../OpenApi/NotificationHeaderFilter.cs | 33 +- .../OpenApi/ReponseHeaderOperationFilter.cs | 28 +- ...equireNonNullablePropertiesSchemaFilter.cs | 19 +- .../src/packages/core/backend-api/sdk.gen.ts | 3 + .../packages/core/backend-api/types.gen.ts | 16 +- .../Composers/UmbracoExtensionApiComposer.cs | 2 +- .../DeliveryApi/OpenApiContractTest.cs | 10 +- 21 files changed, 523 insertions(+), 428 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index e859c39080..2c9fa306e4 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -74,7 +74,7 @@ - + diff --git a/src/Umbraco.Cms.Api.Common/Configuration/ConfigureUmbracoSwaggerGenOptions.cs b/src/Umbraco.Cms.Api.Common/Configuration/ConfigureUmbracoSwaggerGenOptions.cs index a4cabf51bb..4b65bb12bf 100644 --- a/src/Umbraco.Cms.Api.Common/Configuration/ConfigureUmbracoSwaggerGenOptions.cs +++ b/src/Umbraco.Cms.Api.Common/Configuration/ConfigureUmbracoSwaggerGenOptions.cs @@ -4,7 +4,7 @@ using Microsoft.AspNetCore.Mvc.ApiExplorer; using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; -using Microsoft.OpenApi.Models; +using Microsoft.OpenApi; using Swashbuckle.AspNetCore.SwaggerGen; using Umbraco.Cms.Api.Common.OpenApi; using Umbraco.Extensions; diff --git a/src/Umbraco.Cms.Api.Common/OpenApi/EnumSchemaFilter.cs b/src/Umbraco.Cms.Api.Common/OpenApi/EnumSchemaFilter.cs index e2ac5ab870..845adb4990 100644 --- a/src/Umbraco.Cms.Api.Common/OpenApi/EnumSchemaFilter.cs +++ b/src/Umbraco.Cms.Api.Common/OpenApi/EnumSchemaFilter.cs @@ -1,25 +1,27 @@ using System.Reflection; using System.Runtime.Serialization; -using Microsoft.OpenApi.Any; -using Microsoft.OpenApi.Models; +using System.Text.Json.Nodes; +using Microsoft.OpenApi; using Swashbuckle.AspNetCore.SwaggerGen; namespace Umbraco.Cms.Api.Common.OpenApi; public class EnumSchemaFilter : ISchemaFilter { - public void Apply(OpenApiSchema model, SchemaFilterContext context) + public void Apply(IOpenApiSchema model, SchemaFilterContext context) { - if (context.Type.IsEnum) + if (model is not OpenApiSchema schema || context.Type.IsEnum is false) { - model.Type = "string"; - model.Format = null; - model.Enum.Clear(); - foreach (var name in Enum.GetNames(context.Type)) - { - var actualName = context.Type.GetField(name)?.GetCustomAttribute()?.Value ?? name; - model.Enum.Add(new OpenApiString(actualName)); - } + return; + } + + schema.Type = JsonSchemaType.String; + schema.Format = null; + schema.Enum = new List(); + foreach (var name in Enum.GetNames(context.Type)) + { + var actualName = context.Type.GetField(name)?.GetCustomAttribute()?.Value ?? name; + schema.Enum.Add(actualName); } } } diff --git a/src/Umbraco.Cms.Api.Common/OpenApi/MimeTypeDocumentFilter.cs b/src/Umbraco.Cms.Api.Common/OpenApi/MimeTypeDocumentFilter.cs index a756c30f1f..2dfc6a88a4 100644 --- a/src/Umbraco.Cms.Api.Common/OpenApi/MimeTypeDocumentFilter.cs +++ b/src/Umbraco.Cms.Api.Common/OpenApi/MimeTypeDocumentFilter.cs @@ -1,4 +1,4 @@ -using Microsoft.OpenApi.Models; +using Microsoft.OpenApi; using Swashbuckle.AspNetCore.SwaggerGen; using Umbraco.Extensions; @@ -21,25 +21,32 @@ public class MimeTypeDocumentFilter : IDocumentFilter } OpenApiOperation[] operations = swaggerDoc.Paths - .SelectMany(path => path.Value.Operations.Values) + .SelectMany(path => path.Value.Operations?.Values ?? Enumerable.Empty()) .ToArray(); - void RemoveUnwantedMimeTypes(IDictionary content) + void RemoveUnwantedMimeTypes(IDictionary? content) { - if (content.ContainsKey("application/json")) + if (content is null || content.ContainsKey("application/json") is false) { - content.RemoveAll(r => r.Key != "application/json"); + return; } + content.RemoveAll(r => r.Key != "application/json"); } - OpenApiRequestBody[] requestBodies = operations.Select(operation => operation.RequestBody).WhereNotNull().ToArray(); + OpenApiRequestBody[] requestBodies = operations + .Select(operation => operation.RequestBody) + .OfType() + .ToArray(); foreach (OpenApiRequestBody requestBody in requestBodies) { RemoveUnwantedMimeTypes(requestBody.Content); } - OpenApiResponse[] responses = operations.SelectMany(operation => operation.Responses.Values).WhereNotNull().ToArray(); + OpenApiResponse[] responses = operations + .SelectMany(operation => operation.Responses?.Values ?? Enumerable.Empty()) + .OfType() + .ToArray(); foreach (OpenApiResponse response in responses) { RemoveUnwantedMimeTypes(response.Content); diff --git a/src/Umbraco.Cms.Api.Common/OpenApi/RemoveSecuritySchemesDocumentFilter.cs b/src/Umbraco.Cms.Api.Common/OpenApi/RemoveSecuritySchemesDocumentFilter.cs index 8ab2041f7d..c90c9f9c73 100644 --- a/src/Umbraco.Cms.Api.Common/OpenApi/RemoveSecuritySchemesDocumentFilter.cs +++ b/src/Umbraco.Cms.Api.Common/OpenApi/RemoveSecuritySchemesDocumentFilter.cs @@ -1,4 +1,4 @@ -using Microsoft.OpenApi.Models; +using Microsoft.OpenApi; using Swashbuckle.AspNetCore.SwaggerGen; namespace Umbraco.Cms.Api.Common.OpenApi; @@ -20,6 +20,6 @@ public class RemoveSecuritySchemesDocumentFilter : IDocumentFilter return; } - swaggerDoc.Components.SecuritySchemes.Clear(); + swaggerDoc.Components?.SecuritySchemes?.Clear(); } } diff --git a/src/Umbraco.Cms.Api.Common/OpenApi/SwaggerRouteTemplatePipelineFilter.cs b/src/Umbraco.Cms.Api.Common/OpenApi/SwaggerRouteTemplatePipelineFilter.cs index dba0e6f56d..2c45a36754 100644 --- a/src/Umbraco.Cms.Api.Common/OpenApi/SwaggerRouteTemplatePipelineFilter.cs +++ b/src/Umbraco.Cms.Api.Common/OpenApi/SwaggerRouteTemplatePipelineFilter.cs @@ -3,13 +3,12 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Options; -using Microsoft.OpenApi.Models; +using Microsoft.OpenApi; using Swashbuckle.AspNetCore.SwaggerGen; using Swashbuckle.AspNetCore.SwaggerUI; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Hosting; using Umbraco.Cms.Web.Common.ApplicationBuilder; -using Umbraco.Extensions; using IHostingEnvironment = Umbraco.Cms.Core.Hosting.IHostingEnvironment; namespace Umbraco.Cms.Api.Common.OpenApi; diff --git a/src/Umbraco.Cms.Api.Delivery/Configuration/ConfigureUmbracoDeliveryApiSwaggerGenOptions.cs b/src/Umbraco.Cms.Api.Delivery/Configuration/ConfigureUmbracoDeliveryApiSwaggerGenOptions.cs index 9b87166300..8c7437e4bf 100644 --- a/src/Umbraco.Cms.Api.Delivery/Configuration/ConfigureUmbracoDeliveryApiSwaggerGenOptions.cs +++ b/src/Umbraco.Cms.Api.Delivery/Configuration/ConfigureUmbracoDeliveryApiSwaggerGenOptions.cs @@ -1,6 +1,6 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; -using Microsoft.OpenApi.Models; +using Microsoft.OpenApi; using Swashbuckle.AspNetCore.SwaggerGen; using Umbraco.Cms.Api.Common.OpenApi; using Umbraco.Cms.Api.Delivery.Filters; diff --git a/src/Umbraco.Cms.Api.Delivery/Configuration/ConfigureUmbracoMemberAuthenticationDeliveryApiSwaggerGenOptions.cs b/src/Umbraco.Cms.Api.Delivery/Configuration/ConfigureUmbracoMemberAuthenticationDeliveryApiSwaggerGenOptions.cs index 3161105dca..5922768462 100644 --- a/src/Umbraco.Cms.Api.Delivery/Configuration/ConfigureUmbracoMemberAuthenticationDeliveryApiSwaggerGenOptions.cs +++ b/src/Umbraco.Cms.Api.Delivery/Configuration/ConfigureUmbracoMemberAuthenticationDeliveryApiSwaggerGenOptions.cs @@ -1,6 +1,6 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; -using Microsoft.OpenApi.Models; +using Microsoft.OpenApi; using Swashbuckle.AspNetCore.SwaggerGen; using Umbraco.Cms.Api.Common.Security; using Umbraco.Cms.Api.Delivery.Controllers.Content; @@ -35,23 +35,9 @@ public class ConfigureUmbracoMemberAuthenticationDeliveryApiSwaggerGenOptions : return; } - operation.Security = new List - { - new OpenApiSecurityRequirement - { - { - new OpenApiSecurityScheme - { - Reference = new OpenApiReference - { - Type = ReferenceType.SecurityScheme, - Id = AuthSchemeName, - } - }, - [] - } - } - }; + var schemaRef = new OpenApiSecuritySchemeReference(AuthSchemeName, context.Document); + operation.Security ??= new List(); + operation.Security.Add(new OpenApiSecurityRequirement { [schemaRef] = [] }); } public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context) @@ -61,6 +47,8 @@ public class ConfigureUmbracoMemberAuthenticationDeliveryApiSwaggerGenOptions : return; } + swaggerDoc.Components ??= new OpenApiComponents(); + swaggerDoc.Components.SecuritySchemes ??= new Dictionary(); swaggerDoc.Components.SecuritySchemes.Add( AuthSchemeName, new OpenApiSecurityScheme @@ -74,9 +62,9 @@ public class ConfigureUmbracoMemberAuthenticationDeliveryApiSwaggerGenOptions : AuthorizationCode = new OpenApiOAuthFlow { AuthorizationUrl = new Uri(Paths.MemberApi.AuthorizationEndpoint, UriKind.Relative), - TokenUrl = new Uri(Paths.MemberApi.TokenEndpoint, UriKind.Relative) - } - } + TokenUrl = new Uri(Paths.MemberApi.TokenEndpoint, UriKind.Relative), + }, + }, }); } } diff --git a/src/Umbraco.Cms.Api.Delivery/Filters/SwaggerContentDocumentationFilter.cs b/src/Umbraco.Cms.Api.Delivery/Filters/SwaggerContentDocumentationFilter.cs index 014aed28c8..290f6b66dc 100644 --- a/src/Umbraco.Cms.Api.Delivery/Filters/SwaggerContentDocumentationFilter.cs +++ b/src/Umbraco.Cms.Api.Delivery/Filters/SwaggerContentDocumentationFilter.cs @@ -1,9 +1,9 @@ -using Microsoft.OpenApi.Any; -using Microsoft.OpenApi.Models; +using System.Text.Json.Nodes; +using Microsoft.OpenApi; using Swashbuckle.AspNetCore.SwaggerGen; using Umbraco.Cms.Api.Delivery.Configuration; -using Umbraco.Cms.Api.Delivery.Controllers; using Umbraco.Cms.Api.Delivery.Controllers.Content; +using Umbraco.Cms.Core; namespace Umbraco.Cms.Api.Delivery.Filters; @@ -13,7 +13,7 @@ internal sealed class SwaggerContentDocumentationFilter : SwaggerDocumentationFi protected override void ApplyOperation(OpenApiOperation operation, OperationFilterContext context) { - operation.Parameters ??= new List(); + operation.Parameters ??= new List(); AddExpand(operation, context); @@ -21,50 +21,50 @@ internal sealed class SwaggerContentDocumentationFilter : SwaggerDocumentationFi operation.Parameters.Add(new OpenApiParameter { - Name = Core.Constants.DeliveryApi.HeaderNames.AcceptLanguage, + Name = Constants.DeliveryApi.HeaderNames.AcceptLanguage, In = ParameterLocation.Header, Required = false, Description = "Defines the language to return. Use this when querying language variant content items.", - Schema = new OpenApiSchema { Type = "string" }, - Examples = new Dictionary + Schema = new OpenApiSchema { Type = JsonSchemaType.String }, + Examples = new Dictionary { - { "Default", new OpenApiExample { Value = new OpenApiString(string.Empty) } }, - { "English culture", new OpenApiExample { Value = new OpenApiString("en-us") } } - } + { "Default", new OpenApiExample { Value = string.Empty } }, + { "English culture", new OpenApiExample { Value = "en-us" } }, + }, }); operation.Parameters.Add(new OpenApiParameter { - Name = Core.Constants.DeliveryApi.HeaderNames.AcceptSegment, + Name = Constants.DeliveryApi.HeaderNames.AcceptSegment, In = ParameterLocation.Header, Required = false, Description = "Defines the segment to return. Use this when querying segment variant content items.", - Schema = new OpenApiSchema { Type = "string" }, - Examples = new Dictionary + Schema = new OpenApiSchema { Type = JsonSchemaType.String }, + Examples = new Dictionary { - { "Default", new OpenApiExample { Value = new OpenApiString(string.Empty) } }, - { "Segment One", new OpenApiExample { Value = new OpenApiString("segment-one") } } - } + { "Default", new OpenApiExample { Value = string.Empty } }, + { "Segment One", new OpenApiExample { Value = "segment-one" } }, + }, }); AddApiKey(operation); operation.Parameters.Add(new OpenApiParameter { - Name = Core.Constants.DeliveryApi.HeaderNames.Preview, + Name = Constants.DeliveryApi.HeaderNames.Preview, In = ParameterLocation.Header, Required = false, Description = "Whether to request draft content.", - Schema = new OpenApiSchema { Type = "boolean" } + Schema = new OpenApiSchema { Type = JsonSchemaType.Boolean }, }); operation.Parameters.Add(new OpenApiParameter { - Name = Core.Constants.DeliveryApi.HeaderNames.StartItem, + Name = Constants.DeliveryApi.HeaderNames.StartItem, In = ParameterLocation.Header, Required = false, Description = "URL segment or GUID of a root content item.", - Schema = new OpenApiSchema { Type = "string" } + Schema = new OpenApiSchema { Type = JsonSchemaType.String }, }); } @@ -92,105 +92,36 @@ internal sealed class SwaggerContentDocumentationFilter : SwaggerDocumentationFi } } - private Dictionary FetchQueryParameterExamples() => + private Dictionary FetchQueryParameterExamples() => new() { - { "Select all", new OpenApiExample { Value = new OpenApiString(string.Empty) } }, - { - "Select all ancestors of a node by id", - new OpenApiExample { Value = new OpenApiString("ancestors:id") } - }, - { - "Select all ancestors of a node by path", - new OpenApiExample { Value = new OpenApiString("ancestors:path") } - }, - { - "Select all children of a node by id", - new OpenApiExample { Value = new OpenApiString("children:id") } - }, - { - "Select all children of a node by path", - new OpenApiExample { Value = new OpenApiString("children:path") } - }, - { - "Select all descendants of a node by id", - new OpenApiExample { Value = new OpenApiString("descendants:id") } - }, - { - "Select all descendants of a node by path", - new OpenApiExample { Value = new OpenApiString("descendants:path") } - } + { "Select all", new OpenApiExample { Value = string.Empty } }, + { "Select all ancestors of a node by id", new OpenApiExample { Value = "ancestors:id" } }, + { "Select all ancestors of a node by path", new OpenApiExample { Value = "ancestors:path" } }, + { "Select all children of a node by id", new OpenApiExample { Value = "children:id" } }, + { "Select all children of a node by path", new OpenApiExample { Value = "children:path" } }, + { "Select all descendants of a node by id", new OpenApiExample { Value = "descendants:id" } }, + { "Select all descendants of a node by path", new OpenApiExample { Value = "descendants:path" } }, }; - private Dictionary FilterQueryParameterExamples() => + private Dictionary FilterQueryParameterExamples() => new() { - { "Default filter", new OpenApiExample { Value = new OpenApiString(string.Empty) } }, - { - "Filter by content type (equals)", - new OpenApiExample { Value = new OpenApiArray { new OpenApiString("contentType:alias1") } } - }, - { - "Filter by name (contains)", - new OpenApiExample { Value = new OpenApiArray { new OpenApiString("name:nodeName") } } - }, - { - "Filter by creation date (less than)", - new OpenApiExample { Value = new OpenApiArray { new OpenApiString("createDate<2024-01-01") } } - }, - { - "Filter by update date (greater than or equal)", - new OpenApiExample { Value = new OpenApiArray { new OpenApiString("updateDate>:2023-01-01") } } - } + { "Default filter", new OpenApiExample { Value = string.Empty } }, + { "Filter by content type (equals)", new OpenApiExample { Value = new JsonArray { "contentType:alias1" } } }, + { "Filter by name (contains)", new OpenApiExample { Value = new JsonArray { "name:nodeName" } } }, + { "Filter by creation date (less than)", new OpenApiExample { Value = new JsonArray { "createDate<2024-01-01" } } }, + { "Filter by update date (greater than or equal)", new OpenApiExample { Value = new JsonArray { "updateDate>:2023-01-01" } } }, }; - private Dictionary SortQueryParameterExamples() => + private Dictionary SortQueryParameterExamples() => new() { - { "Default sort", new OpenApiExample { Value = new OpenApiString(string.Empty) } }, - { - "Sort by create date", - new OpenApiExample - { - Value = new OpenApiArray - { - new OpenApiString("createDate:asc"), new OpenApiString("createDate:desc") - } - } - }, - { - "Sort by level", - new OpenApiExample - { - Value = new OpenApiArray { new OpenApiString("level:asc"), new OpenApiString("level:desc") } - } - }, - { - "Sort by name", - new OpenApiExample - { - Value = new OpenApiArray { new OpenApiString("name:asc"), new OpenApiString("name:desc") } - } - }, - { - "Sort by sort order", - new OpenApiExample - { - Value = new OpenApiArray - { - new OpenApiString("sortOrder:asc"), new OpenApiString("sortOrder:desc") - } - } - }, - { - "Sort by update date", - new OpenApiExample - { - Value = new OpenApiArray - { - new OpenApiString("updateDate:asc"), new OpenApiString("updateDate:desc") - } - } - } + { "Default sort", new OpenApiExample { Value = string.Empty } }, + { "Sort by create date", new OpenApiExample { Value = new JsonArray { "createDate:asc", "createDate:desc" } } }, + { "Sort by level", new OpenApiExample { Value = new JsonArray { "level:asc", "level:desc" } } }, + { "Sort by name", new OpenApiExample { Value = new JsonArray { "name:asc", "name:desc" } } }, + { "Sort by sort order", new OpenApiExample { Value = new JsonArray { "sortOrder:asc", "sortOrder:desc" } } }, + { "Sort by update date", new OpenApiExample { Value = new JsonArray { "updateDate:asc", "updateDate:desc" } } }, }; } diff --git a/src/Umbraco.Cms.Api.Delivery/Filters/SwaggerDocumentationFilterBase.cs b/src/Umbraco.Cms.Api.Delivery/Filters/SwaggerDocumentationFilterBase.cs index 8a450a2f9b..2776f6a39e 100644 --- a/src/Umbraco.Cms.Api.Delivery/Filters/SwaggerDocumentationFilterBase.cs +++ b/src/Umbraco.Cms.Api.Delivery/Filters/SwaggerDocumentationFilterBase.cs @@ -1,7 +1,7 @@ using Microsoft.AspNetCore.Mvc; -using Microsoft.OpenApi.Any; -using Microsoft.OpenApi.Models; +using Microsoft.OpenApi; using Swashbuckle.AspNetCore.SwaggerGen; +using Umbraco.Cms.Core; namespace Umbraco.Cms.Api.Delivery.Filters; @@ -9,6 +9,8 @@ internal abstract class SwaggerDocumentationFilterBase : SwaggerFilterBase, IOperationFilter, IParameterFilter where TBaseController : Controller { + protected abstract string DocumentationLink { get; } + public void Apply(OpenApiOperation operation, OperationFilterContext context) { if (CanApply(context)) @@ -17,21 +19,19 @@ internal abstract class SwaggerDocumentationFilterBase } } - public void Apply(OpenApiParameter parameter, ParameterFilterContext context) + public void Apply(IOpenApiParameter parameter, ParameterFilterContext context) { - if (CanApply(context)) + if (CanApply(context) && parameter is OpenApiParameter openApiParameter) { - ApplyParameter(parameter, context); + ApplyParameter(openApiParameter, context); } } - protected abstract string DocumentationLink { get; } - protected abstract void ApplyOperation(OpenApiOperation operation, OperationFilterContext context); protected abstract void ApplyParameter(OpenApiParameter parameter, ParameterFilterContext context); - protected void AddQueryParameterDocumentation(OpenApiParameter parameter, Dictionary examples, string description) + protected void AddQueryParameterDocumentation(OpenApiParameter parameter, Dictionary examples, string description) { parameter.Description = QueryParameterDescription(description); parameter.Examples = examples; @@ -60,15 +60,19 @@ internal abstract class SwaggerDocumentationFilterBase AddFields(operation); } - protected void AddApiKey(OpenApiOperation operation) => - operation.Parameters.Add(new OpenApiParameter - { - Name = Core.Constants.DeliveryApi.HeaderNames.ApiKey, - In = ParameterLocation.Header, - Required = false, - Description = "API key specified through configuration to authorize access to the API.", - Schema = new OpenApiSchema { Type = "string" } - }); + protected void AddApiKey(OpenApiOperation operation) + { + operation.Parameters ??= new List(); + operation.Parameters.Add( + new OpenApiParameter + { + Name = Constants.DeliveryApi.HeaderNames.ApiKey, + In = ParameterLocation.Header, + Required = false, + Description = "API key specified through configuration to authorize access to the API.", + Schema = new OpenApiSchema { Type = JsonSchemaType.String }, + }); + } protected string PaginationDescription(bool skip, string itemType) => $"Specifies the number of found {itemType} items to {(skip ? "skip" : "take")}. Use this to control pagination of the response."; @@ -82,78 +86,70 @@ internal abstract class SwaggerDocumentationFilterBase // FIXME: remove this when Delivery API V1 has been removed (expectedly in V15) private void AddExpandV1(OpenApiOperation operation) - => operation.Parameters.Add(new OpenApiParameter - { - Name = "expand", - In = ParameterLocation.Query, - Required = false, - Description = QueryParameterDescription("Defines the properties that should be expanded in the response"), - Schema = new OpenApiSchema { Type = "string" }, - Examples = new Dictionary + { + operation.Parameters ??= new List(); + operation.Parameters.Add( + new OpenApiParameter { - { "Expand none", new OpenApiExample { Value = new OpenApiString(string.Empty) } }, - { "Expand all", new OpenApiExample { Value = new OpenApiString("all") } }, + Name = "expand", + In = ParameterLocation.Query, + Required = false, + Description = + QueryParameterDescription("Defines the properties that should be expanded in the response"), + Schema = new OpenApiSchema { Type = JsonSchemaType.String }, + Examples = new Dictionary { - "Expand specific property", - new OpenApiExample { Value = new OpenApiString("property:alias1") } + { "Expand none", new OpenApiExample { Value = string.Empty } }, + { "Expand all", new OpenApiExample { Value = "all" } }, + { "Expand specific property", new OpenApiExample { Value = "property:alias1" } }, + { "Expand specific properties", new OpenApiExample { Value = "property:alias1,alias2" } }, }, - { - "Expand specific properties", - new OpenApiExample { Value = new OpenApiString("property:alias1,alias2") } - } - } - }); + }); + } private void AddExpand(OpenApiOperation operation) - => operation.Parameters.Add(new OpenApiParameter - { - Name = "expand", - In = ParameterLocation.Query, - Required = false, - Description = QueryParameterDescription("Defines the properties that should be expanded in the response"), - Schema = new OpenApiSchema { Type = "string" }, - Examples = new Dictionary + { + operation.Parameters ??= new List(); + operation.Parameters.Add( + new OpenApiParameter { - { "Expand none", new OpenApiExample { Value = new OpenApiString(string.Empty) } }, - { "Expand all properties", new OpenApiExample { Value = new OpenApiString("properties[$all]") } }, + Name = "expand", + In = ParameterLocation.Query, + Required = false, + Description = + QueryParameterDescription("Defines the properties that should be expanded in the response"), + Schema = new OpenApiSchema { Type = JsonSchemaType.String }, + Examples = new Dictionary { - "Expand specific property", - new OpenApiExample { Value = new OpenApiString("properties[alias1]") } + { "Expand none", new OpenApiExample { Value = string.Empty } }, + { "Expand all properties", new OpenApiExample { Value = "properties[$all]" } }, + { "Expand specific property", new OpenApiExample { Value = "properties[alias1]" } }, + { "Expand specific properties", new OpenApiExample { Value = "properties[alias1,alias2]" } }, + { "Expand nested properties", new OpenApiExample { Value = "properties[alias1[properties[nestedAlias1,nestedAlias2]]]" } }, }, - { - "Expand specific properties", - new OpenApiExample { Value = new OpenApiString("properties[alias1,alias2]") } - }, - { - "Expand nested properties", - new OpenApiExample { Value = new OpenApiString("properties[alias1[properties[nestedAlias1,nestedAlias2]]]") } - } - } - }); + }); + } private void AddFields(OpenApiOperation operation) - => operation.Parameters.Add(new OpenApiParameter - { - Name = "fields", - In = ParameterLocation.Query, - Required = false, - Description = QueryParameterDescription("Explicitly defines which properties should be included in the response (by default all properties are included)"), - Schema = new OpenApiSchema { Type = "string" }, - Examples = new Dictionary + { + operation.Parameters ??= new List(); + operation.Parameters.Add( + new OpenApiParameter { - { "Include all properties", new OpenApiExample { Value = new OpenApiString("properties[$all]") } }, + Name = "fields", + In = ParameterLocation.Query, + Required = false, + Description = + QueryParameterDescription( + "Explicitly defines which properties should be included in the response (by default all properties are included)"), + Schema = new OpenApiSchema { Type = JsonSchemaType.String }, + Examples = new Dictionary { - "Include only specific property", - new OpenApiExample { Value = new OpenApiString("properties[alias1]") } + { "Include all properties", new OpenApiExample { Value = "properties[$all]" } }, + { "Include only specific property", new OpenApiExample { Value = "properties[alias1]" } }, + { "Include only specific properties", new OpenApiExample { Value = "properties[alias1,alias2]" } }, + { "Include only specific nested properties", new OpenApiExample { Value = "properties[alias1[properties[nestedAlias1,nestedAlias2]]]" } }, }, - { - "Include only specific properties", - new OpenApiExample { Value = new OpenApiString("properties[alias1,alias2]") } - }, - { - "Include only specific nested properties", - new OpenApiExample { Value = new OpenApiString("properties[alias1[properties[nestedAlias1,nestedAlias2]]]") } - } - } - }); + }); + } } diff --git a/src/Umbraco.Cms.Api.Delivery/Filters/SwaggerMediaDocumentationFilter.cs b/src/Umbraco.Cms.Api.Delivery/Filters/SwaggerMediaDocumentationFilter.cs index 85ba66e648..a8da861325 100644 --- a/src/Umbraco.Cms.Api.Delivery/Filters/SwaggerMediaDocumentationFilter.cs +++ b/src/Umbraco.Cms.Api.Delivery/Filters/SwaggerMediaDocumentationFilter.cs @@ -1,8 +1,7 @@ -using Microsoft.OpenApi.Any; -using Microsoft.OpenApi.Models; +using System.Text.Json.Nodes; +using Microsoft.OpenApi; using Swashbuckle.AspNetCore.SwaggerGen; using Umbraco.Cms.Api.Delivery.Configuration; -using Umbraco.Cms.Api.Delivery.Controllers; using Umbraco.Cms.Api.Delivery.Controllers.Media; namespace Umbraco.Cms.Api.Delivery.Filters; @@ -13,7 +12,7 @@ internal sealed class SwaggerMediaDocumentationFilter : SwaggerDocumentationFilt protected override void ApplyOperation(OpenApiOperation operation, OperationFilterContext context) { - operation.Parameters ??= new List(); + operation.Parameters ??= new List(); AddExpand(operation, context); @@ -46,77 +45,29 @@ internal sealed class SwaggerMediaDocumentationFilter : SwaggerDocumentationFilt } } - private Dictionary FetchQueryParameterExamples() => + private Dictionary FetchQueryParameterExamples() => new() { - { - "Select all children at root level", - new OpenApiExample { Value = new OpenApiString("children:/") } - }, - { - "Select all children of a media item by id", - new OpenApiExample { Value = new OpenApiString("children:id") } - }, - { - "Select all children of a media item by path", - new OpenApiExample { Value = new OpenApiString("children:path") } - } + { "Select all children at root level", new OpenApiExample { Value = "children:/" } }, + { "Select all children of a media item by id", new OpenApiExample { Value = "children:id" } }, + { "Select all children of a media item by path", new OpenApiExample { Value = "children:path" } }, }; - private Dictionary FilterQueryParameterExamples() => + private Dictionary FilterQueryParameterExamples() => new() { - { "Default filter", new OpenApiExample { Value = new OpenApiString(string.Empty) } }, - { - "Filter by media type", - new OpenApiExample { Value = new OpenApiArray { new OpenApiString("mediaType:alias1") } } - }, - { - "Filter by name", - new OpenApiExample { Value = new OpenApiArray { new OpenApiString("name:nodeName") } } - } + { "Default filter", new OpenApiExample { Value = string.Empty } }, + { "Filter by media type", new OpenApiExample { Value = new JsonArray { "mediaType:alias1" } } }, + { "Filter by name", new OpenApiExample { Value = new JsonArray { "name:nodeName" } } }, }; - private Dictionary SortQueryParameterExamples() => + private Dictionary SortQueryParameterExamples() => new() { - { "Default sort", new OpenApiExample { Value = new OpenApiString(string.Empty) } }, - { - "Sort by create date", - new OpenApiExample - { - Value = new OpenApiArray - { - new OpenApiString("createDate:asc"), new OpenApiString("createDate:desc") - } - } - }, - { - "Sort by name", - new OpenApiExample - { - Value = new OpenApiArray { new OpenApiString("name:asc"), new OpenApiString("name:desc") } - } - }, - { - "Sort by sort order", - new OpenApiExample - { - Value = new OpenApiArray - { - new OpenApiString("sortOrder:asc"), new OpenApiString("sortOrder:desc") - } - } - }, - { - "Sort by update date", - new OpenApiExample - { - Value = new OpenApiArray - { - new OpenApiString("updateDate:asc"), new OpenApiString("updateDate:desc") - } - } - } + { "Default sort", new OpenApiExample { Value = string.Empty } }, + { "Sort by create date", new OpenApiExample { Value = new JsonArray { "createDate:asc", "createDate:desc" } } }, + { "Sort by name", new OpenApiExample { Value = new JsonArray { "name:asc", "name:desc" } } }, + { "Sort by sort order", new OpenApiExample { Value = new JsonArray { "sortOrder:asc", "sortOrder:desc" } } }, + { "Sort by update date", new OpenApiExample { Value = new JsonArray { "updateDate:asc", "updateDate:desc" } } }, }; } diff --git a/src/Umbraco.Cms.Api.Management/Configuration/ConfigureUmbracoManagementApiSwaggerGenOptions.cs b/src/Umbraco.Cms.Api.Management/Configuration/ConfigureUmbracoManagementApiSwaggerGenOptions.cs index 74862e3bab..98ed51f42d 100644 --- a/src/Umbraco.Cms.Api.Management/Configuration/ConfigureUmbracoManagementApiSwaggerGenOptions.cs +++ b/src/Umbraco.Cms.Api.Management/Configuration/ConfigureUmbracoManagementApiSwaggerGenOptions.cs @@ -1,8 +1,8 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; -using Microsoft.OpenApi.Models; +using Microsoft.OpenApi; using Swashbuckle.AspNetCore.SwaggerGen; -using Umbraco.Cms.Api.Common.OpenApi; +using Umbraco.Cms.Api.Common.Security; using Umbraco.Cms.Api.Common.Serialization; using Umbraco.Cms.Api.Management.DependencyInjection; using Umbraco.Cms.Api.Management.OpenApi; @@ -11,7 +11,7 @@ namespace Umbraco.Cms.Api.Management.Configuration; public class ConfigureUmbracoManagementApiSwaggerGenOptions : IConfigureOptions { - private IUmbracoJsonTypeInfoResolver _umbracoJsonTypeInfoResolver; + private readonly IUmbracoJsonTypeInfoResolver _umbracoJsonTypeInfoResolver; public ConfigureUmbracoManagementApiSwaggerGenOptions(IUmbracoJsonTypeInfoResolver umbracoJsonTypeInfoResolver) { @@ -20,7 +20,6 @@ public class ConfigureUmbracoManagementApiSwaggerGenOptions : IConfigureOptions< public void Configure(SwaggerGenOptions swaggerGenOptions) { - swaggerGenOptions.SwaggerDoc( ManagementApiConfiguration.ApiName, new OpenApiInfo @@ -51,10 +50,10 @@ public class ConfigureUmbracoManagementApiSwaggerGenOptions : IConfigureOptions< AuthorizationCode = new OpenApiOAuthFlow { AuthorizationUrl = - new Uri(Common.Security.Paths.BackOfficeApi.AuthorizationEndpoint, UriKind.Relative), - TokenUrl = new Uri(Common.Security.Paths.BackOfficeApi.TokenEndpoint, UriKind.Relative) - } - } + new Uri(Paths.BackOfficeApi.AuthorizationEndpoint, UriKind.Relative), + TokenUrl = new Uri(Paths.BackOfficeApi.TokenEndpoint, UriKind.Relative), + }, + }, }); // Sets Security requirement on backoffice apis diff --git a/src/Umbraco.Cms.Api.Management/OpenApi.json b/src/Umbraco.Cms.Api.Management/OpenApi.json index f64726a323..86d522dd3a 100644 --- a/src/Umbraco.Cms.Api.Management/OpenApi.json +++ b/src/Umbraco.Cms.Api.Management/OpenApi.json @@ -11898,6 +11898,95 @@ ] } }, + "/umbraco/management/api/v1/help": { + "get": { + "tags": [ + "Help" + ], + "operationId": "GetHelp", + "parameters": [ + { + "name": "section", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "tree", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "skip", + "in": "query", + "schema": { + "type": "integer", + "format": "int32", + "default": 0 + } + }, + { + "name": "take", + "in": "query", + "schema": { + "type": "integer", + "format": "int32", + "default": 100 + } + }, + { + "name": "baseUrl", + "in": "query", + "schema": { + "type": "string", + "default": "https://our.umbraco.com" + } + } + ], + "responses": { + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/ProblemDetails" + } + ] + } + } + } + }, + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/PagedHelpPageResponseModel" + } + ] + } + } + } + }, + "401": { + "description": "The resource is protected and requires an authentication token" + } + }, + "deprecated": true, + "security": [ + { + "Backoffice-User": [ ] + } + ] + } + }, "/umbraco/management/api/v1/imaging/resize/urls": { "get": { "tags": [ @@ -20712,6 +20801,14 @@ "format": "int32", "default": 100 } + }, + { + "name": "foldersOnly", + "in": "query", + "schema": { + "type": "boolean", + "default": false + } } ], "responses": { @@ -20773,6 +20870,14 @@ "type": "integer", "format": "int32" } + }, + { + "name": "foldersOnly", + "in": "query", + "schema": { + "type": "boolean", + "default": false + } } ], "responses": { @@ -37753,9 +37858,6 @@ { "$ref": "#/components/schemas/DocumentPropertyValuePermissionPresentationModel" }, - { - "$ref": "#/components/schemas/DocumentTypePermissionPresentationModel" - }, { "$ref": "#/components/schemas/UnknownTypePermissionPresentationModel" } @@ -38042,9 +38144,6 @@ { "$ref": "#/components/schemas/DocumentPropertyValuePermissionPresentationModel" }, - { - "$ref": "#/components/schemas/DocumentTypePermissionPresentationModel" - }, { "$ref": "#/components/schemas/UnknownTypePermissionPresentationModel" } @@ -39443,36 +39542,6 @@ }, "additionalProperties": false }, - "DocumentTypePermissionPresentationModel": { - "required": [ - "$type", - "documentTypeAlias", - "verbs" - ], - "type": "object", - "properties": { - "$type": { - "type": "string" - }, - "verbs": { - "uniqueItems": true, - "type": "array", - "items": { - "type": "string" - } - }, - "documentTypeAlias": { - "type": "string" - } - }, - "additionalProperties": false, - "discriminator": { - "propertyName": "$type", - "mapping": { - "DocumentTypePermissionPresentationModel": "#/components/schemas/DocumentTypePermissionPresentationModel" - } - } - }, "DocumentTypePropertyTypeContainerResponseModel": { "required": [ "id", @@ -48232,9 +48301,6 @@ { "$ref": "#/components/schemas/DocumentPropertyValuePermissionPresentationModel" }, - { - "$ref": "#/components/schemas/DocumentTypePermissionPresentationModel" - }, { "$ref": "#/components/schemas/UnknownTypePermissionPresentationModel" } @@ -48672,9 +48738,6 @@ { "$ref": "#/components/schemas/DocumentPropertyValuePermissionPresentationModel" }, - { - "$ref": "#/components/schemas/DocumentTypePermissionPresentationModel" - }, { "$ref": "#/components/schemas/UnknownTypePermissionPresentationModel" } @@ -49356,5 +49419,160 @@ } } } - } -} + }, + "tags": [ + { + "name": "Culture" + }, + { + "name": "Data Type" + }, + { + "name": "Dictionary" + }, + { + "name": "Document Blueprint" + }, + { + "name": "Document Type" + }, + { + "name": "Document Version" + }, + { + "name": "Document" + }, + { + "name": "Dynamic Root" + }, + { + "name": "Health Check" + }, + { + "name": "Help" + }, + { + "name": "Imaging" + }, + { + "name": "Import" + }, + { + "name": "Indexer" + }, + { + "name": "Install" + }, + { + "name": "Language" + }, + { + "name": "Log Viewer" + }, + { + "name": "Manifest" + }, + { + "name": "Media Type" + }, + { + "name": "Media" + }, + { + "name": "Member Group" + }, + { + "name": "Member Type" + }, + { + "name": "Member" + }, + { + "name": "Models Builder" + }, + { + "name": "News Dashboard" + }, + { + "name": "Object Types" + }, + { + "name": "oEmbed" + }, + { + "name": "Package" + }, + { + "name": "Partial View" + }, + { + "name": "Preview" + }, + { + "name": "Profiling" + }, + { + "name": "Property Type" + }, + { + "name": "Published Cache" + }, + { + "name": "Redirect Management" + }, + { + "name": "Relation Type" + }, + { + "name": "Relation" + }, + { + "name": "Script" + }, + { + "name": "Searcher" + }, + { + "name": "Security" + }, + { + "name": "Segment" + }, + { + "name": "Server" + }, + { + "name": "Static File" + }, + { + "name": "Stylesheet" + }, + { + "name": "Tag" + }, + { + "name": "Telemetry" + }, + { + "name": "Template" + }, + { + "name": "Temporary File" + }, + { + "name": "Upgrade" + }, + { + "name": "User Data" + }, + { + "name": "User Group" + }, + { + "name": "User" + }, + { + "name": "Webhook" + } + ] +} \ No newline at end of file diff --git a/src/Umbraco.Cms.Api.Management/OpenApi/BackOfficeSecurityRequirementsOperationFilterBase.cs b/src/Umbraco.Cms.Api.Management/OpenApi/BackOfficeSecurityRequirementsOperationFilterBase.cs index e2ff1e609a..bcc069f90b 100644 --- a/src/Umbraco.Cms.Api.Management/OpenApi/BackOfficeSecurityRequirementsOperationFilterBase.cs +++ b/src/Umbraco.Cms.Api.Management/OpenApi/BackOfficeSecurityRequirementsOperationFilterBase.cs @@ -1,6 +1,6 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; -using Microsoft.OpenApi.Models; +using Microsoft.OpenApi; using Swashbuckle.AspNetCore.SwaggerGen; using Umbraco.Cms.Api.Management.DependencyInjection; using Umbraco.Extensions; @@ -21,27 +21,17 @@ public abstract class BackOfficeSecurityRequirementsOperationFilterBase : IOpera if (!context.MethodInfo.GetCustomAttributes(true).Any(x => x is AllowAnonymousAttribute) && !(context.MethodInfo.DeclaringType?.GetCustomAttributes(true).Any(x => x is AllowAnonymousAttribute) ?? false)) { - operation.Responses.Add(StatusCodes.Status401Unauthorized.ToString(), new OpenApiResponse - { - Description = "The resource is protected and requires an authentication token" - }); - - operation.Security = new List - { - new OpenApiSecurityRequirement + operation.Responses ??= new OpenApiResponses(); + operation.Responses.Add( + StatusCodes.Status401Unauthorized.ToString(), + new OpenApiResponse { - { - new OpenApiSecurityScheme - { - Reference = new OpenApiReference - { - Type = ReferenceType.SecurityScheme, - Id = ManagementApiConfiguration.ApiSecurityName - } - }, [] - } - } - }; + Description = "The resource is protected and requires an authentication token", + }); + + var schemaRef = new OpenApiSecuritySchemeReference(ManagementApiConfiguration.ApiSecurityName, context.Document); + operation.Security ??= new List(); + operation.Security.Add(new OpenApiSecurityRequirement { [schemaRef] = [] }); } // Assuming if and endpoint have more then one AuthorizeAttribute, there is a risk the user do not have access while still being authorized. @@ -57,10 +47,13 @@ public abstract class BackOfficeSecurityRequirementsOperationFilterBase : IOpera if (numberOfAuthorizeAttributes > 2 || hasConstructorInjectingIAuthorizationService) { - operation.Responses.Add(StatusCodes.Status403Forbidden.ToString(), new OpenApiResponse() - { - Description = "The authenticated user does not have access to this resource" - }); + operation.Responses ??= new OpenApiResponses(); + operation.Responses.Add( + StatusCodes.Status403Forbidden.ToString(), + new OpenApiResponse + { + Description = "The authenticated user does not have access to this resource", + }); } } } diff --git a/src/Umbraco.Cms.Api.Management/OpenApi/NotificationHeaderFilter.cs b/src/Umbraco.Cms.Api.Management/OpenApi/NotificationHeaderFilter.cs index f11c350aae..5872c36b45 100644 --- a/src/Umbraco.Cms.Api.Management/OpenApi/NotificationHeaderFilter.cs +++ b/src/Umbraco.Cms.Api.Management/OpenApi/NotificationHeaderFilter.cs @@ -1,5 +1,5 @@ using Microsoft.AspNetCore.Http; -using Microsoft.OpenApi.Models; +using Microsoft.OpenApi; using Swashbuckle.AspNetCore.SwaggerGen; using Umbraco.Cms.Api.Management.ViewModels; using Umbraco.Cms.Core; @@ -24,28 +24,25 @@ internal sealed class NotificationHeaderFilter : IOperationFilter // filter out irrelevant responses (401 will never produce notifications) IEnumerable relevantResponses = operation - .Responses + .Responses? .Where(pair => pair.Key != StatusCodes.Status401Unauthorized.ToString()) - .Select(pair => pair.Value); + .Select(pair => pair.Value) + .OfType() + ?? Enumerable.Empty(); foreach (OpenApiResponse response in relevantResponses) { - response.Headers.TryAdd(Constants.Headers.Notifications, new OpenApiHeader - { - Description = "The list of notifications produced during the request.", - Schema = new OpenApiSchema + response.Headers ??= new Dictionary(); + response.Headers.TryAdd( + Constants.Headers.Notifications, + new OpenApiHeader { - Type = "array", - Nullable = true, - Items = new OpenApiSchema() + Description = "The list of notifications produced during the request.", + Schema = new OpenApiSchema { - Reference = new OpenApiReference() - { - Type = ReferenceType.Schema, - Id = notificationModelType.Name - }, - } - } - }); + Type = JsonSchemaType.Array | JsonSchemaType.Null, + Items = new OpenApiSchemaReference(notificationModelType.Name), + }, + }); } } } diff --git a/src/Umbraco.Cms.Api.Management/OpenApi/ReponseHeaderOperationFilter.cs b/src/Umbraco.Cms.Api.Management/OpenApi/ReponseHeaderOperationFilter.cs index e639a64091..4bbc032398 100644 --- a/src/Umbraco.Cms.Api.Management/OpenApi/ReponseHeaderOperationFilter.cs +++ b/src/Umbraco.Cms.Api.Management/OpenApi/ReponseHeaderOperationFilter.cs @@ -1,5 +1,5 @@ using Microsoft.AspNetCore.Http; -using Microsoft.OpenApi.Models; +using Microsoft.OpenApi; using Swashbuckle.AspNetCore.SwaggerGen; using Umbraco.Cms.Api.Management.DependencyInjection; using Umbraco.Cms.Core; @@ -11,36 +11,36 @@ internal sealed class ResponseHeaderOperationFilter : IOperationFilter { public void Apply(OpenApiOperation operation, OperationFilterContext context) { - if (context.MethodInfo.HasMapToApiAttribute(ManagementApiConfiguration.ApiName) == false) + if (context.MethodInfo.HasMapToApiAttribute(ManagementApiConfiguration.ApiName) is false || operation.Responses is null) { return; } - foreach ((var key, OpenApiResponse? value) in operation.Responses) + foreach ((var key, IOpenApiResponse value) in operation.Responses) { + if (value is not OpenApiResponse openApiResponse) + { + continue; + } + switch (int.Parse(key)) { case StatusCodes.Status201Created: // NOTE: The header order matters to the back-office client. Do not change. - SetHeader(value, Constants.Headers.GeneratedResource, "Identifier of the newly created resource", "string"); - SetHeader(value, Constants.Headers.Location, "Location of the newly created resource", "string", "uri"); + SetHeader(openApiResponse, Constants.Headers.GeneratedResource, "Identifier of the newly created resource", JsonSchemaType.String); + SetHeader(openApiResponse, Constants.Headers.Location, "Location of the newly created resource", JsonSchemaType.String, "uri"); break; } } } - private static void SetHeader(OpenApiResponse value, string headerName, string description, string type, string? format = null) + private static void SetHeader(OpenApiResponse value, string headerName, string description, JsonSchemaType type, string? format = null) { - - if (value.Headers is null) - { - value.Headers = new Dictionary(); - } - - value.Headers[headerName] = new OpenApiHeader() + value.Headers ??= new Dictionary(); + value.Headers[headerName] = new OpenApiHeader { Description = description, - Schema = new OpenApiSchema { Description = description, Type = type, Format = format } + Schema = new OpenApiSchema { Description = description, Type = type, Format = format }, }; } } diff --git a/src/Umbraco.Cms.Api.Management/OpenApi/RequireNonNullablePropertiesSchemaFilter.cs b/src/Umbraco.Cms.Api.Management/OpenApi/RequireNonNullablePropertiesSchemaFilter.cs index 0d06da8bf6..5c957725d3 100644 --- a/src/Umbraco.Cms.Api.Management/OpenApi/RequireNonNullablePropertiesSchemaFilter.cs +++ b/src/Umbraco.Cms.Api.Management/OpenApi/RequireNonNullablePropertiesSchemaFilter.cs @@ -1,4 +1,4 @@ -using Microsoft.OpenApi.Models; +using Microsoft.OpenApi; using Swashbuckle.AspNetCore.SwaggerGen; namespace Umbraco.Cms.Api.Management.OpenApi; @@ -8,14 +8,21 @@ public class RequireNonNullablePropertiesSchemaFilter : ISchemaFilter /// /// Add to model.Required all properties where Nullable is false. /// - public void Apply(OpenApiSchema model, SchemaFilterContext context) + public void Apply(IOpenApiSchema model, SchemaFilterContext context) { - var additionalRequiredProps = model.Properties - .Where(x => !x.Value.Nullable && !model.Required.Contains(x.Key)) - .Select(x => x.Key); + if (model is not OpenApiSchema schema) + { + return; + } + + IEnumerable additionalRequiredProps = schema.Properties + ?.Where(x => x.Value.Type?.HasFlag(JsonSchemaType.Null) is not true && model.Required?.Contains(x.Key) is not true) + .Select(x => x.Key) + ?? []; + schema.Required ??= new SortedSet(); foreach (var propKey in additionalRequiredProps) { - model.Required.Add(propKey); + schema.Required.Add(propKey); } } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/backend-api/sdk.gen.ts b/src/Umbraco.Web.UI.Client/src/packages/core/backend-api/sdk.gen.ts index 65dfc31492..3ddc93f1d6 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/backend-api/sdk.gen.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/backend-api/sdk.gen.ts @@ -1905,6 +1905,9 @@ export class HealthCheckService { } export class HelpService { + /** + * @deprecated + */ public static getHelp(options?: Options) { return (options?.client ?? client).get({ security: [ diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/backend-api/types.gen.ts b/src/Umbraco.Web.UI.Client/src/packages/core/backend-api/types.gen.ts index 9ea9d69e56..9be02f73c0 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/backend-api/types.gen.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/backend-api/types.gen.ts @@ -412,7 +412,7 @@ export type CreateUserGroupRequestModel = { mediaStartNode?: ReferenceByIdModel | null; mediaRootAccess: boolean; fallbackPermissions: Array; - permissions: Array; + permissions: Array; id?: string | null; }; @@ -471,7 +471,7 @@ export type CurrentUserResponseModel = { hasAccessToAllLanguages: boolean; hasAccessToSensitiveData: boolean; fallbackPermissions: Array; - permissions: Array; + permissions: Array; allowedSections: Array; isAdmin: boolean; }; @@ -773,12 +773,6 @@ export type DocumentTypeItemResponseModel = { description?: string | null; }; -export type DocumentTypePermissionPresentationModel = { - $type: string; - verbs: Array; - documentTypeAlias: string; -}; - export type DocumentTypePropertyTypeContainerResponseModel = { id: string; parent?: ReferenceByIdModel | null; @@ -2812,7 +2806,7 @@ export type UpdateUserGroupRequestModel = { mediaStartNode?: ReferenceByIdModel | null; mediaRootAccess: boolean; fallbackPermissions: Array; - permissions: Array; + permissions: Array; }; export type UpdateUserGroupsOnUserRequestModel = { @@ -2913,7 +2907,7 @@ export type UserGroupResponseModel = { mediaStartNode?: ReferenceByIdModel | null; mediaRootAccess: boolean; fallbackPermissions: Array; - permissions: Array; + permissions: Array; id: string; isDeletable: boolean; aliasCanBeChanged: boolean; @@ -10958,6 +10952,7 @@ export type GetTreeMemberTypeRootData = { query?: { skip?: number; take?: number; + foldersOnly?: boolean; }; url: '/umbraco/management/api/v1/tree/member-type/root'; }; @@ -10989,6 +10984,7 @@ export type GetTreeMemberTypeSiblingsData = { target?: string; before?: number; after?: number; + foldersOnly?: boolean; }; url: '/umbraco/management/api/v1/tree/member-type/siblings'; }; diff --git a/templates/UmbracoExtension/Composers/UmbracoExtensionApiComposer.cs b/templates/UmbracoExtension/Composers/UmbracoExtensionApiComposer.cs index 481e1d19bb..e7112e959c 100644 --- a/templates/UmbracoExtension/Composers/UmbracoExtensionApiComposer.cs +++ b/templates/UmbracoExtension/Composers/UmbracoExtensionApiComposer.cs @@ -3,7 +3,7 @@ using Microsoft.AspNetCore.Mvc.ApiExplorer; using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.Extensions.Options; using Microsoft.Extensions.DependencyInjection; -using Microsoft.OpenApi.Models; +using Microsoft.OpenApi; using Swashbuckle.AspNetCore.SwaggerGen; using Umbraco.Cms.Core.Composing; using Umbraco.Cms.Core.DependencyInjection; diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Core/DeliveryApi/OpenApiContractTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Core/DeliveryApi/OpenApiContractTest.cs index 3514f32d2b..6d74f5ff5e 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Core/DeliveryApi/OpenApiContractTest.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Core/DeliveryApi/OpenApiContractTest.cs @@ -1596,7 +1596,15 @@ internal sealed class OpenApiContractTest : UmbracoTestServerTestBase "additionalProperties": { } } } - } + }, + "tags": [ + { + "name": "Content" + }, + { + "name": "Media" + } + ] } """; } From 2b7efbe861143614ba0621ba0cd2813ed008271b Mon Sep 17 00:00:00 2001 From: Lee Kelleher Date: Mon, 24 Nov 2025 14:41:24 +0100 Subject: [PATCH 6/6] Localization: Restores region-specific cultures (#20939) * Adds localization manifests for region-specific cultures This is to support backwards-compatibility and v13 upgradability. * Removed `uiCulture` from Vietnamese localizations since it duplicated the English fallback texts. * 'en' localization file formatting * Update src/Umbraco.Web.UI.Client/src/assets/lang/en.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../src/assets/lang/en.ts | 42 ++- .../src/assets/lang/vi.ts | 30 -- .../packages/core/localization/manifests.ts | 278 +++++++++++------- 3 files changed, 201 insertions(+), 149 deletions(-) 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 05fa44a2d0..652ae2f129 100644 --- a/src/Umbraco.Web.UI.Client/src/assets/lang/en.ts +++ b/src/Umbraco.Web.UI.Client/src/assets/lang/en.ts @@ -2555,16 +2555,21 @@ export default { }, welcomeDashboard: { umbracoForumHeadline: 'The Umbraco community forum', - umbracoForumDescription: 'The forum is the central hub for the Umbraco developer community. This is where developers, integrators, and contributors come together to ask questions, share knowledge, and collaborate on all things Umbraco.', + umbracoForumDescription: + 'The forum is the central hub for the Umbraco developer community. This is where developers, integrators, and contributors come together to ask questions, share knowledge, and collaborate on all things Umbraco.', umbracoForumButton: 'Visit the Umbraco community forum', umbracoCommunityHeadline: 'The Umbraco community site', - umbracoCommunityDescription: 'The gathering place for all things Umbraco. Whether you write, teach, test, give feedback, or want to connect with others, there’s a way for you to be part of the Friendly Umbraco community.', + umbracoCommunityDescription: + 'The gathering place for all things Umbraco. Whether you write, teach, test, give feedback, or want to connect with others, there’s a way for you to be part of the Friendly Umbraco community.', documentationHeadline: 'Documentation', - documentationDescription: 'Your guide to everything Umbraco. Learn how to get started, explore new features, and discover best practices through clear examples and explanations.', + documentationDescription: + 'Your guide to everything Umbraco. Learn how to get started, explore new features, and discover best practices through clear examples and explanations.', resourcesHeadline: 'Resources', - resourcesDescription: 'Explore Umbraco resources to learn, build, and grow your skills. Find blogs, tutorials, demos, documentation, and videos to help you make the most of Umbraco.', + resourcesDescription: + 'Explore Umbraco resources to learn, build, and grow your skills. Find blogs, tutorials, demos, documentation, and videos to help you make the most of Umbraco.', trainingHeadline: 'Training', - trainingDescription: 'Master Umbraco with official training. Get practical experience through instructor-led courses and earn certifications that help you grow your skills and career.', + trainingDescription: + 'Master Umbraco with official training. Get practical experience through instructor-led courses and earn certifications that help you grow your skills and career.', }, blockEditor: { headlineCreateBlock: 'Pick Element Type', @@ -2877,29 +2882,52 @@ export default { ar: 'العربية', bs: 'Bosanski', cs: 'Česky', + 'cs-cz': 'Česky (Czechia)', cy: 'Cymraeg', + 'cy-gb': 'Cymraeg (UK)', da: 'Dansk', + 'da-dk': 'Dansk (Danmark)', de: 'Deutsch', + 'de-de': 'Deutsch (Deutschland)', + 'de-ch': 'Deutsch (Schweiz)', en: 'English (UK)', 'en-us': 'English (US)', es: 'Español', + 'es-es': 'Español (España)', fr: 'Français', - he: 'Hebrew', + 'fr-fr': 'Français (France)', + 'fr-ch': 'Français (Suisse)', + he: 'עברית', + 'he-il': 'עברית (ישראל)', hr: 'Hrvatski', + 'hr-hr': 'Hrvatski (Hrvatska)', it: 'Italiano', + 'it-it': 'Italiano (Italia)', + 'it-ch': 'Italiano (Svizzera)', ja: '日本語', + 'ja-jp': '日本語 (日本)', ko: '한국어', + 'ko-kr': '한국어 (한국)', nb: 'Norsk Bokmål', + 'nb-no': 'Norsk (Bokmål)', nl: 'Nederlands', + 'nl-nl': 'Nederlands (Nederland)', pl: 'Polski', + 'pl-pl': 'Polski (Polska)', pt: 'Português', 'pt-br': 'Português (Brasil)', - ro: 'Romana', + ro: 'Română', + 'ro-ro': 'Română (România)', ru: 'Русский', + 'ru-ru': 'Русский (Россия)', sv: 'Svenska', + 'sv-se': 'Svenska (Sverige)', tr: 'Türkçe', + 'tr-tr': 'Türkçe (Türkiye Cumhuriyeti)', uk: 'Українська', + 'uk-ua': 'Українська (Україна)', zh: '中文', + 'zh-cn': '中文(简体,中国)', 'zh-tw': '中文(正體,台灣)', vi: 'Tiếng Việt', }, diff --git a/src/Umbraco.Web.UI.Client/src/assets/lang/vi.ts b/src/Umbraco.Web.UI.Client/src/assets/lang/vi.ts index 4eb741360a..dddedeb197 100644 --- a/src/Umbraco.Web.UI.Client/src/assets/lang/vi.ts +++ b/src/Umbraco.Web.UI.Client/src/assets/lang/vi.ts @@ -2819,34 +2819,4 @@ export default { resetUrlMessage: 'Bạn có chắc chắn muốn đặt lại URL này không?', resetUrlLabel: 'Đặt lại', }, - uiCulture: { - ar: 'العربية', - bs: 'Bosanski', - cs: 'Česky', - cy: 'Cymraeg', - da: 'Dansk', - de: 'Deutsch', - en: 'English (UK)', - 'en-us': 'English (US)', - es: 'Español', - fr: 'Français', - he: 'Hebrew', - hr: 'Hrvatski', - it: 'Italiano', - ja: '日本語', - ko: '한국어', - nb: 'Norsk Bokmål', - nl: 'Nederlands', - pl: 'Polski', - pt: 'Português', - 'pt-br': 'Português (Brasil)', - ro: 'Romana', - ru: 'Русский', - sv: 'Svenska', - tr: 'Türkçe', - uk: 'Українська', - zh: '中文', - 'zh-tw': '中文(正體,台灣)', - vi: 'Tiếng Việt', - }, } as UmbLocalizationDictionary; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/localization/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/core/localization/manifests.ts index 17760f4434..f429831c7c 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/localization/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/localization/manifests.ts @@ -4,281 +4,335 @@ export const manifests: Array = [ { type: 'localization', alias: 'Umb.Localization.AR', - weight: 100, name: 'Arabic Backoffice UI Localization', - meta: { - culture: 'ar', - }, + meta: { culture: 'ar' }, js: () => import('../../../assets/lang/ar.js'), }, { type: 'localization', alias: 'Umb.Localization.BS', - weight: 100, name: 'Bosnian Backoffice UI Localization', - meta: { - culture: 'bs', - }, + meta: { culture: 'bs' }, js: () => import('../../../assets/lang/bs.js'), }, { type: 'localization', alias: 'Umb.Localization.CS', - weight: 100, name: 'Czech Backoffice UI Localization', - meta: { - culture: 'cs', - }, + meta: { culture: 'cs' }, js: () => import('../../../assets/lang/cs.js'), }, + { + type: 'localization', + alias: 'Umb.Localization.CS_CZ', + name: 'Czech (Czechia) Backoffice UI Localization', + meta: { culture: 'cs-CZ' }, + }, { type: 'localization', alias: 'Umb.Localization.CY', - weight: 100, name: 'Welsh Backoffice UI Localization', - meta: { - culture: 'cy', - }, + meta: { culture: 'cy' }, js: () => import('../../../assets/lang/cy.js'), }, + { + type: 'localization', + alias: 'Umb.Localization.CY_GB', + name: 'Welsh (UK) Backoffice UI Localization', + meta: { culture: 'cy-GB' }, + }, { type: 'localization', alias: 'Umb.Localization.DA', - weight: 100, name: 'Danish Backoffice UI Localization', - meta: { - culture: 'da', - }, + meta: { culture: 'da' }, js: () => import('../../../assets/lang/da.js'), }, + { + type: 'localization', + alias: 'Umb.Localization.DA-DK', + name: 'Danish (Denmark) Backoffice UI Localization', + meta: { culture: 'da-DK' }, + }, { type: 'localization', alias: 'Umb.Localization.DE', - weight: 100, name: 'German Backoffice UI Localization', - meta: { - culture: 'de', - }, + meta: { culture: 'de' }, js: () => import('../../../assets/lang/de.js'), }, + { + type: 'localization', + alias: 'Umb.Localization.DE_DE', + name: 'German (Germany) Backoffice UI Localization', + meta: { culture: 'de-DE' }, + }, + { + type: 'localization', + alias: 'Umb.Localization.DE_CH', + name: 'German (Switzerland) Backoffice UI Localization', + meta: { culture: 'de-CH' }, + }, { type: 'localization', alias: 'Umb.Localization.EN', - weight: 100, name: 'English (United Kingdom) Backoffice UI Localization', - meta: { - culture: 'en', - }, + meta: { culture: 'en' }, js: () => import('../../../assets/lang/en.js'), }, { type: 'localization', alias: 'Umb.Localization.EN_US', - weight: 100, name: 'English (United States) Backoffice UI Localization', - meta: { - culture: 'en-US', - }, + meta: { culture: 'en-US' }, js: () => import('../../../assets/lang/en-us.js'), }, { type: 'localization', alias: 'Umb.Localization.ES', - weight: 100, name: 'Spanish Backoffice UI Localization', - meta: { - culture: 'es', - }, + meta: { culture: 'es' }, js: () => import('../../../assets/lang/es.js'), }, + { + type: 'localization', + alias: 'Umb.Localization.ES_ES', + name: 'Spanish (Spain) Backoffice UI Localization', + meta: { culture: 'es-ES' }, + }, { type: 'localization', alias: 'Umb.Localization.FR', - weight: 100, name: 'French Backoffice UI Localization', - meta: { - culture: 'fr', - }, + meta: { culture: 'fr' }, js: () => import('../../../assets/lang/fr.js'), }, + { + type: 'localization', + alias: 'Umb.Localization.FR_FR', + name: 'French (France) Backoffice UI Localization', + meta: { culture: 'fr-FR' }, + }, + { + type: 'localization', + alias: 'Umb.Localization.FR_CH', + name: 'French (Switzerland) Backoffice UI Localization', + meta: { culture: 'fr-CH' }, + }, { type: 'localization', alias: 'Umb.Localization.HE', - weight: 100, name: 'Hebrew Backoffice UI Localization', - meta: { - culture: 'he', - }, + meta: { culture: 'he' }, js: () => import('../../../assets/lang/he.js'), }, + { + type: 'localization', + alias: 'Umb.Localization.HE_IL', + name: 'Hebrew (Israel) Backoffice UI Localization', + meta: { culture: 'he-IL' }, + }, { type: 'localization', alias: 'Umb.Localization.HR', - weight: 100, name: 'Croatian Backoffice UI Localization', - meta: { - culture: 'hr', - }, + meta: { culture: 'hr' }, js: () => import('../../../assets/lang/hr.js'), }, + { + type: 'localization', + alias: 'Umb.Localization.HR_HR', + name: 'Croatian (Croatia) Backoffice UI Localization', + meta: { culture: 'hr-HR' }, + }, { type: 'localization', alias: 'Umb.Localization.IT', - weight: 100, name: 'Italian Backoffice UI Localization', - meta: { - culture: 'it', - }, + meta: { culture: 'it' }, js: () => import('../../../assets/lang/it.js'), }, + { + type: 'localization', + alias: 'Umb.Localization.IT_IT', + name: 'Italian (Italy) Backoffice UI Localization', + meta: { culture: 'it-IT' }, + }, + { + type: 'localization', + alias: 'Umb.Localization.IT_CH', + name: 'Italian (Switzerland) Backoffice UI Localization', + meta: { culture: 'it-CH' }, + }, { type: 'localization', alias: 'Umb.Localization.JA', - weight: 100, name: 'Japanese Backoffice UI Localization', - meta: { - culture: 'ja', - }, + meta: { culture: 'ja' }, js: () => import('../../../assets/lang/ja.js'), }, + { + type: 'localization', + alias: 'Umb.Localization.JA_JP', + name: 'Japanese (Japan) Backoffice UI Localization', + meta: { culture: 'ja-JP' }, + }, { type: 'localization', alias: 'Umb.Localization.KO', - weight: 100, name: 'Korean Backoffice UI Localization', - meta: { - culture: 'ko', - }, + meta: { culture: 'ko' }, js: () => import('../../../assets/lang/ko.js'), }, + { + type: 'localization', + alias: 'Umb.Localization.KO_KR', + name: 'Korean (Korea) Backoffice UI Localization', + meta: { culture: 'ko-KR' }, + }, { type: 'localization', alias: 'Umb.Localization.NB', - weight: 100, name: 'Norwegian Backoffice UI Localization', - meta: { - culture: 'nb', - }, + meta: { culture: 'nb' }, js: () => import('../../../assets/lang/nb.js'), }, + { + type: 'localization', + alias: 'Umb.Localization.NB_NO', + name: 'Norwegian (Norway) Backoffice UI Localization', + meta: { culture: 'nb-NO' }, + }, { type: 'localization', alias: 'Umb.Localization.NL', - weight: 100, name: 'Dutch Backoffice UI Localization', - meta: { - culture: 'nl', - }, + meta: { culture: 'nl' }, js: () => import('../../../assets/lang/nl.js'), }, + { + type: 'localization', + alias: 'Umb.Localization.NL_NL', + name: 'Dutch (Netherlands) Backoffice UI Localization', + meta: { culture: 'nl-NL' }, + }, { type: 'localization', alias: 'Umb.Localization.PL', - weight: 100, name: 'Polish Backoffice UI Localization', - meta: { - culture: 'pl', - }, + meta: { culture: 'pl' }, js: () => import('../../../assets/lang/pl.js'), }, + { + type: 'localization', + alias: 'Umb.Localization.PL_PL', + name: 'Polish (Poland) Backoffice UI Localization', + meta: { culture: 'pl-PL' }, + }, { type: 'localization', alias: 'Umb.Localization.PT', - weight: 100, name: 'Portuguese Backoffice UI Localization', - meta: { - culture: 'pt', - }, + meta: { culture: 'pt' }, js: () => import('../../../assets/lang/pt.js'), }, { type: 'localization', alias: 'Umb.Localization.PT_BR', - weight: 100, name: 'Portuguese (Brazil) Backoffice UI Localization', - meta: { - culture: 'pt-BR', - }, + meta: { culture: 'pt-BR' }, js: () => import('../../../assets/lang/pt-br.js'), }, { type: 'localization', alias: 'Umb.Localization.RO', - weight: 100, name: 'Romanian Backoffice UI Localization', - meta: { - culture: 'ro', - }, + meta: { culture: 'ro' }, js: () => import('../../../assets/lang/ro.js'), }, + { + type: 'localization', + alias: 'Umb.Localization.RO_RO', + name: 'Romanian (Romania) Backoffice UI Localization', + meta: { culture: 'ro-RO' }, + }, { type: 'localization', alias: 'Umb.Localization.RU', - weight: 100, name: 'Russian Backoffice UI Localization', - meta: { - culture: 'ru', - }, + meta: { culture: 'ru' }, js: () => import('../../../assets/lang/ru.js'), }, + { + type: 'localization', + alias: 'Umb.Localization.RU_RU', + name: 'Russian (Russia) Backoffice UI Localization', + meta: { culture: 'ru-RU' }, + }, { type: 'localization', alias: 'Umb.Localization.SV', - weight: 100, name: 'Swedish Backoffice UI Localization', - meta: { - culture: 'sv', - }, + meta: { culture: 'sv' }, js: () => import('../../../assets/lang/sv.js'), }, + { + type: 'localization', + alias: 'Umb.Localization.SV_SE', + name: 'Swedish (Sweden) Backoffice UI Localization', + meta: { culture: 'sv-SE' }, + }, { type: 'localization', alias: 'Umb.Localization.TR', - weight: 100, name: 'Turkish Backoffice UI Localization', - meta: { - culture: 'tr', - }, + meta: { culture: 'tr' }, js: () => import('../../../assets/lang/tr.js'), }, + { + type: 'localization', + alias: 'Umb.Localization.TR_TR', + name: 'Turkish (Türkiye) Backoffice UI Localization', + meta: { culture: 'tr-TR' }, + }, { type: 'localization', alias: 'Umb.Localization.UK', - weight: 100, name: 'Ukrainian Backoffice UI Localization', - meta: { - culture: 'uk', - }, + meta: { culture: 'uk' }, js: () => import('../../../assets/lang/uk.js'), }, + { + type: 'localization', + alias: 'Umb.Localization.UK_UA', + name: 'Ukrainian (Ukraine) Backoffice UI Localization', + meta: { culture: 'uk-UA' }, + }, { type: 'localization', alias: 'Umb.Localization.ZH', - weight: 100, name: 'Chinese Backoffice UI Localization', - meta: { - culture: 'zh', - }, + meta: { culture: 'zh' }, js: () => import('../../../assets/lang/zh.js'), }, + { + type: 'localization', + alias: 'Umb.Localization.ZH_CN', + name: 'Chinese (Simplified, China) Backoffice UI Localization', + meta: { culture: 'zh-CN' }, + }, { type: 'localization', alias: 'Umb.Localization.ZH_TW', - weight: 100, name: 'Chinese (Taiwan) Backoffice UI Localization', - meta: { - culture: 'zh-TW', - }, + meta: { culture: 'zh-TW' }, js: () => import('../../../assets/lang/zh-tw.js'), }, { type: 'localization', alias: 'Umb.Localization.VI', - weight: 100, name: 'Vietnamese Backoffice UI Localization', - meta: { - culture: 'vi', - }, + meta: { culture: 'vi' }, js: () => import('../../../assets/lang/vi.js'), }, ];