diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index c0eaf680a3..06fd873638 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -19,6 +19,7 @@ We also encourage community members to feel free to comment on others' pull requ * [What can I contribute?](#what-can-i-contribute) + [Making larger changes](#making-larger-changes) + [Pull request or package?](#pull-request-or-package) + + [Unwanted changes](#unwanted-changes) + [Ownership and copyright](#ownership-and-copyright) - [Finding your first issue: Up for grabs](#finding-your-first-issue-up-for-grabs) - [Making your changes](#making-your-changes) @@ -69,6 +70,25 @@ If you're unsure about whether your changes belong in the core Umbraco CMS or if If it doesn’t fit in CMS right now, we will likely encourage you to make it into a package instead. A package is a great way to check out popularity of a feature, learn how people use it, validate good usability and fix bugs. Eventually, a package could "graduate" to be included in the CMS. +#### Unwanted changes +While most changes are welcome, there are certain types of changes that are discouraged and might get your pull request refused. +Of course this will depend heavily on the specific change, but please take the following examples in mind. + +- **Breaking changes (code and/or behavioral) 💥** - sometimes it can be a bit hard to know if a change is breaking or not. Fortunately, if it relates to code, the build will fail and warn you. +- **Large refactors 🤯** - the larger the refactor, the larger the probability of introducing new bugs/issues. +- **Changes to obsolete code and/or property editors ✍️** +- **Adding new config options 🦾** - while having more flexibility is (most of the times) better, having too many options can also become overwhelming/confusing, especially if there are other (good/simple) ways to achieve it. +- **Whitespace changes 🫥** - while some of our files might not follow the formatting/whitespace rules (mostly old ones), changing several of them in one go would cause major merge conflicts with open pull requests or other work in progress. Do feel free to fix these when you are working on another issue/feature and end up "touching" those files! +- **Adding new extension/helper methods ✋** - keep in mind that more code also means more to maintain, so if a helper is only meaningful for a few, it might not be worth adding it to the core. + +While these are only a few examples, it is important to ask yourself these questions before making a pull request: + +- How many will benefit from this change? +- Are there other ways to achieve this? And if so, how do they compare? +- How maintainable is the change? +- What would be the effort to test it properly? +- Do the benefits outweigh the risks? + #### Ownership and copyright It is your responsibility to make sure that you're allowed to share the code you're providing us. For example, you should have permission from your employer or customer to share code. diff --git a/.github/README.md b/.github/README.md index e633679795..08d7fea3f6 100644 --- a/.github/README.md +++ b/.github/README.md @@ -4,6 +4,7 @@ [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](CONTRIBUTING.md) [![Follow Umbraco on Twitter](https://img.shields.io/badge/Follow-blue?logo=twitter&logoColor=fff)](https://twitter.com/intent/follow?screen_name=umbraco) [![Chat about Umbraco on Discord](https://img.shields.io/discord/869656431308189746?logo=discord&logoColor=fff)](https://discord.gg/umbraco) +[![Read what's going on in the Umbraco Discord chat now](https://img.shields.io/badge/read-discord-blue)](https://discord-chats.umbraco.com) [![Build status](https://img.shields.io/azure-devops/build/umbraco/Umbraco%2520Cms/301?logo=azurepipelines&label=Azure%20Pipelines)](https://umbraco.visualstudio.com/Umbraco%20Cms/_build?definitionId=301) [![Open in GitHub Codespaces](https://img.shields.io/badge/Open%20in%20GitHub%20Codespaces-525252?logo=github)](https://github.com/codespaces/new?hide_repo_select=true&ref=contrib&repo=10601208&machine=basicLinux32gb&devcontainer_path=.devcontainer%2Fdevcontainer.json&location=WestEurope) diff --git a/Directory.Build.props b/Directory.Build.props index a87ecd9ae7..b0b655f4b0 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -38,8 +38,8 @@ - - + + diff --git a/build/azure-pipelines.yml b/build/azure-pipelines.yml index ef825ade5a..8113777d0d 100644 --- a/build/azure-pipelines.yml +++ b/build/azure-pipelines.yml @@ -43,7 +43,7 @@ parameters: default: ' ' variables: - nodeVersion: 18.16.0 + nodeVersion: 18.16.x dotnetVersion: 8.x dotnetIncludePreviewVersions: true solution: umbraco.sln @@ -99,29 +99,12 @@ stages: command: restore projects: $(solution) - task: DotNetCoreCLI@2 - displayName: Run dotnet build + name: build + displayName: Run dotnet build and generate NuGet packages inputs: command: build projects: $(solution) - arguments: '--configuration $(buildConfiguration) --no-restore -p:ContinuousIntegrationBuild=true' - - script: | - version="$(Build.BuildNumber)" - echo "Version: $version" - - major="$(echo $version | cut -d '.' -f 1)" - echo "Major version: $major" - - echo "##vso[task.setvariable variable=majorVersion;isOutput=true]$major" - displayName: Set major version - name: determineMajorVersion - - script: dotnet pack $(solution) --configuration $(buildConfiguration) --no-build --property:PackageOutputPath=$(Build.ArtifactStagingDirectory)/nupkg - displayName: Run dotnet pack - - script: | - sha="$(Build.SourceVersion)" - sha=${sha:0:7} - buildnumber="$(Build.BuildNumber)_$(Build.BuildId)_$sha" - echo "##vso[build.updatebuildnumber]$buildnumber" - displayName: Update build number + arguments: '--configuration $(buildConfiguration) --no-restore --property:ContinuousIntegrationBuild=true --property:GeneratePackageOnBuild=true --property:PackageOutputPath=$(Build.ArtifactStagingDirectory)/nupkg' - task: PublishPipelineArtifact@1 displayName: Publish nupkg inputs: @@ -134,11 +117,11 @@ stages: artifactName: build_output - stage: Build_Docs - condition: and(succeeded(), or(startsWith(variables['Build.SourceBranch'], 'refs/heads/release/'), ${{parameters.buildApiDocs}})) + condition: and(succeeded(), or(eq(dependencies.Build.outputs['A.build.NBGV_PublicRelease'], 'True'), ${{parameters.buildApiDocs}})) displayName: Prepare API Documentation dependsOn: Build variables: - umbracoMajorVersion: $[ stageDependencies.Build.A.outputs['determineMajorVersion.majorVersion'] ] + umbracoMajorVersion: $[ stageDependencies.Build.A.outputs['build.NBGV_VersionMajor'] ] jobs: # C# API Reference - job: @@ -192,9 +175,9 @@ stages: vmImage: 'ubuntu-latest' steps: - task: NodeTool@0 - displayName: Use Node.js 10.15.0 + displayName: Use Node.js 10.15.x inputs: - versionSpec: 10.15.0 # Won't work with higher versions + versionSpec: 10.15.x # Won't work with higher versions - script: | npm ci --no-fund --no-audit --prefer-offline npx gulp docs @@ -262,6 +245,8 @@ stages: - stage: Integration displayName: Integration Tests dependsOn: Build + variables: + releaseTestFilter: eq(dependencies.Build.outputs['A.build.NBGV_PublicRelease'], 'True') jobs: # Integration Tests (SQLite) - job: @@ -294,7 +279,7 @@ stages: command: test projects: '**/*.Tests.Integration.csproj' testRunTitle: Integration Tests SQLite - $(Agent.OS) - ${{ if or( parameters.forceReleaseTestFilter, startsWith(variables['Build.SourceBranch'], 'refs/heads/release/')) }}: + ${{ if or(variables.releaseTestFilter, parameters.forceReleaseTestFilter) }}: arguments: '--configuration $(buildConfiguration) --no-build ${{parameters.integrationReleaseTestFilter}}' ${{ else }}: arguments: '--configuration $(buildConfiguration) ${{parameters.integrationNonReleaseTestFilter}}' @@ -308,7 +293,7 @@ stages: command: test projects: '**/*.Tests.Integration.csproj' testRunTitle: Integration Tests SQLite - $(Agent.OS) - ${{ if or( parameters.forceReleaseTestFilter, startsWith(variables['Build.SourceBranch'], 'refs/heads/release/')) }}: + ${{ if or(variables.releaseTestFilter, parameters.forceReleaseTestFilter) }}: arguments: '--configuration $(buildConfiguration) --no-build ${{parameters.nonWindowsIntegrationReleaseTestFilter}}' ${{ else }}: arguments: '--configuration $(buildConfiguration) ${{parameters.nonWindowsIntegrationNonReleaseTestFilter}}' @@ -319,7 +304,7 @@ stages: # Integration Tests (SQL Server) - job: timeoutInMinutes: 120 - condition: or(startsWith(variables['Build.SourceBranch'], 'refs/heads/release/'), ${{parameters.sqlServerIntegrationTests}}) + condition: or(eq(stageDependencies.Build.A.outputs['build.NBGV_PublicRelease'], 'True'), ${{parameters.sqlServerIntegrationTests}}) displayName: Integration Tests (SQL Server) strategy: matrix: @@ -359,7 +344,7 @@ stages: command: test projects: '**/*.Tests.Integration.csproj' testRunTitle: Integration Tests SQL Server - $(Agent.OS) - ${{ if or( parameters.forceReleaseTestFilter, startsWith(variables['Build.SourceBranch'], 'refs/heads/release/')) }}: + ${{ if or(variables.releaseTestFilter, parameters.forceReleaseTestFilter) }}: arguments: '--configuration $(buildConfiguration) --no-build ${{parameters.integrationReleaseTestFilter}}' ${{ else }}: arguments: '--configuration $(buildConfiguration) --no-build ${{parameters.integrationNonReleaseTestFilter}}' @@ -374,7 +359,7 @@ stages: command: test projects: '**/*.Tests.Integration.csproj' testRunTitle: Integration Tests SQL Server - $(Agent.OS) - ${{ if or( parameters.forceReleaseTestFilter, startsWith(variables['Build.SourceBranch'], 'refs/heads/release/')) }}: + ${{ if or(variables.releaseTestFilter, parameters.forceReleaseTestFilter) }}: arguments: '--configuration $(buildConfiguration) --no-build ${{parameters.nonWindowsIntegrationReleaseTestFilter}}' ${{ else }}: arguments: '--configuration $(buildConfiguration) --no-build ${{parameters.nonWindowsIntegrationNonReleaseTestFilter}}' @@ -535,7 +520,7 @@ stages: - Unit - Integration # - E2E # TODO: Enable when stable. - condition: and(succeeded(), or(startsWith(variables['Build.SourceBranch'], 'refs/heads/release/'), ${{parameters.myGetDeploy}})) + condition: and(succeeded(), or(eq(dependencies.Build.outputs['A.build.NBGV_PublicRelease'], 'True'), ${{parameters.myGetDeploy}})) jobs: - job: displayName: Push to pre-release feed @@ -558,7 +543,7 @@ stages: dependsOn: - Deploy_MyGet - Build_Docs - condition: and(succeeded(), or(startsWith(variables['Build.SourceBranch'], 'refs/heads/release/'), ${{parameters.nuGetDeploy}})) + condition: and(succeeded(), or(eq(dependencies.Build.outputs['A.build.NBGV_PublicRelease'], 'True'), ${{parameters.nuGetDeploy}})) jobs: - job: displayName: Push to NuGet @@ -581,12 +566,12 @@ stages: pool: vmImage: 'windows-latest' # Apparently AzureFileCopy is windows only :( variables: - umbracoMajorVersion: $[ stageDependencies.Build.A.outputs['determineMajorVersion.majorVersion'] ] + umbracoMajorVersion: $[ stageDependencies.Build.A.outputs['build.NBGV_VersionMajor'] ] displayName: Upload API Documention dependsOn: - Build - Deploy_NuGet - condition: and(succeeded(), or(startsWith(variables['Build.SourceBranch'], 'refs/heads/release/'), ${{parameters.uploadApiDocs}})) + condition: and(succeeded(), or(eq(dependencies.Build.outputs['A.build.NBGV_PublicRelease'], 'True'), ${{parameters.uploadApiDocs}})) jobs: - job: displayName: Upload C# Docs diff --git a/src/Umbraco.Cms.Api.Common/DependencyInjection/UmbracoBuilderAuthExtensions.cs b/src/Umbraco.Cms.Api.Common/DependencyInjection/UmbracoBuilderAuthExtensions.cs new file mode 100644 index 0000000000..79ce7cb71e --- /dev/null +++ b/src/Umbraco.Cms.Api.Common/DependencyInjection/UmbracoBuilderAuthExtensions.cs @@ -0,0 +1,106 @@ +using System.Security.Cryptography; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.IdentityModel.Tokens; +using Umbraco.Cms.Api.Common.Security; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.DependencyInjection; +using Umbraco.Cms.Infrastructure.HostedServices; + +namespace Umbraco.Cms.Api.Common.DependencyInjection; + +public static class UmbracoBuilderAuthExtensions +{ + private static bool _initialized; + + public static IUmbracoBuilder AddUmbracoOpenIddict(this IUmbracoBuilder builder) + { + if (_initialized is false) + { + ConfigureOpenIddict(builder); + _initialized = true; + } + + return builder; + } + + private static void ConfigureOpenIddict(IUmbracoBuilder builder) + { + builder.Services.AddOpenIddict() + // Register the OpenIddict server components. + .AddServer(options => + { + // Enable the authorization and token endpoints. + // - important: member endpoints MUST be added before backoffice endpoints to ensure that auto-discovery works for members + // FIXME: swap paths here so member API is first (see comment above) + options + .SetAuthorizationEndpointUris( + Paths.MemberApi.AuthorizationEndpoint.TrimStart(Constants.CharArrays.ForwardSlash)) + .SetTokenEndpointUris( + Paths.MemberApi.TokenEndpoint.TrimStart(Constants.CharArrays.ForwardSlash)) + .SetLogoutEndpointUris( + Paths.MemberApi.LogoutEndpoint.TrimStart(Constants.CharArrays.ForwardSlash)) + .SetRevocationEndpointUris( + Paths.MemberApi.RevokeEndpoint.TrimStart(Constants.CharArrays.ForwardSlash)); + + // Enable authorization code flow with PKCE + options + .AllowAuthorizationCodeFlow() + .RequireProofKeyForCodeExchange() + .AllowRefreshTokenFlow(); + + // Register the ASP.NET Core host and configure for custom authentication endpoint. + options + .UseAspNetCore() + .EnableAuthorizationEndpointPassthrough() + .EnableLogoutEndpointPassthrough(); + + // Enable reference tokens + // - see https://documentation.openiddict.com/configuration/token-storage.html + options + .UseReferenceAccessTokens() + .UseReferenceRefreshTokens(); + + // Use ASP.NET Core Data Protection for tokens instead of JWT. + // This is more secure, and has the added benefit of having a high throughput + // but means that all servers (such as in a load balanced setup) + // needs to use the same application name and key ring, + // however this is already recommended for load balancing, so should be fine. + // See https://documentation.openiddict.com/configuration/token-formats.html#switching-to-data-protection-tokens + // and https://learn.microsoft.com/en-us/aspnet/core/security/data-protection/configuration/overview?view=aspnetcore-7.0 + // for more information + options.UseDataProtection(); + + // Register encryption and signing credentials to protect tokens. + // Note that for tokens generated/validated using ASP.NET Core Data Protection, + // a separate key ring is used, distinct from the credentials discussed in + // https://documentation.openiddict.com/configuration/encryption-and-signing-credentials.html + // More details can be found here: https://github.com/openiddict/openiddict-core/issues/1892#issuecomment-1737308506 + // "When using ASP.NET Core Data Protection to generate opaque tokens, the signing and encryption credentials + // registered via Add*Key/Certificate() are not used". But since OpenIddict requires the registration of such, + // we can generate random keys per instance without them taking effect. + // - see also https://github.com/openiddict/openiddict-core/issues/1231 + options + .AddEncryptionKey(new SymmetricSecurityKey(RandomNumberGenerator.GetBytes(32))) // generate a cryptographically secure random 256-bits key + .AddSigningKey(new RsaSecurityKey(RSA.Create(keySizeInBits: 2048))); // generate RSA key with recommended size of 2048-bits + }) + + // Register the OpenIddict validation components. + .AddValidation(options => + { + // Import the configuration from the local OpenIddict server instance. + options.UseLocalServer(); + + // Register the ASP.NET Core host. + options.UseAspNetCore(); + + // Enable token entry validation + // - see https://documentation.openiddict.com/configuration/token-storage.html#enabling-token-entry-validation-at-the-api-level + options.EnableTokenEntryValidation(); + + // Use ASP.NET Core Data Protection for tokens instead of JWT. (see note in AddServer) + options.UseDataProtection(); + }); + + builder.Services.AddHostedService(); + } +} diff --git a/src/Umbraco.Cms.Api.Common/OpenApi/SwaggerRouteTemplatePipelineFilter.cs b/src/Umbraco.Cms.Api.Common/OpenApi/SwaggerRouteTemplatePipelineFilter.cs index f71badb4cd..4e19042e99 100644 --- a/src/Umbraco.Cms.Api.Common/OpenApi/SwaggerRouteTemplatePipelineFilter.cs +++ b/src/Umbraco.Cms.Api.Common/OpenApi/SwaggerRouteTemplatePipelineFilter.cs @@ -5,6 +5,7 @@ using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Options; using Microsoft.OpenApi.Models; using Swashbuckle.AspNetCore.SwaggerGen; +using Swashbuckle.AspNetCore.SwaggerUI; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Web.Common.ApplicationBuilder; @@ -32,17 +33,8 @@ public class SwaggerRouteTemplatePipelineFilter : UmbracoPipelineFilter { swaggerOptions.RouteTemplate = SwaggerRouteTemplate(applicationBuilder); }); - applicationBuilder.UseSwaggerUI( - swaggerUiOptions => - { - swaggerUiOptions.RoutePrefix = SwaggerUiRoutePrefix(applicationBuilder); - foreach ((var name, OpenApiInfo? apiInfo) in swaggerGenOptions.Value.SwaggerGeneratorOptions.SwaggerDocs - .OrderBy(x => x.Value.Title)) - { - swaggerUiOptions.SwaggerEndpoint($"{name}/swagger.json", $"{apiInfo.Title}"); - } - }); + applicationBuilder.UseSwaggerUI(swaggerUiOptions => SwaggerUiConfiguration(swaggerUiOptions, swaggerGenOptions.Value, applicationBuilder)); } protected virtual bool SwaggerIsEnabled(IApplicationBuilder applicationBuilder) @@ -57,6 +49,22 @@ public class SwaggerRouteTemplatePipelineFilter : UmbracoPipelineFilter protected virtual string SwaggerUiRoutePrefix(IApplicationBuilder applicationBuilder) => $"{GetUmbracoPath(applicationBuilder).TrimStart(Constants.CharArrays.ForwardSlash)}/swagger"; + protected virtual void SwaggerUiConfiguration( + SwaggerUIOptions swaggerUiOptions, + SwaggerGenOptions swaggerGenOptions, + IApplicationBuilder applicationBuilder) + { + swaggerUiOptions.RoutePrefix = SwaggerUiRoutePrefix(applicationBuilder); + + foreach ((var name, OpenApiInfo? apiInfo) in swaggerGenOptions.SwaggerGeneratorOptions.SwaggerDocs + .OrderBy(x => x.Value.Title)) + { + swaggerUiOptions.SwaggerEndpoint($"{name}/swagger.json", $"{apiInfo.Title}"); + } + + swaggerUiOptions.OAuthUsePkce(); + } + private string GetUmbracoPath(IApplicationBuilder applicationBuilder) { GlobalSettings settings = applicationBuilder.ApplicationServices.GetRequiredService>().Value; diff --git a/src/Umbraco.Cms.Api.Common/Security/Paths.cs b/src/Umbraco.Cms.Api.Common/Security/Paths.cs new file mode 100644 index 0000000000..44b6935d12 --- /dev/null +++ b/src/Umbraco.Cms.Api.Common/Security/Paths.cs @@ -0,0 +1,20 @@ +namespace Umbraco.Cms.Api.Common.Security; + +public static class Paths +{ + public static class MemberApi + { + public const string EndpointTemplate = "security/member"; + + public static readonly string AuthorizationEndpoint = EndpointPath($"{EndpointTemplate}/authorize"); + + public static readonly string TokenEndpoint = EndpointPath($"{EndpointTemplate}/token"); + + public static readonly string LogoutEndpoint = EndpointPath($"{EndpointTemplate}/signout"); + + public static readonly string RevokeEndpoint = EndpointPath($"{EndpointTemplate}/revoke"); + + // NOTE: we're NOT using /api/v1.0/ here because it will clash with the Delivery API docs + private static string EndpointPath(string relativePath) => $"/umbraco/delivery/api/v1/{relativePath}"; + } +} diff --git a/src/Umbraco.Cms.Api.Delivery/Configuration/ConfigureUmbracoMemberAuthenticationDeliveryApiSwaggerGenOptions.cs b/src/Umbraco.Cms.Api.Delivery/Configuration/ConfigureUmbracoMemberAuthenticationDeliveryApiSwaggerGenOptions.cs new file mode 100644 index 0000000000..49f5a6fc56 --- /dev/null +++ b/src/Umbraco.Cms.Api.Delivery/Configuration/ConfigureUmbracoMemberAuthenticationDeliveryApiSwaggerGenOptions.cs @@ -0,0 +1,74 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Microsoft.OpenApi.Models; +using Swashbuckle.AspNetCore.SwaggerGen; +using Umbraco.Cms.Api.Common.Security; +using Umbraco.Cms.Api.Delivery.Controllers; +using Umbraco.Cms.Api.Delivery.Filters; + +namespace Umbraco.Cms.Api.Delivery.Configuration; + +/// +/// This configures member authentication for the Delivery API in Swagger. Consult the docs for +/// member authentication within the Delivery API for instructions on how to use this. +/// +/// +/// This class is not used by the core CMS due to the required installation dependencies (local login page among other things). +/// +public class ConfigureUmbracoMemberAuthenticationDeliveryApiSwaggerGenOptions : IConfigureOptions +{ + private const string AuthSchemeName = "Umbraco Member"; + + public void Configure(SwaggerGenOptions options) + { + options.AddSecurityDefinition( + AuthSchemeName, + new OpenApiSecurityScheme + { + In = ParameterLocation.Header, + Name = AuthSchemeName, + Type = SecuritySchemeType.OAuth2, + Description = "Umbraco Member Authentication", + Flows = new OpenApiOAuthFlows + { + AuthorizationCode = new OpenApiOAuthFlow + { + AuthorizationUrl = new Uri(Paths.MemberApi.AuthorizationEndpoint, UriKind.Relative), + TokenUrl = new Uri(Paths.MemberApi.TokenEndpoint, UriKind.Relative) + } + } + }); + + // add security requirements for content API operations + options.OperationFilter(); + } + + private class DeliveryApiSecurityFilter : SwaggerFilterBase, IOperationFilter + { + public void Apply(OpenApiOperation operation, OperationFilterContext context) + { + if (CanApply(context) is false) + { + return; + } + + operation.Security = new List + { + new OpenApiSecurityRequirement + { + { + new OpenApiSecurityScheme + { + Reference = new OpenApiReference + { + Type = ReferenceType.SecurityScheme, + Id = AuthSchemeName, + } + }, + new string[] { } + } + } + }; + } + } +} diff --git a/src/Umbraco.Cms.Api.Delivery/Controllers/Content/ByIdContentApiController.cs b/src/Umbraco.Cms.Api.Delivery/Controllers/Content/ByIdContentApiController.cs index d16afc4d6d..877e662da7 100644 --- a/src/Umbraco.Cms.Api.Delivery/Controllers/Content/ByIdContentApiController.cs +++ b/src/Umbraco.Cms.Api.Delivery/Controllers/Content/ByIdContentApiController.cs @@ -1,7 +1,9 @@ using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Core.DeliveryApi; +using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Models.DeliveryApi; using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Cms.Core.Services; @@ -11,14 +13,41 @@ namespace Umbraco.Cms.Api.Delivery.Controllers.Content; [ApiVersion("1.0")] public class ByIdContentApiController : ContentApiItemControllerBase { + private readonly IRequestMemberAccessService _requestMemberAccessService; + + [Obsolete($"Please use the constructor that does not accept {nameof(IPublicAccessService)}. Will be removed in V14.")] public ByIdContentApiController( IApiPublishedContentCache apiPublishedContentCache, IApiContentResponseBuilder apiContentResponseBuilder, IPublicAccessService publicAccessService) - : base(apiPublishedContentCache, apiContentResponseBuilder, publicAccessService) + : this( + apiPublishedContentCache, + apiContentResponseBuilder, + StaticServiceProvider.Instance.GetRequiredService()) { } + [Obsolete($"Please use the constructor that does not accept {nameof(IPublicAccessService)}. Will be removed in V14.")] + public ByIdContentApiController( + IApiPublishedContentCache apiPublishedContentCache, + IApiContentResponseBuilder apiContentResponseBuilder, + IPublicAccessService publicAccessService, + IRequestMemberAccessService requestMemberAccessService) + : this( + apiPublishedContentCache, + apiContentResponseBuilder, + requestMemberAccessService) + { + } + + [ActivatorUtilitiesConstructor] + public ByIdContentApiController( + IApiPublishedContentCache apiPublishedContentCache, + IApiContentResponseBuilder apiContentResponseBuilder, + IRequestMemberAccessService requestMemberAccessService) + : base(apiPublishedContentCache, apiContentResponseBuilder) + => _requestMemberAccessService = requestMemberAccessService; + /// /// Gets a content item by id. /// @@ -28,6 +57,7 @@ public class ByIdContentApiController : ContentApiItemControllerBase [MapToApiVersion("1.0")] [ProducesResponseType(typeof(IApiContentResponse), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task ById(Guid id) { @@ -38,9 +68,10 @@ public class ByIdContentApiController : ContentApiItemControllerBase return NotFound(); } - if (IsProtected(contentItem)) + IActionResult? deniedAccessResult = await HandleMemberAccessAsync(contentItem, _requestMemberAccessService); + if (deniedAccessResult is not null) { - return Unauthorized(); + return deniedAccessResult; } IApiContentResponse? apiContentResponse = ApiContentResponseBuilder.Build(contentItem); @@ -49,6 +80,6 @@ public class ByIdContentApiController : ContentApiItemControllerBase return NotFound(); } - return await Task.FromResult(Ok(apiContentResponse)); + return Ok(apiContentResponse); } } diff --git a/src/Umbraco.Cms.Api.Delivery/Controllers/Content/ByIdsContentApiController.cs b/src/Umbraco.Cms.Api.Delivery/Controllers/Content/ByIdsContentApiController.cs index 5d415fffe6..df7e3b26a4 100644 --- a/src/Umbraco.Cms.Api.Delivery/Controllers/Content/ByIdsContentApiController.cs +++ b/src/Umbraco.Cms.Api.Delivery/Controllers/Content/ByIdsContentApiController.cs @@ -1,7 +1,9 @@ using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Core.DeliveryApi; +using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Models.DeliveryApi; using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Cms.Core.Services; @@ -12,14 +14,41 @@ namespace Umbraco.Cms.Api.Delivery.Controllers.Content; [ApiVersion("1.0")] public class ByIdsContentApiController : ContentApiItemControllerBase { + private readonly IRequestMemberAccessService _requestMemberAccessService; + + [Obsolete($"Please use the constructor that does not accept {nameof(IPublicAccessService)}. Will be removed in V14.")] public ByIdsContentApiController( IApiPublishedContentCache apiPublishedContentCache, IApiContentResponseBuilder apiContentResponseBuilder, IPublicAccessService publicAccessService) - : base(apiPublishedContentCache, apiContentResponseBuilder, publicAccessService) + : this( + apiPublishedContentCache, + apiContentResponseBuilder, + StaticServiceProvider.Instance.GetRequiredService()) { } + [Obsolete($"Please use the constructor that does not accept {nameof(IPublicAccessService)}. Will be removed in V14.")] + public ByIdsContentApiController( + IApiPublishedContentCache apiPublishedContentCache, + IApiContentResponseBuilder apiContentResponseBuilder, + IPublicAccessService publicAccessService, + IRequestMemberAccessService requestMemberAccessService) + : this( + apiPublishedContentCache, + apiContentResponseBuilder, + requestMemberAccessService) + { + } + + [ActivatorUtilitiesConstructor] + public ByIdsContentApiController( + IApiPublishedContentCache apiPublishedContentCache, + IApiContentResponseBuilder apiContentResponseBuilder, + IRequestMemberAccessService requestMemberAccessService) + : base(apiPublishedContentCache, apiContentResponseBuilder) + => _requestMemberAccessService = requestMemberAccessService; + /// /// Gets content items by ids. /// @@ -28,12 +57,19 @@ public class ByIdsContentApiController : ContentApiItemControllerBase [HttpGet("item")] [MapToApiVersion("1.0")] [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] - public async Task Item([FromQuery(Name = "id")] HashSet ids) + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + public async Task Item([FromQuery(Name = "id")] HashSet ids) { - IEnumerable contentItems = ApiPublishedContentCache.GetByIds(ids); + IPublishedContent[] contentItems = ApiPublishedContentCache.GetByIds(ids).ToArray(); + + IActionResult? deniedAccessResult = await HandleMemberAccessAsync(contentItems, _requestMemberAccessService); + if (deniedAccessResult is not null) + { + return deniedAccessResult; + } IApiContentResponse[] apiContentItems = contentItems - .Where(contentItem => !IsProtected(contentItem)) .Select(ApiContentResponseBuilder.Build) .WhereNotNull() .ToArray(); diff --git a/src/Umbraco.Cms.Api.Delivery/Controllers/Content/ByRouteContentApiController.cs b/src/Umbraco.Cms.Api.Delivery/Controllers/Content/ByRouteContentApiController.cs index 2d22887637..4806db45ff 100644 --- a/src/Umbraco.Cms.Api.Delivery/Controllers/Content/ByRouteContentApiController.cs +++ b/src/Umbraco.Cms.Api.Delivery/Controllers/Content/ByRouteContentApiController.cs @@ -1,8 +1,10 @@ using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Core; using Umbraco.Cms.Core.DeliveryApi; +using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Models.DeliveryApi; using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Cms.Core.Services; @@ -16,7 +18,9 @@ public class ByRouteContentApiController : ContentApiItemControllerBase private readonly IRequestRoutingService _requestRoutingService; private readonly IRequestRedirectService _requestRedirectService; private readonly IRequestPreviewService _requestPreviewService; + private readonly IRequestMemberAccessService _requestMemberAccessService; + [Obsolete($"Please use the constructor that does not accept {nameof(IPublicAccessService)}. Will be removed in V14.")] public ByRouteContentApiController( IApiPublishedContentCache apiPublishedContentCache, IApiContentResponseBuilder apiContentResponseBuilder, @@ -24,11 +28,49 @@ public class ByRouteContentApiController : ContentApiItemControllerBase IRequestRoutingService requestRoutingService, IRequestRedirectService requestRedirectService, IRequestPreviewService requestPreviewService) - : base(apiPublishedContentCache, apiContentResponseBuilder, publicAccessService) + : this( + apiPublishedContentCache, + apiContentResponseBuilder, + requestRoutingService, + requestRedirectService, + requestPreviewService, + StaticServiceProvider.Instance.GetRequiredService()) + { + } + + [Obsolete($"Please use the constructor that does not accept {nameof(IPublicAccessService)}. Will be removed in V14.")] + public ByRouteContentApiController( + IApiPublishedContentCache apiPublishedContentCache, + IApiContentResponseBuilder apiContentResponseBuilder, + IPublicAccessService publicAccessService, + IRequestRoutingService requestRoutingService, + IRequestRedirectService requestRedirectService, + IRequestPreviewService requestPreviewService, + IRequestMemberAccessService requestMemberAccessService) + : this( + apiPublishedContentCache, + apiContentResponseBuilder, + requestRoutingService, + requestRedirectService, + requestPreviewService, + requestMemberAccessService) + { + } + + [ActivatorUtilitiesConstructor] + public ByRouteContentApiController( + IApiPublishedContentCache apiPublishedContentCache, + IApiContentResponseBuilder apiContentResponseBuilder, + IRequestRoutingService requestRoutingService, + IRequestRedirectService requestRedirectService, + IRequestPreviewService requestPreviewService, + IRequestMemberAccessService requestMemberAccessService) + : base(apiPublishedContentCache, apiContentResponseBuilder) { _requestRoutingService = requestRoutingService; _requestRedirectService = requestRedirectService; _requestPreviewService = requestPreviewService; + _requestMemberAccessService = requestMemberAccessService; } /// @@ -44,6 +86,7 @@ public class ByRouteContentApiController : ContentApiItemControllerBase [MapToApiVersion("1.0")] [ProducesResponseType(typeof(IApiContentResponse), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task ByRoute(string path = "") { @@ -55,9 +98,10 @@ public class ByRouteContentApiController : ContentApiItemControllerBase IPublishedContent? contentItem = GetContent(path); if (contentItem is not null) { - if (IsProtected(contentItem)) + IActionResult? deniedAccessResult = await HandleMemberAccessAsync(contentItem, _requestMemberAccessService); + if (deniedAccessResult is not null) { - return Unauthorized(); + return deniedAccessResult; } return await Task.FromResult(Ok(ApiContentResponseBuilder.Build(contentItem))); diff --git a/src/Umbraco.Cms.Api.Delivery/Controllers/Content/ContentApiControllerBase.cs b/src/Umbraco.Cms.Api.Delivery/Controllers/Content/ContentApiControllerBase.cs index ebfa32c479..405da6e15f 100644 --- a/src/Umbraco.Cms.Api.Delivery/Controllers/Content/ContentApiControllerBase.cs +++ b/src/Umbraco.Cms.Api.Delivery/Controllers/Content/ContentApiControllerBase.cs @@ -1,4 +1,5 @@ -using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; using Umbraco.Cms.Api.Common.Builders; using Umbraco.Cms.Api.Delivery.Filters; using Umbraco.Cms.Api.Delivery.Routing; @@ -44,4 +45,14 @@ public abstract class ContentApiControllerBase : DeliveryApiControllerBase .WithDetail($"Content query status \"{status}\" was not expected here") .Build()), }; + + /// + /// Creates a 403 Forbidden result. + /// + /// + /// Use this method instead of on the controller base. The latter will yield + /// a redirect to an access denied URL because of the default cookie auth scheme. This method ensures that a proper + /// 403 Forbidden status code is returned to the client. + /// + protected IActionResult Forbidden() => new StatusCodeResult(StatusCodes.Status403Forbidden); } diff --git a/src/Umbraco.Cms.Api.Delivery/Controllers/Content/ContentApiItemControllerBase.cs b/src/Umbraco.Cms.Api.Delivery/Controllers/Content/ContentApiItemControllerBase.cs index dad11de009..895cd376cd 100644 --- a/src/Umbraco.Cms.Api.Delivery/Controllers/Content/ContentApiItemControllerBase.cs +++ b/src/Umbraco.Cms.Api.Delivery/Controllers/Content/ContentApiItemControllerBase.cs @@ -1,22 +1,58 @@ -using Umbraco.Cms.Core.DeliveryApi; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; +using Umbraco.Cms.Core.DeliveryApi; +using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.Security; using Umbraco.Cms.Core.Services; namespace Umbraco.Cms.Api.Delivery.Controllers.Content; public abstract class ContentApiItemControllerBase : ContentApiControllerBase { + // TODO: Remove this in V14 when the obsolete constructors have been removed private readonly IPublicAccessService _publicAccessService; + [Obsolete($"Please use the constructor that does not accept {nameof(IPublicAccessService)}. Will be removed in V14.")] protected ContentApiItemControllerBase( IApiPublishedContentCache apiPublishedContentCache, IApiContentResponseBuilder apiContentResponseBuilder, IPublicAccessService publicAccessService) - : base(apiPublishedContentCache, apiContentResponseBuilder) - => _publicAccessService = publicAccessService; + : this(apiPublishedContentCache, apiContentResponseBuilder) + { + } - // NOTE: we're going to test for protected content at item endpoint level, because the check has already been - // performed at content index time for the query endpoint and we don't want that extra overhead when - // returning multiple items. + protected ContentApiItemControllerBase( + IApiPublishedContentCache apiPublishedContentCache, + IApiContentResponseBuilder apiContentResponseBuilder) + : base(apiPublishedContentCache, apiContentResponseBuilder) + => _publicAccessService = StaticServiceProvider.Instance.GetRequiredService(); + + [Obsolete($"Please use {nameof(IPublicAccessService)} to test for content protection. Will be removed in V14.")] protected bool IsProtected(IPublishedContent content) => _publicAccessService.IsProtected(content.Path); + + protected async Task HandleMemberAccessAsync(IPublishedContent contentItem, IRequestMemberAccessService requestMemberAccessService) + { + PublicAccessStatus accessStatus = await requestMemberAccessService.MemberHasAccessToAsync(contentItem); + return accessStatus is PublicAccessStatus.AccessAccepted + ? null + : accessStatus is PublicAccessStatus.AccessDenied + ? Forbidden() + : Unauthorized(); + } + + protected async Task HandleMemberAccessAsync(IEnumerable contentItems, IRequestMemberAccessService requestMemberAccessService) + { + foreach (IPublishedContent content in contentItems) + { + IActionResult? result = await HandleMemberAccessAsync(content, requestMemberAccessService); + // if any of the content items yield an error based on the current member access, return that error + if (result is not null) + { + return result; + } + } + + return null; + } } diff --git a/src/Umbraco.Cms.Api.Delivery/Controllers/Content/QueryContentApiController.cs b/src/Umbraco.Cms.Api.Delivery/Controllers/Content/QueryContentApiController.cs index d4db82d9be..2f4e8af9c8 100644 --- a/src/Umbraco.Cms.Api.Delivery/Controllers/Content/QueryContentApiController.cs +++ b/src/Umbraco.Cms.Api.Delivery/Controllers/Content/QueryContentApiController.cs @@ -1,9 +1,11 @@ using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Api.Common.ViewModels.Pagination; using Umbraco.Cms.Core; using Umbraco.Cms.Core.DeliveryApi; +using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.DeliveryApi; using Umbraco.Cms.Core.Models.PublishedContent; @@ -15,14 +17,33 @@ namespace Umbraco.Cms.Api.Delivery.Controllers.Content; [ApiVersion("1.0")] public class QueryContentApiController : ContentApiControllerBase { + private readonly IRequestMemberAccessService _requestMemberAccessService; private readonly IApiContentQueryService _apiContentQueryService; + [Obsolete($"Please use the constructor that accepts {nameof(IRequestMemberAccessService)}. Will be removed in V14.")] public QueryContentApiController( IApiPublishedContentCache apiPublishedContentCache, IApiContentResponseBuilder apiContentResponseBuilderBuilder, IApiContentQueryService apiContentQueryService) + : this( + apiPublishedContentCache, + apiContentResponseBuilderBuilder, + apiContentQueryService, + StaticServiceProvider.Instance.GetRequiredService()) + { + } + + [ActivatorUtilitiesConstructor] + public QueryContentApiController( + IApiPublishedContentCache apiPublishedContentCache, + IApiContentResponseBuilder apiContentResponseBuilderBuilder, + IApiContentQueryService apiContentQueryService, + IRequestMemberAccessService requestMemberAccessService) : base(apiPublishedContentCache, apiContentResponseBuilderBuilder) - => _apiContentQueryService = apiContentQueryService; + { + _apiContentQueryService = apiContentQueryService; + _requestMemberAccessService = requestMemberAccessService; + } /// /// Gets a paginated list of content item(s) from query. @@ -45,7 +66,8 @@ public class QueryContentApiController : ContentApiControllerBase int skip = 0, int take = 10) { - Attempt, ApiContentQueryOperationStatus> queryAttempt = _apiContentQueryService.ExecuteQuery(fetch, filter, sort, skip, take); + ProtectedAccess protectedAccess = await _requestMemberAccessService.MemberAccessAsync(); + Attempt, ApiContentQueryOperationStatus> queryAttempt = _apiContentQueryService.ExecuteQuery(fetch, filter, sort, protectedAccess, skip, take); if (queryAttempt.Success is false) { @@ -62,6 +84,6 @@ public class QueryContentApiController : ContentApiControllerBase Items = apiContentItems }; - return await Task.FromResult(Ok(model)); + return Ok(model); } } diff --git a/src/Umbraco.Cms.Api.Delivery/Controllers/Security/MemberController.cs b/src/Umbraco.Cms.Api.Delivery/Controllers/Security/MemberController.cs new file mode 100644 index 0000000000..a9c670b7ad --- /dev/null +++ b/src/Umbraco.Cms.Api.Delivery/Controllers/Security/MemberController.cs @@ -0,0 +1,171 @@ +using System.Security.Claims; +using Asp.Versioning; +using Microsoft.AspNetCore; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using OpenIddict.Abstractions; +using OpenIddict.Server.AspNetCore; +using Umbraco.Cms.Api.Delivery.Routing; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Security; +using Umbraco.Cms.Web.Common.Security; +using Umbraco.Extensions; +using SignInResult = Microsoft.AspNetCore.Mvc.SignInResult; +using IdentitySignInResult = Microsoft.AspNetCore.Identity.SignInResult; + +namespace Umbraco.Cms.Api.Delivery.Controllers.Security; + +[ApiVersion("1.0")] +[ApiController] +[VersionedDeliveryApiRoute(Common.Security.Paths.MemberApi.EndpointTemplate)] +[ApiExplorerSettings(IgnoreApi = true)] +public class MemberController : DeliveryApiControllerBase +{ + private readonly IHttpContextAccessor _httpContextAccessor; + private readonly IMemberSignInManager _memberSignInManager; + private readonly IMemberManager _memberManager; + private readonly DeliveryApiSettings _deliveryApiSettings; + private readonly ILogger _logger; + + public MemberController( + IHttpContextAccessor httpContextAccessor, + IMemberSignInManager memberSignInManager, + IMemberManager memberManager, + IOptions deliveryApiSettings, + ILogger logger) + { + _httpContextAccessor = httpContextAccessor; + _memberSignInManager = memberSignInManager; + _memberManager = memberManager; + _logger = logger; + _deliveryApiSettings = deliveryApiSettings.Value; + } + + [HttpGet("authorize")] + [MapToApiVersion("1.0")] + public async Task Authorize() + { + // in principle this is not necessary for now, since the member application has been removed, thus making + // the member client ID invalid for the authentication code flow. However, if we ever add additional flows + // to the API, we should perform this check, so we might as well include it upfront. + if (_deliveryApiSettings.MemberAuthorizationIsEnabled() is false) + { + return BadRequest("Member authorization is not allowed."); + } + + HttpContext context = _httpContextAccessor.GetRequiredHttpContext(); + OpenIddictRequest? request = context.GetOpenIddictServerRequest(); + if (request is null) + { + return BadRequest("Unable to obtain OpenID data from the current request."); + } + + // make sure this endpoint ONLY handles member authentication + if (request.ClientId is not Constants.OAuthClientIds.Member) + { + return BadRequest("The specified client ID cannot be used here."); + } + + return request.IdentityProvider.IsNullOrWhiteSpace() + ? await AuthorizeInternal(request) + : await AuthorizeExternal(request); + } + + [HttpGet("signout")] + [MapToApiVersion("1.0")] + public async Task Signout() + { + await _memberSignInManager.SignOutAsync(); + return SignOut(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); + } + + private async Task AuthorizeInternal(OpenIddictRequest request) + { + // retrieve the user principal stored in the authentication cookie. + AuthenticateResult cookieAuthResult = await HttpContext.AuthenticateAsync(IdentityConstants.ApplicationScheme); + var userName = cookieAuthResult.Succeeded + ? cookieAuthResult.Principal?.Identity?.Name + : null; + + if (userName is null) + { + return Challenge(IdentityConstants.ApplicationScheme); + } + + MemberIdentityUser? member = await _memberManager.FindByNameAsync(userName); + if (member is null) + { + _logger.LogError("The member with username {userName} was successfully authorized, but could not be retrieved by the member manager", userName); + return BadRequest("The member could not be found."); + } + + return await SignInMember(member, request); + } + + private async Task AuthorizeExternal(OpenIddictRequest request) + { + var provider = request.IdentityProvider ?? throw new ArgumentException("No identity provider found in request", nameof(request)); + ExternalLoginInfo? loginInfo = await _memberSignInManager.GetExternalLoginInfoAsync(); + + if (loginInfo?.Principal is null) + { + AuthenticationProperties properties = _memberSignInManager.ConfigureExternalAuthenticationProperties(provider, null); + return Challenge(properties, provider); + } + + // NOTE: if we're going to support 2FA for members, we need to: + // - use SecuritySettings.MemberBypassTwoFactorForExternalLogins instead of the hardcoded value (true) for "bypassTwoFactor". + // - handle IdentitySignInResult.TwoFactorRequired + IdentitySignInResult result = await _memberSignInManager.ExternalLoginSignInAsync(loginInfo, false, true); + if (result == IdentitySignInResult.Success) + { + // get the member and perform sign-in + MemberIdentityUser? member = await _memberManager.FindByLoginAsync(loginInfo.LoginProvider, loginInfo.ProviderKey); + if (member is null) + { + _logger.LogError("A member was successfully authorized using external authentication, but could not be retrieved by the member manager"); + return BadRequest("The member could not be found."); + } + + // update member authentication tokens if succeeded + await _memberSignInManager.UpdateExternalAuthenticationTokensAsync(loginInfo); + return await SignInMember(member, request); + } + + var errorProperties = new AuthenticationProperties(new Dictionary + { + [OpenIddictServerAspNetCoreConstants.Properties.Error] = OpenIddictConstants.Errors.AccessDenied, + [OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "The member is not allowed to access this resource." + }); + return Forbid(errorProperties, provider); + } + + private async Task SignInMember(MemberIdentityUser member, OpenIddictRequest request) + { + ClaimsPrincipal memberPrincipal = await _memberSignInManager.CreateUserPrincipalAsync(member); + memberPrincipal.SetClaim(OpenIddictConstants.Claims.Subject, member.Key.ToString()); + + IList roles = await _memberManager.GetRolesAsync(member); + memberPrincipal.SetClaim(Constants.OAuthClaims.MemberKey, member.Key.ToString()); + memberPrincipal.SetClaim(Constants.OAuthClaims.MemberRoles, string.Join(",", roles)); + + Claim[] claims = memberPrincipal.Claims.ToArray(); + foreach (Claim claim in claims.Where(claim => claim.Type is not Constants.Security.SecurityStampClaimType)) + { + claim.SetDestinations(OpenIddictConstants.Destinations.AccessToken); + } + + if (request.GetScopes().Contains(OpenIddictConstants.Scopes.OfflineAccess)) + { + // "offline_access" scope is required to use refresh tokens + memberPrincipal.SetScopes(OpenIddictConstants.Scopes.OfflineAccess); + } + + return new SignInResult(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme, memberPrincipal); + } +} diff --git a/src/Umbraco.Cms.Api.Delivery/DependencyInjection/UmbracoBuilderExtensions.cs b/src/Umbraco.Cms.Api.Delivery/DependencyInjection/UmbracoBuilderExtensions.cs index d87741746a..86c8583708 100644 --- a/src/Umbraco.Cms.Api.Delivery/DependencyInjection/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.Cms.Api.Delivery/DependencyInjection/UmbracoBuilderExtensions.cs @@ -1,15 +1,21 @@ using System.Text.Json; using System.Text.Json.Serialization; +using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Api.Common.DependencyInjection; using Umbraco.Cms.Api.Delivery.Accessors; using Umbraco.Cms.Api.Delivery.Configuration; +using Umbraco.Cms.Api.Delivery.Handlers; using Umbraco.Cms.Api.Delivery.Json; using Umbraco.Cms.Api.Delivery.Rendering; +using Umbraco.Cms.Api.Delivery.Routing; +using Umbraco.Cms.Api.Delivery.Security; using Umbraco.Cms.Api.Delivery.Services; using Umbraco.Cms.Core; using Umbraco.Cms.Core.DeliveryApi; using Umbraco.Cms.Core.DependencyInjection; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Infrastructure.Security; namespace Umbraco.Extensions; @@ -29,6 +35,8 @@ public static class UmbracoBuilderExtensions builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); + builder.Services.AddTransient(); + builder.Services.AddTransient(); builder.Services.ConfigureOptions(); builder.AddUmbracoApiOpenApiUI(); @@ -44,6 +52,16 @@ public static class UmbracoBuilderExtensions options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter()); }); + builder.Services.AddAuthentication(); + builder.AddUmbracoOpenIddict(); + builder.AddNotificationAsyncHandler(); + builder.AddNotificationAsyncHandler(); + builder.AddNotificationAsyncHandler(); + builder.AddNotificationAsyncHandler(); + builder.AddNotificationAsyncHandler(); + + // FIXME: remove this when Delivery API V1 is removed + builder.Services.AddSingleton(); return builder; } } diff --git a/src/Umbraco.Cms.Api.Delivery/Filters/SwaggerDocumentationFilterBase.cs b/src/Umbraco.Cms.Api.Delivery/Filters/SwaggerDocumentationFilterBase.cs index 32791b9b5e..36721cc0f2 100644 --- a/src/Umbraco.Cms.Api.Delivery/Filters/SwaggerDocumentationFilterBase.cs +++ b/src/Umbraco.Cms.Api.Delivery/Filters/SwaggerDocumentationFilterBase.cs @@ -1,18 +1,17 @@ -using System.Reflection; -using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc; using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Models; using Swashbuckle.AspNetCore.SwaggerGen; -using Umbraco.Extensions; namespace Umbraco.Cms.Api.Delivery.Filters; -internal abstract class SwaggerDocumentationFilterBase : IOperationFilter, IParameterFilter +internal abstract class SwaggerDocumentationFilterBase + : SwaggerFilterBase, IOperationFilter, IParameterFilter where TBaseController : Controller { public void Apply(OpenApiOperation operation, OperationFilterContext context) { - if (CanApply(context.MethodInfo)) + if (CanApply(context)) { ApplyOperation(operation, context); } @@ -20,7 +19,7 @@ internal abstract class SwaggerDocumentationFilterBase : IOpera public void Apply(OpenApiParameter parameter, ParameterFilterContext context) { - if (CanApply(context.ParameterInfo.Member)) + if (CanApply(context)) { ApplyParameter(parameter, context); } @@ -76,7 +75,4 @@ internal abstract class SwaggerDocumentationFilterBase : IOpera private string QueryParameterDescription(string description) => $"{description}. Refer to [the documentation]({DocumentationLink}#query-parameters) for more details on this."; - - private bool CanApply(MemberInfo member) - => member.DeclaringType?.Implements() is true; } diff --git a/src/Umbraco.Cms.Api.Delivery/Filters/SwaggerFilterBase.cs b/src/Umbraco.Cms.Api.Delivery/Filters/SwaggerFilterBase.cs new file mode 100644 index 0000000000..7992ac279a --- /dev/null +++ b/src/Umbraco.Cms.Api.Delivery/Filters/SwaggerFilterBase.cs @@ -0,0 +1,19 @@ +using System.Reflection; +using Microsoft.AspNetCore.Mvc; +using Swashbuckle.AspNetCore.SwaggerGen; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Api.Delivery.Filters; + +internal abstract class SwaggerFilterBase + where TBaseController : Controller +{ + protected bool CanApply(OperationFilterContext context) + => CanApply(context.MethodInfo); + + protected bool CanApply(ParameterFilterContext context) + => CanApply(context.ParameterInfo.Member); + + private bool CanApply(MemberInfo member) + => member.DeclaringType?.Implements() is true; +} diff --git a/src/Umbraco.Cms.Api.Delivery/Handlers/InitializeMemberApplicationNotificationHandler.cs b/src/Umbraco.Cms.Api.Delivery/Handlers/InitializeMemberApplicationNotificationHandler.cs new file mode 100644 index 0000000000..242fd47857 --- /dev/null +++ b/src/Umbraco.Cms.Api.Delivery/Handlers/InitializeMemberApplicationNotificationHandler.cs @@ -0,0 +1,79 @@ +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Infrastructure.Security; + +namespace Umbraco.Cms.Api.Delivery.Handlers; + +internal sealed class InitializeMemberApplicationNotificationHandler : INotificationAsyncHandler +{ + private readonly IMemberApplicationManager _memberApplicationManager; + private readonly IRuntimeState _runtimeState; + private readonly ILogger _logger; + private readonly DeliveryApiSettings _deliveryApiSettings; + + public InitializeMemberApplicationNotificationHandler( + IMemberApplicationManager memberApplicationManager, + IRuntimeState runtimeState, + IOptions deliveryApiSettings, + ILogger logger) + { + _memberApplicationManager = memberApplicationManager; + _runtimeState = runtimeState; + _logger = logger; + _deliveryApiSettings = deliveryApiSettings.Value; + } + + public async Task HandleAsync(UmbracoApplicationStartingNotification notification, CancellationToken cancellationToken) + { + if (_runtimeState.Level != RuntimeLevel.Run) + { + return; + } + + if (_deliveryApiSettings.MemberAuthorization?.AuthorizationCodeFlow?.Enabled is not true) + { + await _memberApplicationManager.DeleteMemberApplicationAsync(cancellationToken); + return; + } + + if (ValidateRedirectUrls(_deliveryApiSettings.MemberAuthorization.AuthorizationCodeFlow.LoginRedirectUrls) is false) + { + await _memberApplicationManager.DeleteMemberApplicationAsync(cancellationToken); + return; + } + + if (_deliveryApiSettings.MemberAuthorization.AuthorizationCodeFlow.LogoutRedirectUrls.Any() + && ValidateRedirectUrls(_deliveryApiSettings.MemberAuthorization.AuthorizationCodeFlow.LogoutRedirectUrls) is false) + { + await _memberApplicationManager.DeleteMemberApplicationAsync(cancellationToken); + return; + } + + await _memberApplicationManager.EnsureMemberApplicationAsync( + _deliveryApiSettings.MemberAuthorization.AuthorizationCodeFlow.LoginRedirectUrls, + _deliveryApiSettings.MemberAuthorization.AuthorizationCodeFlow.LogoutRedirectUrls, + cancellationToken); + } + + private bool ValidateRedirectUrls(Uri[] redirectUrls) + { + if (redirectUrls.Any() is false) + { + _logger.LogWarning("No redirect URLs defined for Delivery API member authentication - cannot enable member authentication"); + return false; + } + + if (redirectUrls.All(url => url.IsAbsoluteUri) is false) + { + _logger.LogWarning("All redirect URLs defined for Delivery API member authentication must be absolute - cannot enable member authentication"); + return false; + } + + return true; + } +} diff --git a/src/Umbraco.Cms.Api.Delivery/Handlers/RevokeMemberAuthenticationTokensNotificationHandler.cs b/src/Umbraco.Cms.Api.Delivery/Handlers/RevokeMemberAuthenticationTokensNotificationHandler.cs new file mode 100644 index 0000000000..81d0fb4132 --- /dev/null +++ b/src/Umbraco.Cms.Api.Delivery/Handlers/RevokeMemberAuthenticationTokensNotificationHandler.cs @@ -0,0 +1,102 @@ +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using OpenIddict.Abstractions; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; + +namespace Umbraco.Cms.Api.Delivery.Handlers; + +internal sealed class RevokeMemberAuthenticationTokensNotificationHandler + : INotificationAsyncHandler, + INotificationAsyncHandler, + INotificationAsyncHandler, + INotificationAsyncHandler +{ + private readonly IMemberService _memberService; + private readonly IOpenIddictTokenManager _tokenManager; + private readonly bool _enabled; + private readonly ILogger _logger; + + public RevokeMemberAuthenticationTokensNotificationHandler( + IMemberService memberService, + IOpenIddictTokenManager tokenManager, + IOptions deliveryApiSettings, + ILogger logger) + { + _memberService = memberService; + _tokenManager = tokenManager; + _logger = logger; + _enabled = deliveryApiSettings.Value.MemberAuthorizationIsEnabled(); + } + + public async Task HandleAsync(MemberSavedNotification notification, CancellationToken cancellationToken) + { + if (_enabled is false) + { + return; + } + + foreach (IMember member in notification.SavedEntities.Where(member => member.IsLockedOut || member.IsApproved is false)) + { + // member is locked out and/or un-approved, make sure we revoke all tokens + await RevokeTokensAsync(member); + } + } + + public async Task HandleAsync(MemberDeletedNotification notification, CancellationToken cancellationToken) + { + if (_enabled is false) + { + return; + } + + foreach (IMember member in notification.DeletedEntities) + { + await RevokeTokensAsync(member); + } + } + + public async Task HandleAsync(AssignedMemberRolesNotification notification, CancellationToken cancellationToken) + => await MemberRolesChangedAsync(notification); + + public async Task HandleAsync(RemovedMemberRolesNotification notification, CancellationToken cancellationToken) + => await MemberRolesChangedAsync(notification); + + private async Task RevokeTokensAsync(IMember member) + { + var tokens = await _tokenManager.FindBySubjectAsync(member.Key.ToString()).ToArrayAsync(); + if (tokens.Any() is false) + { + return; + } + + _logger.LogInformation("Deleting {count} active tokens for member with ID {id}", tokens.Length, member.Id); + foreach (var token in tokens) + { + await _tokenManager.DeleteAsync(token); + } + } + + private async Task MemberRolesChangedAsync(MemberRolesNotification notification) + { + if (_enabled is false) + { + return; + } + + foreach (var memberId in notification.MemberIds) + { + IMember? member = _memberService.GetById(memberId); + if (member is null) + { + _logger.LogWarning("Unable to find member with ID {id}", memberId); + continue; + } + + await RevokeTokensAsync(member); + } + } +} diff --git a/src/Umbraco.Cms.Api.Delivery/Routing/DeliveryApiItemsEndpointsMatcherPolicy.cs b/src/Umbraco.Cms.Api.Delivery/Routing/DeliveryApiItemsEndpointsMatcherPolicy.cs new file mode 100644 index 0000000000..ec1b222cd0 --- /dev/null +++ b/src/Umbraco.Cms.Api.Delivery/Routing/DeliveryApiItemsEndpointsMatcherPolicy.cs @@ -0,0 +1,55 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Controllers; +using Microsoft.AspNetCore.Routing; +using Microsoft.AspNetCore.Routing.Matching; +using Umbraco.Cms.Api.Delivery.Controllers; + +namespace Umbraco.Cms.Api.Delivery.Routing; + +// FIXME: remove this when Delivery API V1 is removed +internal sealed class DeliveryApiItemsEndpointsMatcherPolicy : MatcherPolicy, IEndpointSelectorPolicy +{ + public override int Order => 100; + + public bool AppliesToEndpoints(IReadOnlyList endpoints) + { + for (var i = 0; i < endpoints.Count; i++) + { + ControllerActionDescriptor? controllerActionDescriptor = endpoints[i].Metadata.GetMetadata(); + if (IsByIdsController(controllerActionDescriptor) || IsByRouteController(controllerActionDescriptor)) + { + return true; + } + } + + return false; + } + + public Task ApplyAsync(HttpContext httpContext, CandidateSet candidates) + { + var hasIdQueryParameter = httpContext.Request.Query.ContainsKey("id"); + for (var i = 0; i < candidates.Count; i++) + { + ControllerActionDescriptor? controllerActionDescriptor = candidates[i].Endpoint?.Metadata.GetMetadata(); + if (IsByIdsController(controllerActionDescriptor)) + { + candidates.SetValidity(i, hasIdQueryParameter); + } + else if (IsByRouteController(controllerActionDescriptor)) + { + candidates.SetValidity(i, hasIdQueryParameter is false); + } + } + + return Task.CompletedTask; + } + + private static bool IsByIdsController(ControllerActionDescriptor? controllerActionDescriptor) + => IsControllerType(controllerActionDescriptor) || IsControllerType(controllerActionDescriptor); + + private static bool IsByRouteController(ControllerActionDescriptor? controllerActionDescriptor) + => IsControllerType(controllerActionDescriptor) || IsControllerType(controllerActionDescriptor); + + private static bool IsControllerType(ControllerActionDescriptor? controllerActionDescriptor) + => controllerActionDescriptor?.MethodInfo.DeclaringType == typeof(T); +} diff --git a/src/Umbraco.Cms.Api.Delivery/Security/MemberApplicationManager.cs b/src/Umbraco.Cms.Api.Delivery/Security/MemberApplicationManager.cs new file mode 100644 index 0000000000..1e1be7f420 --- /dev/null +++ b/src/Umbraco.Cms.Api.Delivery/Security/MemberApplicationManager.cs @@ -0,0 +1,67 @@ +using OpenIddict.Abstractions; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Infrastructure.Security; + +namespace Umbraco.Cms.Api.Delivery.Security; + +public class MemberApplicationManager : OpenIdDictApplicationManagerBase, IMemberApplicationManager +{ + private readonly IRuntimeState _runtimeState; + + public MemberApplicationManager(IOpenIddictApplicationManager applicationManager, IRuntimeState runtimeState) + : base(applicationManager) + => _runtimeState = runtimeState; + + public async Task EnsureMemberApplicationAsync(IEnumerable loginRedirectUrls, IEnumerable logoutRedirectUrls, CancellationToken cancellationToken = default) + { + if (_runtimeState.Level < RuntimeLevel.Run) + { + return; + } + + Uri[] loginRedirectUrlsArray = loginRedirectUrls as Uri[] ?? loginRedirectUrls.ToArray(); + if (loginRedirectUrlsArray.All(r => r.IsAbsoluteUri) is false) + { + throw new ArgumentException("Expected absolute login redirect URLs for Delivery API member authentication", nameof(loginRedirectUrls)); + } + + Uri[] logoutRedirectUrlsArray = logoutRedirectUrls as Uri[] ?? logoutRedirectUrls.ToArray(); + if (logoutRedirectUrlsArray.All(r => r.IsAbsoluteUri) is false) + { + throw new ArgumentException("Expected absolute logout redirect URLs for Delivery API member authentication", nameof(logoutRedirectUrlsArray)); + } + + var applicationDescriptor = new OpenIddictApplicationDescriptor + { + DisplayName = "Umbraco member access", + ClientId = Constants.OAuthClientIds.Member, + Type = OpenIddictConstants.ClientTypes.Public, + Permissions = + { + OpenIddictConstants.Permissions.Endpoints.Authorization, + OpenIddictConstants.Permissions.Endpoints.Token, + OpenIddictConstants.Permissions.Endpoints.Logout, + OpenIddictConstants.Permissions.Endpoints.Revocation, + OpenIddictConstants.Permissions.GrantTypes.AuthorizationCode, + OpenIddictConstants.Permissions.GrantTypes.RefreshToken, + OpenIddictConstants.Permissions.ResponseTypes.Code + } + }; + + foreach (Uri redirectUrl in loginRedirectUrlsArray) + { + applicationDescriptor.RedirectUris.Add(redirectUrl); + } + + foreach (Uri redirectUrl in logoutRedirectUrlsArray) + { + applicationDescriptor.PostLogoutRedirectUris.Add(redirectUrl); + } + + await CreateOrUpdate(applicationDescriptor, cancellationToken); + } + + public async Task DeleteMemberApplicationAsync(CancellationToken cancellationToken = default) + => await Delete(Constants.OAuthClientIds.Member, cancellationToken); +} diff --git a/src/Umbraco.Cms.Api.Delivery/Services/ApiContentQueryProvider.cs b/src/Umbraco.Cms.Api.Delivery/Services/ApiContentQueryProvider.cs index 45ae32b4f5..73e5bb4a53 100644 --- a/src/Umbraco.Cms.Api.Delivery/Services/ApiContentQueryProvider.cs +++ b/src/Umbraco.Cms.Api.Delivery/Services/ApiContentQueryProvider.cs @@ -3,9 +3,12 @@ using Examine.Lucene.Providers; using Examine.Lucene.Search; using Examine.Search; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.DeliveryApi; using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.DeliveryApi; using Umbraco.Cms.Infrastructure.Examine; using Umbraco.Extensions; @@ -18,6 +21,7 @@ internal sealed class ApiContentQueryProvider : IApiContentQueryProvider { private const string ItemIdFieldName = "itemId"; private readonly IExamineManager _examineManager; + private readonly DeliveryApiSettings _deliveryApiSettings; private readonly ILogger _logger; private readonly string _fallbackGuidValue; private readonly Dictionary _fieldTypes; @@ -25,9 +29,11 @@ internal sealed class ApiContentQueryProvider : IApiContentQueryProvider public ApiContentQueryProvider( IExamineManager examineManager, ContentIndexHandlerCollection indexHandlers, + IOptions deliveryApiSettings, ILogger logger) { _examineManager = examineManager; + _deliveryApiSettings = deliveryApiSettings.Value; _logger = logger; // A fallback value is needed for Examine queries in case we don't have a value - we can't pass null or empty string @@ -41,7 +47,27 @@ internal sealed class ApiContentQueryProvider : IApiContentQueryProvider .ToDictionary(field => field.FieldName, field => field.FieldType, StringComparer.InvariantCultureIgnoreCase); } - public PagedModel ExecuteQuery(SelectorOption selectorOption, IList filterOptions, IList sortOptions, string culture, bool preview, int skip, int take) + [Obsolete($"Use the {nameof(ExecuteQuery)} method that accepts {nameof(ProtectedAccess)}. Will be removed in V14.")] + public PagedModel ExecuteQuery( + SelectorOption selectorOption, + IList filterOptions, + IList sortOptions, + string culture, + bool preview, + int skip, + int take) + => ExecuteQuery(selectorOption, filterOptions, sortOptions, culture, ProtectedAccess.None, preview, skip, take); + + /// + public PagedModel ExecuteQuery( + SelectorOption selectorOption, + IList filterOptions, + IList sortOptions, + string culture, + ProtectedAccess protectedAccess, + bool preview, + int skip, + int take) { if (!_examineManager.TryGetIndex(Constants.UmbracoIndexes.DeliveryApiContentIndexName, out IIndex? index)) { @@ -49,7 +75,7 @@ internal sealed class ApiContentQueryProvider : IApiContentQueryProvider return new PagedModel(); } - IBooleanOperation queryOperation = BuildSelectorOperation(selectorOption, index, culture, preview); + IBooleanOperation queryOperation = BuildSelectorOperation(selectorOption, index, culture, protectedAccess, preview); ApplyFiltering(filterOptions, queryOperation); ApplySorting(sortOptions, queryOperation); @@ -77,7 +103,7 @@ internal sealed class ApiContentQueryProvider : IApiContentQueryProvider FieldName = UmbracoExamineFieldNames.CategoryFieldName, Values = new[] { "content" } }; - private IBooleanOperation BuildSelectorOperation(SelectorOption selectorOption, IIndex index, string culture, bool preview) + private IBooleanOperation BuildSelectorOperation(SelectorOption selectorOption, IIndex index, string culture, ProtectedAccess protectedAccess, bool preview) { // Needed for enabling leading wildcards searches BaseLuceneSearcher searcher = index.Searcher as BaseLuceneSearcher ?? throw new InvalidOperationException($"Index searcher must be of type {nameof(BaseLuceneSearcher)}."); @@ -92,8 +118,12 @@ internal sealed class ApiContentQueryProvider : IApiContentQueryProvider ? query.Field(selectorOption.FieldName, selectorOption.Values.First()) : query.GroupedOr(new[] { selectorOption.FieldName }, selectorOption.Values); - // Item culture must be either the requested culture or "none" - selectorOperation.And().GroupedOr(new[] { UmbracoExamineFieldNames.DeliveryApiContentIndex.Culture }, culture.ToLowerInvariant().IfNullOrWhiteSpace(_fallbackGuidValue), "none"); + AddCultureQuery(culture, selectorOperation); + + if (_deliveryApiSettings.MemberAuthorizationIsEnabled()) + { + AddProtectedAccessQuery(protectedAccess, selectorOperation); + } // when not fetching for preview, make sure the "published" field is "y" if (preview is false) @@ -104,6 +134,45 @@ internal sealed class ApiContentQueryProvider : IApiContentQueryProvider return selectorOperation; } + private void AddCultureQuery(string culture, IBooleanOperation selectorOperation) => + selectorOperation + .And() + .GroupedOr( + // Item culture must be either the requested culture or "none" + new[] { UmbracoExamineFieldNames.DeliveryApiContentIndex.Culture }, + culture.ToLowerInvariant().IfNullOrWhiteSpace(_fallbackGuidValue), + "none"); + + private void AddProtectedAccessQuery(ProtectedAccess protectedAccess, IBooleanOperation selectorOperation) + { + var protectedAccessValues = new List(); + if (protectedAccess.MemberKey is not null) + { + protectedAccessValues.Add($"u:{protectedAccess.MemberKey}"); + } + + if (protectedAccess.MemberRoles?.Any() is true) + { + protectedAccessValues.AddRange(protectedAccess.MemberRoles.Select(r => $"r:{r}")); + } + + if (protectedAccessValues.Any()) + { + selectorOperation.And( + inner => inner + .Field(UmbracoExamineFieldNames.DeliveryApiContentIndex.Protected, "n") + .Or(protectedAccessInner => protectedAccessInner + .GroupedOr( + new[] { UmbracoExamineFieldNames.DeliveryApiContentIndex.ProtectedAccess }, + protectedAccessValues.ToArray())), + BooleanOperation.Or); + } + else + { + selectorOperation.And().Field(UmbracoExamineFieldNames.DeliveryApiContentIndex.Protected, "n"); + } + } + private void ApplyFiltering(IList filterOptions, IBooleanOperation queryOperation) { void HandleExact(IQuery query, string fieldName, string[] values) diff --git a/src/Umbraco.Cms.Api.Delivery/Services/ApiContentQueryService.cs b/src/Umbraco.Cms.Api.Delivery/Services/ApiContentQueryService.cs index dc03a79e19..a81ae211ed 100644 --- a/src/Umbraco.Cms.Api.Delivery/Services/ApiContentQueryService.cs +++ b/src/Umbraco.Cms.Api.Delivery/Services/ApiContentQueryService.cs @@ -2,6 +2,7 @@ using Umbraco.Cms.Api.Delivery.Indexing.Selectors; using Umbraco.Cms.Core; using Umbraco.Cms.Core.DeliveryApi; using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.DeliveryApi; using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Cms.Core.Services.OperationStatus; using Umbraco.Extensions; @@ -36,8 +37,23 @@ internal sealed class ApiContentQueryService : IApiContentQueryService _requestPreviewService = requestPreviewService; } + [Obsolete($"Use the {nameof(ExecuteQuery)} method that accepts {nameof(ProtectedAccess)}. Will be removed in V14.")] + public Attempt, ApiContentQueryOperationStatus> ExecuteQuery( + string? fetch, + IEnumerable filters, + IEnumerable sorts, + int skip, + int take) + => ExecuteQuery(fetch, filters, sorts, ProtectedAccess.None, skip, take); + /// - public Attempt, ApiContentQueryOperationStatus> ExecuteQuery(string? fetch, IEnumerable filters, IEnumerable sorts, int skip, int take) + public Attempt, ApiContentQueryOperationStatus> ExecuteQuery( + string? fetch, + IEnumerable filters, + IEnumerable sorts, + ProtectedAccess protectedAccess, + int skip, + int take) { var emptyResult = new PagedModel(); @@ -77,7 +93,7 @@ internal sealed class ApiContentQueryService : IApiContentQueryService var culture = _variationContextAccessor.VariationContext?.Culture ?? string.Empty; var isPreview = _requestPreviewService.IsPreview(); - PagedModel result = _apiContentQueryProvider.ExecuteQuery(selectorOption, filterOptions, sortOptions, culture, isPreview, skip, take); + PagedModel result = _apiContentQueryProvider.ExecuteQuery(selectorOption, filterOptions, sortOptions, culture, protectedAccess, isPreview, skip, take); return Attempt.SucceedWithStatus(ApiContentQueryOperationStatus.Success, result); } diff --git a/src/Umbraco.Cms.Api.Delivery/Services/RequestMemberAccessService.cs b/src/Umbraco.Cms.Api.Delivery/Services/RequestMemberAccessService.cs new file mode 100644 index 0000000000..c0bd9c1b9e --- /dev/null +++ b/src/Umbraco.Cms.Api.Delivery/Services/RequestMemberAccessService.cs @@ -0,0 +1,84 @@ +using System.Security.Claims; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Options; +using OpenIddict.Abstractions; +using OpenIddict.Validation.AspNetCore; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.DeliveryApi; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.DeliveryApi; +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.Security; +using Umbraco.Cms.Core.Services; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Api.Delivery.Services; + +internal sealed class RequestMemberAccessService : IRequestMemberAccessService +{ + private readonly IHttpContextAccessor _httpContextAccessor; + private readonly IPublicAccessService _publicAccessService; + private readonly IPublicAccessChecker _publicAccessChecker; + private readonly DeliveryApiSettings _deliveryApiSettings; + + public RequestMemberAccessService( + IHttpContextAccessor httpContextAccessor, + IPublicAccessService publicAccessService, + IPublicAccessChecker publicAccessChecker, + IOptions deliveryApiSettings) + { + _httpContextAccessor = httpContextAccessor; + _publicAccessService = publicAccessService; + _publicAccessChecker = publicAccessChecker; + + _deliveryApiSettings = deliveryApiSettings.Value; + } + + public async Task MemberHasAccessToAsync(IPublishedContent content) + { + PublicAccessEntry? publicAccessEntry = _publicAccessService.GetEntryForContent(content.Path); + if (publicAccessEntry is null) + { + return PublicAccessStatus.AccessAccepted; + } + + ClaimsPrincipal? requestPrincipal = await GetRequestPrincipal(); + if (requestPrincipal is null) + { + return PublicAccessStatus.NotLoggedIn; + } + + return await _publicAccessChecker.HasMemberAccessToContentAsync(content.Id, requestPrincipal); + } + + public async Task MemberAccessAsync() + { + ClaimsPrincipal? requestPrincipal = await GetRequestPrincipal(); + return new ProtectedAccess(MemberKey(requestPrincipal), MemberRoles(requestPrincipal)); + } + + private async Task GetRequestPrincipal() + { + // exit fast if no member authorization is enabled whatsoever + if (_deliveryApiSettings.MemberAuthorizationIsEnabled() is false) + { + return null; + } + + HttpContext httpContext = _httpContextAccessor.GetRequiredHttpContext(); + AuthenticateResult result = await httpContext.AuthenticateAsync(OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme); + return result.Succeeded + ? result.Principal + : null; + } + + private static Guid? MemberKey(ClaimsPrincipal? claimsPrincipal) + => claimsPrincipal is not null && Guid.TryParse(claimsPrincipal.GetClaim(Constants.OAuthClaims.MemberKey), out Guid memberKey) + ? memberKey + : null; + + private static string[]? MemberRoles(ClaimsPrincipal? claimsPrincipal) + => claimsPrincipal?.GetClaim(Constants.OAuthClaims.MemberRoles)?.Split(Constants.CharArrays.Comma); +} diff --git a/src/Umbraco.Cms.Imaging.ImageSharp/Media/ImageSharpImageUrlGenerator.cs b/src/Umbraco.Cms.Imaging.ImageSharp/Media/ImageSharpImageUrlGenerator.cs index 5b437902f5..afcd0f35a2 100644 --- a/src/Umbraco.Cms.Imaging.ImageSharp/Media/ImageSharpImageUrlGenerator.cs +++ b/src/Umbraco.Cms.Imaging.ImageSharp/Media/ImageSharpImageUrlGenerator.cs @@ -49,6 +49,7 @@ public sealed class ImageSharpImageUrlGenerator : IImageUrlGenerator /// Initializes a new instance of the class. /// /// The supported image file types/extensions. + /// The ImageSharp middleware options. /// Contains helpers that allow authorization of image requests. /// /// This constructor is only used for testing. diff --git a/src/Umbraco.Cms.Persistence.EFCore/Locking/SqlServerEFCoreDistributedLockingMechanism.cs b/src/Umbraco.Cms.Persistence.EFCore/Locking/SqlServerEFCoreDistributedLockingMechanism.cs index d5d83f8ecf..ef9b9443ae 100644 --- a/src/Umbraco.Cms.Persistence.EFCore/Locking/SqlServerEFCoreDistributedLockingMechanism.cs +++ b/src/Umbraco.Cms.Persistence.EFCore/Locking/SqlServerEFCoreDistributedLockingMechanism.cs @@ -22,7 +22,7 @@ internal class SqlServerEFCoreDistributedLockingMechanism : IDistributedLocki private readonly Lazy> _scopeAccessor; // Hooray it's a circular dependency. /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// public SqlServerEFCoreDistributedLockingMechanism( ILogger> logger, diff --git a/src/Umbraco.Core/Cache/Refreshers/CacheRefresherBase.cs b/src/Umbraco.Core/Cache/Refreshers/CacheRefresherBase.cs index 849d42309a..0873d32cb8 100644 --- a/src/Umbraco.Core/Cache/Refreshers/CacheRefresherBase.cs +++ b/src/Umbraco.Core/Cache/Refreshers/CacheRefresherBase.cs @@ -35,7 +35,7 @@ public abstract class CacheRefresherBase : ICacheRefresher public abstract string Name { get; } /// - /// Gets the for + /// Gets the for . /// protected ICacheRefresherNotificationFactory NotificationFactory { get; } diff --git a/src/Umbraco.Core/Cache/Refreshers/JsonCacheRefresherBase.cs b/src/Umbraco.Core/Cache/Refreshers/JsonCacheRefresherBase.cs index b22cff56d2..f638ab34b0 100644 --- a/src/Umbraco.Core/Cache/Refreshers/JsonCacheRefresherBase.cs +++ b/src/Umbraco.Core/Cache/Refreshers/JsonCacheRefresherBase.cs @@ -14,7 +14,7 @@ public abstract class JsonCacheRefresherBase : Cach where TNotification : CacheRefresherNotification { /// - /// Initializes a new instance of the . + /// Initializes a new instance of the class. /// protected JsonCacheRefresherBase( AppCaches appCaches, diff --git a/src/Umbraco.Core/Collections/EventClearingObservableCollection.cs b/src/Umbraco.Core/Collections/EventClearingObservableCollection.cs index baf131ca80..3b0994c614 100644 --- a/src/Umbraco.Core/Collections/EventClearingObservableCollection.cs +++ b/src/Umbraco.Core/Collections/EventClearingObservableCollection.cs @@ -37,7 +37,7 @@ public class EventClearingObservableCollection : ObservableCollection - /// Clears all event handlers for the event + /// Clears all event handlers for the event. /// public void ClearCollectionChangedEvents() => _changed = null; diff --git a/src/Umbraco.Core/Collections/ObservableDictionary.cs b/src/Umbraco.Core/Collections/ObservableDictionary.cs index 9e52b4dae7..8d920bbe98 100644 --- a/src/Umbraco.Core/Collections/ObservableDictionary.cs +++ b/src/Umbraco.Core/Collections/ObservableDictionary.cs @@ -1,4 +1,4 @@ -using System.Collections.ObjectModel; +using System.Collections.ObjectModel; using System.Collections.Specialized; namespace Umbraco.Cms.Core.Collections; @@ -84,7 +84,7 @@ public class ObservableDictionary : ObservableCollection, } /// - /// Clears all event handlers + /// Clears all event handlers /// public void ClearCollectionChangedEvents() => _changed = null; diff --git a/src/Umbraco.Core/Configuration/IUmbracoVersion.cs b/src/Umbraco.Core/Configuration/IUmbracoVersion.cs index 3672f28dae..99a5cddfdb 100644 --- a/src/Umbraco.Core/Configuration/IUmbracoVersion.cs +++ b/src/Umbraco.Core/Configuration/IUmbracoVersion.cs @@ -1,3 +1,4 @@ +using System.Reflection; using Umbraco.Cms.Core.Semver; namespace Umbraco.Cms.Core.Configuration; diff --git a/src/Umbraco.Core/Configuration/Models/DeliveryApiSettings.cs b/src/Umbraco.Core/Configuration/Models/DeliveryApiSettings.cs index 69b1943c60..c81102b8d2 100644 --- a/src/Umbraco.Core/Configuration/Models/DeliveryApiSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/DeliveryApiSettings.cs @@ -54,6 +54,19 @@ public class DeliveryApiSettings /// public MediaSettings Media { get; set; } = new (); + /// + /// Gets or sets the member authorization settings for the Delivery API. + /// + public MemberAuthorizationSettings? MemberAuthorization { get; set; } = null; + + /// + /// Gets a value indicating if any member authorization type is enabled for the Delivery API. + /// + /// + /// This method is intended for future extension - see remark in . + /// + public bool MemberAuthorizationIsEnabled() => MemberAuthorization?.AuthorizationCodeFlow?.Enabled is true; + /// /// Typed configuration options for the Media APIs of the Delivery API. /// @@ -84,4 +97,45 @@ public class DeliveryApiSettings [DefaultValue(StaticPublicAccess)] public bool PublicAccess { get; set; } = StaticPublicAccess; } + + /// + /// Typed configuration options for member authorization settings for the Delivery API. + /// + /// + /// This class is intended for future extension, if/when adding support for additional + /// authorization flows (i.e. non-interactive authorization flows). + /// + public class MemberAuthorizationSettings + { + /// + /// Gets or sets the Authorization Code Flow configuration for the Delivery API. + /// + public AuthorizationCodeFlowSettings? AuthorizationCodeFlow { get; set; } = null; + } + + /// + /// Typed configuration options for the Authorization Code Flow settings for the Delivery API. + /// + public class AuthorizationCodeFlowSettings + { + /// + /// Gets or sets a value indicating whether Authorization Code Flow should be enabled for the Delivery API. + /// + /// true if Authorization Code Flow should be enabled; otherwise, false. + [DefaultValue(StaticEnabled)] + public bool Enabled { get; set; } = StaticEnabled; + + /// + /// Gets or sets the URLs allowed to use as redirect targets after a successful login (session authorization). + /// + /// The URLs allowed as redirect targets. + public Uri[] LoginRedirectUrls { get; set; } = Array.Empty(); + + /// + /// Gets or sets the URLs allowed to use as redirect targets after a successful logout (session termination). + /// + /// The URLs allowed as redirect targets. + /// These are only required if logout is to be used. + public Uri[] LogoutRedirectUrls { get; set; } = Array.Empty(); + } } diff --git a/src/Umbraco.Core/Constants-CharArrays.cs b/src/Umbraco.Core/Constants-CharArrays.cs index 832cac00e6..98a450e9c8 100644 --- a/src/Umbraco.Core/Constants-CharArrays.cs +++ b/src/Umbraco.Core/Constants-CharArrays.cs @@ -53,7 +53,7 @@ public static partial class Constants public static readonly char[] Comma = { ',' }; /// - /// Char array containing only & + /// Char array containing only & /// public static readonly char[] Ampersand = { '&' }; @@ -88,7 +88,7 @@ public static partial class Constants public static readonly char[] QuestionMark = { '?' }; /// - /// Char array containing ? & + /// Char array containing ? & /// public static readonly char[] QuestionMarkAmpersand = { '?', '&' }; diff --git a/src/Umbraco.Core/Constants-OAuthClaims.cs b/src/Umbraco.Core/Constants-OAuthClaims.cs new file mode 100644 index 0000000000..19e92b20e6 --- /dev/null +++ b/src/Umbraco.Core/Constants-OAuthClaims.cs @@ -0,0 +1,17 @@ +namespace Umbraco.Cms.Core; + +public static partial class Constants +{ + public static class OAuthClaims + { + /// + /// Key for authenticated member. + /// + public const string MemberKey = "umbraco-member-key"; + + /// + /// Roles for authenticated member. + /// + public const string MemberRoles = "umbraco-member-roles"; + } +} diff --git a/src/Umbraco.Core/Constants-OAuthClientIds.cs b/src/Umbraco.Core/Constants-OAuthClientIds.cs new file mode 100644 index 0000000000..1938618fca --- /dev/null +++ b/src/Umbraco.Core/Constants-OAuthClientIds.cs @@ -0,0 +1,12 @@ +namespace Umbraco.Cms.Core; + +public static partial class Constants +{ + public static class OAuthClientIds + { + /// + /// Client ID used for member access. + /// + public const string Member = "umbraco-member"; + } +} diff --git a/src/Umbraco.Core/DelegateEqualityComparer.cs b/src/Umbraco.Core/DelegateEqualityComparer.cs index 8a442e8f85..44d12364cb 100644 --- a/src/Umbraco.Core/DelegateEqualityComparer.cs +++ b/src/Umbraco.Core/DelegateEqualityComparer.cs @@ -33,8 +33,8 @@ public class DelegateEqualityComparer : IEqualityComparer /// /// true if the specified objects are equal; otherwise, false. /// - /// The first object of type to compare. - /// The second object of type to compare. + /// The first object of type to compare. + /// The second object of type to compare. public bool Equals(T? x, T? y) => _equals.Invoke(x, y); /// diff --git a/src/Umbraco.Core/DeliveryApi/IApiContentQueryProvider.cs b/src/Umbraco.Core/DeliveryApi/IApiContentQueryProvider.cs index 69d7025d60..1e49ae7ffa 100644 --- a/src/Umbraco.Core/DeliveryApi/IApiContentQueryProvider.cs +++ b/src/Umbraco.Core/DeliveryApi/IApiContentQueryProvider.cs @@ -1,4 +1,5 @@ using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.DeliveryApi; namespace Umbraco.Cms.Core.DeliveryApi; @@ -7,6 +8,16 @@ namespace Umbraco.Cms.Core.DeliveryApi; /// public interface IApiContentQueryProvider { + [Obsolete($"Use the {nameof(ExecuteQuery)} method that accepts {nameof(ProtectedAccess)}. Will be removed in V14.")] + PagedModel ExecuteQuery( + SelectorOption selectorOption, + IList filterOptions, + IList sortOptions, + string culture, + bool preview, + int skip, + int take); + /// /// Returns a page of item ids that passed the search criteria. /// @@ -15,10 +26,19 @@ public interface IApiContentQueryProvider /// The sorting options of the search criteria. /// The requested culture. /// Whether or not to search for preview content. + /// Defines the limitations for querying protected content. /// Number of search results to skip (for pagination). /// Number of search results to retrieve (for pagination). /// A paged model containing the resulting IDs and the total number of results that matching the search criteria. - PagedModel ExecuteQuery(SelectorOption selectorOption, IList filterOptions, IList sortOptions, string culture, bool preview, int skip, int take); + PagedModel ExecuteQuery( + SelectorOption selectorOption, + IList filterOptions, + IList sortOptions, + string culture, + ProtectedAccess protectedAccess, + bool preview, + int skip, + int take) => new(); /// /// Returns a selector option that can be applied to fetch "all content" (i.e. if a selector option is not present when performing a search). diff --git a/src/Umbraco.Core/DeliveryApi/IApiContentQueryService.cs b/src/Umbraco.Core/DeliveryApi/IApiContentQueryService.cs index 4a01cd926c..03ff82b9c2 100644 --- a/src/Umbraco.Core/DeliveryApi/IApiContentQueryService.cs +++ b/src/Umbraco.Core/DeliveryApi/IApiContentQueryService.cs @@ -1,4 +1,5 @@ using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.DeliveryApi; using Umbraco.Cms.Core.Services.OperationStatus; namespace Umbraco.Cms.Core.DeliveryApi; @@ -8,6 +9,9 @@ namespace Umbraco.Cms.Core.DeliveryApi; /// public interface IApiContentQueryService { + [Obsolete($"Use the {nameof(ExecuteQuery)} method that accepts {nameof(ProtectedAccess)}. Will be removed in V14.")] + Attempt, ApiContentQueryOperationStatus> ExecuteQuery(string? fetch, IEnumerable filters, IEnumerable sorts, int skip, int take); + /// /// Returns an attempt with a collection of item ids that passed the search criteria as a paged model. /// @@ -16,6 +20,8 @@ public interface IApiContentQueryService /// Optional sort query parameters values. /// The amount of items to skip. /// The amount of items to take. + /// Defines the limitations for querying protected content. /// A paged model of item ids that are returned after applying the search queries in an attempt. - Attempt, ApiContentQueryOperationStatus> ExecuteQuery(string? fetch, IEnumerable filters, IEnumerable sorts, int skip, int take); + Attempt, ApiContentQueryOperationStatus> ExecuteQuery(string? fetch, IEnumerable filters, IEnumerable sorts, ProtectedAccess protectedAccess, int skip, int take) + => default; } diff --git a/src/Umbraco.Core/DeliveryApi/IRequestMemberAccessService.cs b/src/Umbraco.Core/DeliveryApi/IRequestMemberAccessService.cs new file mode 100644 index 0000000000..4d41e97fcb --- /dev/null +++ b/src/Umbraco.Core/DeliveryApi/IRequestMemberAccessService.cs @@ -0,0 +1,12 @@ +using Umbraco.Cms.Core.Models.DeliveryApi; +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.Security; + +namespace Umbraco.Cms.Core.DeliveryApi; + +public interface IRequestMemberAccessService +{ + Task MemberHasAccessToAsync(IPublishedContent content); + + Task MemberAccessAsync(); +} diff --git a/src/Umbraco.Core/DeliveryApi/NoopApiContentQueryService.cs b/src/Umbraco.Core/DeliveryApi/NoopApiContentQueryService.cs index d8dda6313c..49cf782d40 100644 --- a/src/Umbraco.Core/DeliveryApi/NoopApiContentQueryService.cs +++ b/src/Umbraco.Core/DeliveryApi/NoopApiContentQueryService.cs @@ -1,11 +1,16 @@ using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.DeliveryApi; using Umbraco.Cms.Core.Services.OperationStatus; namespace Umbraco.Cms.Core.DeliveryApi; public sealed class NoopApiContentQueryService : IApiContentQueryService { - /// + [Obsolete($"Use the {nameof(ExecuteQuery)} method that accepts {nameof(ProtectedAccess)}. Will be removed in V14.")] public Attempt, ApiContentQueryOperationStatus> ExecuteQuery(string? fetch, IEnumerable filters, IEnumerable sorts, int skip, int take) => Attempt.SucceedWithStatus(ApiContentQueryOperationStatus.Success, new PagedModel()); + + /// + public Attempt, ApiContentQueryOperationStatus> ExecuteQuery(string? fetch, IEnumerable filters, IEnumerable sorts, ProtectedAccess protectedAccess, int skip, int take) + => Attempt.SucceedWithStatus(ApiContentQueryOperationStatus.Success, new PagedModel()); } diff --git a/src/Umbraco.Core/DeliveryApi/NoopRequestMemberAccessService.cs b/src/Umbraco.Core/DeliveryApi/NoopRequestMemberAccessService.cs new file mode 100644 index 0000000000..b16a1cf89b --- /dev/null +++ b/src/Umbraco.Core/DeliveryApi/NoopRequestMemberAccessService.cs @@ -0,0 +1,12 @@ +using Umbraco.Cms.Core.Models.DeliveryApi; +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.Security; + +namespace Umbraco.Cms.Core.DeliveryApi; + +public sealed class NoopRequestMemberAccessService : IRequestMemberAccessService +{ + public Task MemberHasAccessToAsync(IPublishedContent content) => Task.FromResult(PublicAccessStatus.AccessAccepted); + + public Task MemberAccessAsync() => Task.FromResult(ProtectedAccess.None); +} diff --git a/src/Umbraco.Core/DependencyInjection/ServiceProviderExtensions.cs b/src/Umbraco.Core/DependencyInjection/ServiceProviderExtensions.cs index 9c2202e2aa..a12cc21dd6 100644 --- a/src/Umbraco.Core/DependencyInjection/ServiceProviderExtensions.cs +++ b/src/Umbraco.Core/DependencyInjection/ServiceProviderExtensions.cs @@ -6,7 +6,7 @@ using Umbraco.Cms.Core.Models.PublishedContent; namespace Umbraco.Extensions; /// -/// Provides extension methods to the class. +/// Provides extension methods to the class. /// public static class ServiceProviderExtensions { @@ -28,7 +28,7 @@ public static class ServiceProviderExtensions /// /// Creates an instance of a service, with arguments. /// - /// The + /// The . /// The type of the instance. /// Named arguments. /// An instance of the specified type. diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/bs.xml b/src/Umbraco.Core/EmbeddedResources/Lang/bs.xml index 212840974c..d7093a2ba5 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/bs.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/bs.xml @@ -158,6 +158,12 @@ Više opcija za objavljivanje Pošalji + + Mediji je izbrisan + Mediji premješten + Mediji kopiran + Mediji spremljen + Pregled za Sadržaj je izbrisan @@ -2653,7 +2659,7 @@ Da upravljate svojom web lokacijom, jednostavno otvorite Umbraco backoffice i po Želite savladati Umbraco? Provedite nekoliko minuta učeći neke najbolje prakse gledajući jedan od ovih videozapisa o korištenju Umbraco-a Umbraco Learning Base Youtube kanal. Ovdje možete pronaći gomilu video materijala koji pokriva mnoge aspekte Umbraco-a.

+

Želite savladati Umbraco? Provedite nekoliko minuta učeći neke najbolje prakse gledajući jedan od ovih videozapisa o korištenju Umbraco-a Umbraco Learning Base Youtube kanal. Ovdje možete pronaći gomilu video materijala koji pokriva mnoge aspekte Umbraco-a.

]]>
Za početak diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/cs.xml b/src/Umbraco.Core/EmbeddedResources/Lang/cs.xml index 07d7c6d28f..347f963e70 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/cs.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/cs.xml @@ -151,6 +151,12 @@ Potvrdit Další možnosti publikování + + Média smazán + Média přesunut + Média zkopírován + Média uložen + Zobrazení pro Obsah smazán diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/cy.xml b/src/Umbraco.Core/EmbeddedResources/Lang/cy.xml index 50db692223..86520b6d03 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/cy.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/cy.xml @@ -35,7 +35,6 @@ Dileu Ailenwi Adfer - Gosod hawliau ar gyfer y dudalen %0% Dewis ble i copïo Dewis ble i symud Yn y strwythyr goeden isod @@ -58,6 +57,7 @@ Ail-anfon Gwahoddiad Golygu cynnwys Dewiswch ble i fewnforio + Cuddio opsiynau nad ydynt ar gael Cynnwys @@ -93,7 +93,8 @@ Dim hawl. - Ychwanegu Parth newydd + Ychwanegu parth newydd + Ychwanegu parth cyfredol dileu Nod annilys. Fformat parth annilys. @@ -104,7 +105,7 @@ Parth '%0%' wedi dileu Parth '%0%' wedi neilltuo eisoes Parth '%0%' wedi diweddaru - Golygu Parthau Presennol + Golygu Parthau Cyfredol @@ -144,11 +145,9 @@ Achub Achub a cau Achub a chyhoeddi - Achub ac amserlenni Achub ac anfon am gymeradwyo Achub gwedd rhestr Amserlenni - Rhagolwg Save and preview Rhagolwg wedi analluogi gan nad oes templed wedi'i neilltuo Dewis arddull @@ -158,13 +157,17 @@ Achub a chynhyrchu modelau Dadwneud Ail-wneud - Rolio yn ôl Dileu tag Canslo Cadarnhau Mwy opsiynau cyhoeddi - Submit - Submit and close + Cyflwyno + + + Cyfrwng wedi'i dileu + Cyfrwng wedi'i symud + Cyfrwng wedi'i copïo + Cyfrwng wedi'i achub Dangos am @@ -180,7 +183,6 @@ Cynnwys wedi'i rolio yn ôl Cynnwys wedi'i anfon i Gyhoeddi Cynnwys wedi'i anfon i gyhoeddi am y ieithoedd: %0% - Cynnwys wedi'i anfon i gyfieithu Trefnu eitemau blant cyflawnwyd gan ddefnyddiwr %0% Copïo @@ -195,7 +197,6 @@ Rolio yn ôl Anfon i Gyhoeddi Anfon i Gyhoeddi - Anfon i Gyfieithu Tefnu Arferu Hanes (pob amrywiad) @@ -205,8 +206,6 @@ Achub - Methwyd creu ffolder o dan id rhiant %0% - Methwyd creu ffolder o dan rhiant efo enw %0% Mae'r enw'r ffolder methu cynnwys nodau anghyfreithlon. Methwyd dileu eitem: %0% @@ -296,23 +295,13 @@ Cynnwys eitemau cynnwys heb eu cyhoeddi. Mae'r gwerth yma'n gudd. Os ydych chi angen hawl i weld y gwerth yma, cysylltwch â gweinyddwr eich gwefan. Mae'r gwerth yma'n gudd. - Pa ieithoedd yr hoffech chi eu cyhoeddi? Mae pob iaith sydd â chynnwys wei cael ei arbed! Pa ieithoedd yr hoffech chi eu cyhoeddi? - Pa ieithoedd yr hoffech chi eu arbed? - Mae pob iaith sydd â chynnwys yn cael ei arbed wrth greu! Pa ieithoedd hoffech chi anfon am gymeradwyaeth? Pa ieithoedd yr hoffech chi eu hamserlennu? Dewiswch yr ieithoedd i'w anghyhoeddi. Bydd anghyhoeddi iaith orfodol yn anghyhoeddi pob iaith. - Ieithoedd Cyhoeddedig - Ieithoedd heb ei gyhoeddi - Ieithoedd heb eu haddasu - Nid yw'r ieithoedd hyn wedi'u creu Bydd pob amrywiad newydd yn cael ei arbed. P'un amrywiadau wyt ti eisiau cyhoeddi? Dewiswch pa amrywiadau wyt ti eisiau arbed. - Dewiswch pa amrywiadau i anfon am gymeradwyaeth. - Gosod cyhoeddi rhestredig... - Dewiswch yr amrywiadau i'w anghyhoeddi. Bydd anghyhoeddi iaith orfodol yn anghyhoeddi pob amrywiad. Mae'r amrywiadau canlynol yn ofynnol er mwyn i gyhoeddi: Ni ddim yn barod i Gyhoeddi Barod i Gyhoeddi? @@ -341,23 +330,18 @@ Cliciwch i lanlwytho - Gollyngwch eich ffeiliau yma... - Dolen i gyfrwng neu cliciwch yma i ddewis ffeiliau - Gallwch lusgo ffeiliau yma i lanlwtho. - Dim ond mathau caniatol o ffeil sydd Ni ellir lanlwytho'r ffeil yma, nid yw math y ffeil yn wedi'i gymeradwyo Maint ffeil uchaf Gwraidd gyfrwng - Methwyd symud cyfrwng Ni all y ffolderi rhiant a chyrchfan fod yr un peth - Methwyd copïo cyfrwng Methwyd creu ffolder o dan id rhiant %0% Methwyd ailenwi'r ffolder gyda id %0% Llusgo a gollwng eich ffeil(iau) i mewn i'r ardal Ni chaniateir llwytho i fyny yn y lleoliad hwn. Ni ellir lanlwytho'r ffeil yma, ni chaniateir y math cyfrwng gydag alias '%0%' yma Ni ellir lanlwytho'r ffeil yma, nid oes ganddi enw ffeil dilys + Mae un neu fwy o ddilysiadau diogelwch ffeil wedi methu Creu aelod newydd @@ -368,6 +352,7 @@ Mae gan yr aelod gyfrinair yn barod Nid yw cloi allan wedi'i alluogi ar gyfer yr aelod hwn Nid yw'r aelod yn y grŵp '%0%' + Prawf Dilysu Dau Gam Wedi methu copïo'r fath cynnwys @@ -417,7 +402,6 @@ Macro rhan-wedd newydd (heb macro) Ffeil ddalen arddull newydd Ffeil ddalen arddull Golygydd Testun Cyfoethog newydd - Macro rhan-wedd wag newydd Pori eich gwefan @@ -466,17 +450,12 @@ Dolen Angor / llinyn ymholi Enw - Gweinyddu enwau gwesteia Cau'r ffenestr yma Ydych chi'n sicr eich bod eisiau dileu %0% yn seiliedig ar %1%]]> - Ydych chi'n sicr eich bod eisiau analluogi - Wyt ti'n siŵr fod ti eisiau dileu %0%]]> - %0%]]> - Ydych chi'n sicr? Ydych chi'n sicr? Torri @@ -497,7 +476,6 @@ Dolen fewnol: Wrth ddefnyddio dolenni leol, defnyddiwch "#" o flaen y ddolen Agor mewn ffenestr newydd? - Gosodiadau Macro Nid yw'r macro yma yn cynnwys unrhyw briodweddau gallwch chi olygu Gludo Golygu hawliau ar gyfer @@ -516,25 +494,15 @@ Bydd storfa'r wefan yn cael ei adnewyddu. Bydd holl gynnwys cyhoeddi yn cael ei ddiweddaru, ac bydd holl gynnwys sydd heb ei gyhoeddi yn dal i fod heb ei gyhoeddi. Nifer o golofnau Nifer o resi - - Gosodwch id dalfan wrth osod ID ar eich dalfan gallwch chwistrellu cynnwys i mewn i'r templed yma o dempledi blentyn, - wrth gyfeirio at yr ID yma gan ddefnyddio elfen <asp:content />.]]> - - - Dewiswch id dalfan o'r rhestr isod. Gallwch ddim ond - ddewis Id (neu sawl) o feistr y dempled bresennol.]]> - Cliciwch ar y llun i weld y maint llawn Dewis eitem Gweld Eitem Storfa - Creu ffolder... Perthnasu at y gwreiddiol Cynnwys disgynyddion Y gymuned fwyaf cyfeillgar Dolen i dudalen Agor y ddolen ddogfen mewn ffenestr neu tab newydd Dolen i gyfrwng - Dolen i ffeil Dewis nod cychwyn cynnwys Dewis cyfrwng Dewis y math o gyfrwng @@ -592,7 +560,6 @@ ]]> Enw Diwylliant - Golygu allwedd yr eitem geiriadur. Mae'r broses yn cymryd mwy o amser na'r disgwyl, gwiriwch y log Umbraco i weld os mae wedi bod unrhyw wall yn ystod y gweithrediad hwn Ni ellir ailadeiladu'r mynegai hwn oherwydd nad yw wedi'i aseinio IIndexPopulator + + + Cynnwys yn y mynegai + Ni ddarganfuwyd unrhyw ganlyniadau + Dangos %0% - %1% o %2% canlyniad(au) - Tudalen %3% o %4% Darparwch eich enw defnyddiwr @@ -649,25 +621,10 @@ Darparwch enw arall... Yn generadu enw arall... Creu eitem - Creu Golygu Enw - Caniatáu ar y gwraidd - Dim ond Mathau o Gynnwys gyda hwn wedi ticio all gael eu creu ar lefel wraidd coed Cynnwys a Chyfrwng - Mathau o nod blentyn caniataol - Cyfansoddiadau Mathau o Ddogfen - Creu - Dileu tab - Disgrifiad - Tab newydd - Tab - Ciplun bach - Galluogi gwedd rhestr - Ffurfweddu'r eitem gynnwysi ddangos rhestr trefnadwy & a chwiladwy o'i phlant, ni fydd y plant yn cael eu dangos yn y goeden - Gwedd rhestr bresennol - Y fath o ddata gwedd rhestr gweithredol Creu gwedd rhestr pwrpasol Dileu gwedd rhestr pwrpasol Mae math o gynnwys, math o gyfrwng neu math o aeold gyda'r enw arall yma'n bodoli eisoes @@ -689,10 +646,6 @@ Taflenni arddull perthnasol Dangos label Lled ac uchder - Holl fathau o briodweddau & data priodwedd - yn defnyddio'r fath yma o ddata yn cael eu dileu yn barhaol, cadarnhewch eich bod eisiau dileu'r rhain hefyd - Iawn, dileu - a holl fathau o briodwedd & data priodwedd sy'n defnyddio'r math o ddata yma Dewiswch y ffolder i symud i'r strwythyr goeden isod wedi symud o dan @@ -720,25 +673,15 @@ Darparwch yr enw ac yr enw arall ar y math o briodwedd newydd! Mae yna broblem gyda hawliau darllen/ysgrifennu i ffeil neu ffolder penodol Gwall yn llwytho sgript Rhan-Wedd (ffeil: %0%) - Gwall yn llwytho Rheolydd Defnyddiwr '%0%' - Gwall yn llwytho Rheolydd Pwrpasol (Gwasaneth: %0%, Math: '%1%') - Gwall yn llwytho sgript MacroEngine (ffeil: %0%) - "Gwall yn dosbarthu'r ffeil XSLT: %0% - "Gwall yn darllen y ffeil XSLT: %0% Darparwch deitl Dewiswch fath Rydych ar fîn gwneud y llun yn fwy 'na'r maint gwreiddiol. Ydych chi'n sicr eich bod eisiau parhau? - Gwall yn y sgript python - Nid yw'r sgript python wedi'i achub gan ei fod yn cynnwys gwall(au) Nod gychwynnol wedi'i ddileu, cysylltwch â'ch gweinyddwr Marciwch gynnwys cyn newid arddull Dim arddulliau gweithredol ar gael Symudwch y cyrchwr ar ochr chwith y ddwy gell yr ydych eisiau cyfuno Ni allwch hollti cell sydd heb ei gyfuno. Mae gan briodwedd hon gwallau - Gwall yn y ffynhonnell XSLT - Nid yw'r XSLTwedi'i achub gan ei fod yn cynnwys gwall(au) - Mae gwall ffurfwedd gyda'r math o ddata sy'n cael ei ddefnyddio ar gyfer y priodwedd yma, gwiriwch y fath o ddata Mae methiant anhysbys wedi digwydd Methiant cydsyniadau optimistaidd, gwrthrych wedi'i addasu @@ -803,7 +746,6 @@ Eicon Id Mewnforio - Cynnwys is-ffolderi wrth chwilio Search only this folder Chwilio yn ffolder hwn yn unig Gwybodaeth Ymyl mewnol @@ -825,7 +767,6 @@ Gofynnol Neges Symud - Mwy Enw Newydd Nesaf @@ -840,7 +781,6 @@ Trefnu wrth Cyfrinair Llwybr - ID Dalfan Un eiliad os gwelwch yn dda... Blaenorol Priodweddau @@ -902,9 +842,7 @@ Erthyglau Fideos Clirio - Arsefydlu Avatar am - Cyfryngau Enw Nôd Darllen fwy @@ -915,27 +853,13 @@ Pennawd maes system Diweddarwyd Diwethaf + Newid + Gwybodaeth Umbraco + Neidio i'r dewislen + Neidio i'r cynnwys - Du - Gwyrdd - Melyn - Oren Glas - Llwyd Las - Llwyd - Brown - Glas Golau - Gwyrddlas - Gwyrdd Golau - Leim - Melyngoch - Oren Ddwfn - Coch - Pinc - Piws - Piws Ddwfn - Dulas Ychwanegu tab @@ -960,7 +884,6 @@ Cyffredinol Golygydd Toglo caniatáu amrywiadau diwylliant - Toglo caniatáu segmentiad Lliw cefndir @@ -979,7 +902,7 @@ Ffurfwedd gronfa ddata Trwy glicio ar y botwm nesaf (neu addasu'r umbracoConfigurationStatus yn web.config), rydych chi'n derbyn y drwydded ar gyfer y feddalwedd hon fel y nodir yn y blwch isod. Sylwch fod y dosbarthiad Umbraco hwn yn cynnwys dwy drwydded wahanol, y drwydded MIT ffynhonnell agored ar gyfer y fframwaith a thrwydded radwedd Umbraco sy'n cwmpasu'r UI. To finish the installation, you'll need to manually edit the - <strong>/web.config file</strong> a diweddaru'r allwedd AppSetting <strong>UmbracoConfigurationStatus</strong> yn y gwaelod i'r werth <strong>'%0%'</strong>. + /web.config file a diweddaru'r allwedd AppSetting UmbracoConfigurationStatus yn y gwaelod i'r werth '%0%'. Cod dilysu Rhowch y cod dilysu os gwelwch yn dda Cod annilys wedi'i nodi + Umbraco: Cod Diogelwch + Eich cod diogelwch yw: %0% Dashfwrdd @@ -1376,36 +1301,16 @@ Er mwyn gweinyddu eich gwefan, agorwch swyddfa gefn Umbraco a dechreuwch ychwang ]]> Bydd hwn yn dileu'r pecyn - Gollwng i lanlwytho Cynhwyswch yr holl nodau plentyn - neu cliciwch yma i ddewis ffeil pecyn - Lanlwytho pecyn - Gosod pecyn leol wrth ddewis o'ch peiriant. Dylwch ddim ond osod pecynnau o ffynonellau yr ydych yn adnabod a bod gennych hyder ynddynt - Lanlwytho pecyn arall - Canslo a lanlwytho pecyn arall Trwydded - Rydw i'n derbyn - termau defnydd - Llwybr i'r ffeil - Llwybr llwyr i'r ffeil (ie: /bin/umbraco.bin) Wedi'i osod - Gosod yn lleol - Gosod pecyn - Gorffen Pecynnau wedi'u gosod Nid oes gennych unrhyw becynnau wedi'u gosod 'Pecynnau' yng nghornel dop, dde eich sgrîn]]> Nid oes gan y pecyn hwn unrhyw olwg cyfluniad Nid oes unrhyw becynnau wedi'u creu eto - Camau Gweithredu Pecyn - URL y Awdur Cynnwys y Pecyn - Ffeiliau y Pecyn - URL Eicon - Gosod pecyn Trwydded - URL Trwydded - Priodweddau Pecyn Chwilio am becynnau Canlyniadau ar gyfer Ni allwn ddarganfod unrhyw beth ar gyfer @@ -1426,7 +1331,6 @@ Er mwyn gweinyddu eich gwefan, agorwch swyddfa gefn Umbraco a dechreuwch ychwang Mae'r pecyn yma yn gydnaws â'r fersiynau canlynol o Umbraco, fel y mae aelodau'r gymued yn adrodd yn ôl. Ni all warantu cydweddoldeb cyflawn ar gyfer fersiynau sydd wedi'u hadrodd o dan 100% Ffynonellau allanol Awdur - Arddangosiad Dogfennaeth Meta ddata pecynnau Enw pecyn @@ -1435,7 +1339,6 @@ Er mwyn gweinyddu eich gwefan, agorwch swyddfa gefn Umbraco a dechreuwch ychwang
Gallwch ddileu hyn yn ddiogel o'r system wrth glicio "dadosod pecyn" isod.]]> - Dim uwchraddiadau ar gael Dewisiadau pecyn Readme pecyn Ystorfa pecyn @@ -1448,24 +1351,7 @@ Er mwyn gweinyddu eich gwefan, agorwch swyddfa gefn Umbraco a dechreuwch ychwang Rhybudd: bydd unrhyw ddogfennau, cyfrwng ayyb sy'n dibynnu ar yr eitemau yr ydych am ddileu yn torri, a gall arwain at system ansefydlog, felly dadosodwch gyda gofal. Os oes unrhyw amheuaeth, cysylltwch ag awdur y pecyn.]]> - Lawrlwytho diweddariad o'r ystorfa - Uwchraddio pecyn - Cyfarwyddiadau uwchraddio - Mae yna uwchraddiad ar gael ar gyfer y pecyn yma. Gallwch lawrlwytho'n uniongyrchol o'r ystorfa pecynnau Umbraco. Fersiwn pecyn - Uwchraddio o ferswin - Hanes ferswin pecyn - Gweld gwefan pecyn - Pecyn wedi'i osod eisoes - Ni all y pecyn yma gael ei osod, mae angen fersiwn Umrbaco o leiaf - Dadosod... - Lawrlwytho... - Mewnforio... - Gosod... - Ailgychwyn, arhoswch... - Wedi cwblhau, bydd eich porwr yn adnewyddu, arhoswch... - Cliciwch 'Cwblhau' i orffen y gosodiad ac adnewyddu'r dudalen. - Lanlwytho pecyn... Wedi gwirio i weithio ar Umbraco Cloud Cyfarwyddiadau gosod Wedi'i hyrwyddo @@ -1483,9 +1369,6 @@ Er mwyn gweinyddu eich gwefan, agorwch swyddfa gefn Umbraco a dechreuwch ychwang Amddiffyniad yn seiliedig grŵp Os ydych chi am ganiatáu mynediad i bob aelod o grwpiau aelodau penodol Mae angen i chi greu grŵp aelod cyn y gallwch ddefnyddio dilysiad grŵp - Amddiffyn ar sail rôl - Os hoffwch reoli cyrchiad i'r dudalen wrth ddefnyddio dilysu ar sail rôl, gan ddefnyddio grwpiau aelodaeth Umbraco. - Mae angen i chi greu grŵp aeloadeth cyn i chi allu defnyddio dilysu ar sail rôl Tudalen Wall Wedi'i ddefnyddio pan mae defnyddwyr wedi mewngofnodi, ond nid oes ganddynt hawliau Dewiswch sut i gyfyngu hawliau at y dudalen yma @@ -1495,10 +1378,6 @@ Er mwyn gweinyddu eich gwefan, agorwch swyddfa gefn Umbraco a dechreuwch ychwang Dewiswch y dudalen sy'n cynnwys y ffurflen mewngofnodi Dileu Amddiffyniad Dewiswch y tudalennau sy'n cynnwys ffurflenni mewngofnodi a negeseuon gwall - Dewiswch y rolau sydd a hawliau i'r dudlaen yma - Gosodwch yr enw defnyddiwr a chyfrinair ar gyfer y dudalen yma - Amddiffyniad defnyddiwr unigol - Os hoffwch osod amddifyniad syml wrth ddefnyddio enw defnyddiwr a chyfrinair sengl %0%?]]> %0%]]> %0%]]> @@ -1527,7 +1406,6 @@ Er mwyn gweinyddu eich gwefan, agorwch swyddfa gefn Umbraco a dechreuwch ychwang Methodd y dilysiad ar gyfer yr iaith ofynnol '%0%'. Roedd yr iaith wedi cael ei arbed ond nid ei chyhoeddi. - Cynnwys is-dudalennau heb eu cyhoeddi Cyhoeddi ar waith - arhoswch... %0% allan o %1% o dudalennau wedi eu cyhoeddi... %0% wedi ei gyhoeddi @@ -1554,12 +1432,10 @@ Er mwyn gweinyddu eich gwefan, agorwch swyddfa gefn Umbraco a dechreuwch ychwang Yn sbwriel Agor mewn Llyfrgell Cyfryngau Newid Eitem Gyfrwng - Ailosod tocio cyfrwng Golygu %0% ar %1% Gwaredu cread? Ydych chi'n siŵr eich bod chi am ganslo'r cread? Rydych chi wedi gwneud newidiadau i'r cynnwys hwn. Ydych chi'n siŵr eich bod chi am eu gwaredu? - Dileu? Dileu pob cyfryngau? Clipfwrdd Ni chaniateir @@ -1580,8 +1456,6 @@ Er mwyn gweinyddu eich gwefan, agorwch swyddfa gefn Umbraco a dechreuwch ychwang Ailosod tocio - Achub tocio - Ychwanegu tocio newydd Wedi gwneud Dadwneud golygion Diffiniad defnyddiwr @@ -1595,37 +1469,29 @@ Er mwyn gweinyddu eich gwefan, agorwch swyddfa gefn Umbraco a dechreuwch ychwang Rolio yn ôl at Dewis fersiwn Gwedd - Yn dangos fersiwn %0% i %1% o %2% fersiynau. Fersiynau Fersiwn drafft cyfredol Fersiwn cyhoeddedig cyfredol + Wedi creu + Fersiwn gyfredol + Nid oes unrhyw wahaniaethau rhwng y fersiwn (drafft) gyfredol a'r fersiwn a ddewiswyd Golygu ffeil sgript - Gwas Cynnwys - Tywyswr - Datblygwr Ffurflenni - Cymorth - Dewin Ffurfweddu Umbraco Cyfrwng Aelodau - Cylchlythyrau Pecynnau Gosodiadau - Ystadegau Cyfieithiad Defnyddwyr - Dadansoddeg + Marchnad - ewch i - Pynciau cymorth ar gyfer - Penodau fideo ar gyfer Teithiau Y fideos tiwtorial Umbraco gorau Ymweld â our.umbraco.com @@ -1635,21 +1501,18 @@ Er mwyn gweinyddu eich gwefan, agorwch swyddfa gefn Umbraco a dechreuwch ychwang Templed diofyn - Allwedd Geiriadur Er mwyn mewnforio math o ddogfen, darganfyddwch y ffeil ".udt" ar ecih cyfrifiadur wrth glicio ar y botwn "Pori" a cliciwch "Mewnforio" (byddwch yn cael eich gofyn i gadarnhau ar y sgrîn nesaf) Teitl Tab Newydd Math o nod Math Taflen arddull Sgript - Priodwedd taflen arddull Tab Teitl Tab Tabiau Math o Gynnwys Meistr wedi'i alluogi Mae'r Math o Gynnwys yma yn defnyddio Dim priodweddau wedi'u diffinio ar y tab yma. Cliciwch ar y ddolen "ychwanegu priodwedd newydd" ar y topi greu priodwedd newydd. - Math o Ddogfen Feistr Creu templedi cydweddol Ychwanegu eicon @@ -1670,7 +1533,6 @@ Er mwyn gweinyddu eich gwefan, agorwch swyddfa gefn Umbraco a dechreuwch ychwang Diffyg hawliau defnyddiwr, ni ellir cwblhau'r gweithred Wedi canslo Gweithred wedi'i ganslo gan ymestyniad 3-ydd parti - Cyhoeddi wedi'i ganslo gan ymestyniad 3-ydd parti Math o briodwedd yn bodoli eisoes Math o briodwedd wedi'i greu Math o ddata: %1%]]> @@ -1684,23 +1546,19 @@ Er mwyn gweinyddu eich gwefan, agorwch swyddfa gefn Umbraco a dechreuwch ychwang Taflen arddull wedi'i achub heb unrhyw wallau Math o ddata wedi'i achub Eitem geiriadur wedi'i achub - Cyhoeddi wedi methu gan nad yw'r dudalen rhiant wedi'i gyhoeddi Cynnwys wedi'i gyhoeddi ac yn weladwy ar y wefan %0% dogfennau wedi'i gyhoeddi ac yn gweledig ar y wefan %0% gyhoeddi ac yn gweledig ar y wefan %0% dogfennau wedi'i gyhoeddi am yr ieithoedd %1% ac yn gweledig ar y wefan - ac yn weladwy ar y wefan tan %0% at %1% Cynnwys wedi'i achub Cofiwch gyhoeddi er mwyn i'r newidiadau fod yn weladwy Mae amserlen ar gyfer cyhoeddi wedi'i diweddaru %0% wedi arbed - Bydd newidiadau yn cael ei gymerdwyo ar %0% at %1% Wedi'i anfon am gymeradwyo Newidiadau wedi'u hanfon am gymeradwyo %0% newidiadau wedi'u hanfon am gymeradwyo Cyfrwng wedi'i achub - Grŵp aeloadeth wedi'i achub Cyfrwng wedi'i achub heb unrhyw wallau Aelod wedi'i achub Priodwedd taflen arddull wedi'i achub @@ -1720,19 +1578,10 @@ Er mwyn gweinyddu eich gwefan, agorwch swyddfa gefn Umbraco a dechreuwch ychwang Math o Gyfrwng wedi'i achub Math o Aelod wedi'i achub Grŵp Aelod wedi'i achub - Sgript Python heb ei achub - Ni ellir achub y sgript Python oherwydd gwall - Sgript Python wedi'i achub - Dim gwallau yn y sgript Python Templed heb ei achub Sicrhewch nad oes gennych 2 dempled gyda'r un enw arall Templed wedi'i achub Templed wedi'i achub heb unrhyw wallau! - XSLT heb ei achub - XSLT yn cynnwys gwall - Ni ellir achub y ffeil XSLT, gwiriwch hawliau ffeil - XSLT wedi'i achub - Dim gwallau yn yr XSLT Cynnwys wedi'i ddadgyhoeddi amrywiad cynnwys %0% wedi'i dadgyhoeddi Roedd yr iaith orfodol '%0%' wedi'i dadgyhoeddi. Mae'r holl ieithoedd ar gyfer yr eitem gynnwys hon bellach wedi'i dadgyhoeddi. @@ -1741,28 +1590,17 @@ Er mwyn gweinyddu eich gwefan, agorwch swyddfa gefn Umbraco a dechreuwch ychwang Rhan-wedd heb ei achub Bu gwall yn ystod achub y ffeil. Hawliau wedi'u hachub ar gyfer - Gwedd sgript wedi'i achub - Gwedd sgript wedi'i achub heb unrhyw wallau! - Gwedd sgript heb ei achub - Bu gwall yn ystod achub y ffeil. - Bu gwall yn ystod achub y ffeil. Wedi dileu %0% o rwpiau defnwyddwr %0% wedi'i ddileu %0% o ddefnyddwyr wedi'u galluogi - Bu gwall yn ystod galluogi'r defnyddwyr Wedi analluogi %0% o ddefnyddwyr - Bu gwall yn ystod analluogi'r defnyddwyr %0% yn awr wedi galluogi - Bu gwall yn ystod galluogi'r defnyddiwr %0% yn awr wedi analluogi - Bu gwall yn ystod analluogi'r defnyddiwr Grwpiau defnyddiwr wedi'u gosod Wedi dileu %0% o rwpiau defnyddwyr %0% wedi dileu Wedi datgloi %0% o ddefnyddwyr - Bu gwall yn ystod datgloi'r defnyddwyr %0% yn awr wedi datgloi - Bu gwall yn ystod datgloi'r defnyddiwr Allforwyd yr aelod at ffeil Bu gwall yn ystod allforio'r aelod Defnyddiwr %0% wedi'i ddileu @@ -1793,7 +1631,6 @@ Er mwyn gweinyddu eich gwefan, agorwch swyddfa gefn Umbraco a dechreuwch ychwang Methu â chopïo gwybodaeth eich system i'r clipfwrdd - Yn defnyddio cystrawen CSS e.e: h1, .coch, .glas Ychwanegu ardull Golygu ardull Ardull golygydd testun cyfoethog @@ -1835,7 +1672,6 @@ Er mwyn gweinyddu eich gwefan, agorwch swyddfa gefn Umbraco a dechreuwch ychwang mae'n wych ar gyfer ail-ddefnyddio côd neu ar gyfer gwahanu templedi cymhleth i mewn i ffeiliau gwahanol. Templed Meistr - Dim templed meistr Dim meistr Datganu templed blentyn @@ -1868,9 +1704,7 @@ Er mwyn gweinyddu eich gwefan, agorwch swyddfa gefn Umbraco a dechreuwch ychwang ]]> Adeiladwr ymholiad - Adeiladu ymholiad o eitemau wedi dychwelyd, mewn - Copi i'r clipfwrdd Rydw i eisiau holl gynnwys cynnwys o'r fath "%0%" @@ -1900,15 +1734,11 @@ Er mwyn gweinyddu eich gwefan, agorwch swyddfa gefn Umbraco a dechreuwch ychwang esgynnol disgynnol Templed - Nid oes modd golygu cynnwys wrth ddefnyddio modd amser rhedeg <code>Production</code>. + Nid oes modd golygu cynnwys wrth ddefnyddio modd amser rhedeg Production. - Golygydd Testun Gyfoethog Llun Macro - Mewnosod - Pennawd - Dyfyniad Dewis math o gynnwys Dewis cynllun Ychwanegu rhes @@ -1920,7 +1750,6 @@ Er mwyn gweinyddu eich gwefan, agorwch swyddfa gefn Umbraco a dechreuwch ychwang Cliciwch i fewnblannu Cliciwch i fewnosod llun Cliciwch i fewnosod macro - Capsiwn llun... Ysgrifennwch yma... Cynlluniau Grid Cynlluniau yw'r holl ardal weithio gyfan ar gyfer y golygydd grid, fel arfer rydych ddim ond angen un neu ddau gynllun gwahanol @@ -1939,7 +1768,6 @@ Er mwyn gweinyddu eich gwefan, agorwch swyddfa gefn Umbraco a dechreuwch ychwang Ffurfweddu pa osodiadau gall olygyddion eu newid Ardduliau Ffurfweddu pa arddulliau gall olygyddion eu newid - Bydd gosodiadau dim ond yn newid os mae'r ffurfwedd json yn ddilys Caniatáu pob golygydd Caniatáu holl ffurfweddi rhes Uchafswm o eitemau @@ -1951,15 +1779,12 @@ Er mwyn gweinyddu eich gwefan, agorwch swyddfa gefn Umbraco a dechreuwch ychwang Rhybudd Rydych chi'n dileu'r ffurfwedd rhes Bydd dileu enw ffurfwedd rhes yn arwain at golli data ar gyfer unrhyw gynnwys cynfodol sy'n seiliedig ar ffurfwedd hwn. - <p>Bydd addasu enw cyfluniad rhes yn arwain at golli data ar gyfer unrhyw gynnwys presennol sy'n seiliedig ar y ffurfweddiad hwn.</p> <p><strong>Ni fydd addasu'r label yn unig yn arwain at golli data.</strong></p> +

Bydd addasu enw cyfluniad rhes yn arwain at golli data ar gyfer unrhyw gynnwys presennol sy'n seiliedig ar y ffurfweddiad hwn.

Ni fydd addasu'r label yn unig yn arwain at golli data.

Rydych chi'n dileu'r gosodiad Bydd addasu cynllun yn arwain at golli data ar gyfer unrhyw gynnwys presennol sy'n seiliedig ar y ffurfweddiad hwn. Cyfansoddiadau - Nid ydych wedi ychwanegu unrhyw dabiau - Ychwanegu tab newydd - Ychwanegu tab arall Grŵp Ni allwch symud y grŵp %0% i'r tab hwn oherwydd bydd y grŵp yn cael yr un alias â thab: "%1%". Ail-enwi'r grŵp i barhau. Nid ydych wedi ychwanegu unrhyw grwpiau @@ -1973,7 +1798,6 @@ Er mwyn gweinyddu eich gwefan, agorwch swyddfa gefn Umbraco a dechreuwch ychwang Dewiswch pa olygoddion templedi sy'n cael defnyddio cynnwys o'r fath yma Caniatáu fel gwraidd Caniatáu golygyddion i greu cynnwys o'r fath yma yng ngwraidd y goeden gynnwys - Iawn - caniatáu cynnwys o'r fath yma yn y gwraidd Mathau o nod blentyn caniateir Caniatáu cynnwys o'r mathau benodol i gael eu creu o dan cynnwys o'r fath yma Dewis nod blentyn @@ -2002,7 +1826,6 @@ Er mwyn gweinyddu eich gwefan, agorwch swyddfa gefn Umbraco a dechreuwch ychwang a phob dogfen sy'n defnyddio'r fath yma a phob eitem gyfrwng sy'n defnyddio'r fath yma a phob aelod sy'n defnyddio'r fath yma - sy'n defnyddio'r golygydd yma fydd yn cael eu diweddaru gyda'r gosodiadau newydd Aeloed yn gallu golygu Caniatáu i'r gwerth briodwedd yma gael ei olygu gan yr aelod ar eu tudalen broffil Yn ddata sensitif @@ -2030,9 +1853,9 @@ Er mwyn gweinyddu eich gwefan, agorwch swyddfa gefn Umbraco a dechreuwch ychwang Rydych wedi gwneud newidiadau i'r eiddo hwn. Ydych chi'n siŵr eich bod chi am eu taflu? Ymddangosiad Label uwchben (lled-llawn) - Ydych chi'n siŵr eich bod chi am ddileu'r tab <strong>%0%</strong>? - Ydych chi'n siŵr eich bod chi am ddileu'r tab grŵp <strong>%0%</strong>? - Ydych chi'n siŵr eich bod chi am ddileu'r eiddo <strong>%0%</strong>? + Ydych chi'n siŵr eich bod chi am ddileu'r tab %0%? + Ydych chi'n siŵr eich bod chi am ddileu'r tab grŵp %0%? + Ydych chi'n siŵr eich bod chi am ddileu'r eiddo %0%? Bydd hyn hefyd yn dileu'r holl eitemau o dan y tab hwn. Bydd hyn hefyd yn dileu'r holl eitemau o dan y grŵp hwn. Ychwanegu tab @@ -2047,7 +1870,7 @@ Er mwyn gweinyddu eich gwefan, agorwch swyddfa gefn Umbraco a dechreuwch ychwang Cadwch y fersiwn diweddaraf y dydd am ddyddiau Atal glanhau Galluogi glanhau - <strong>NODYN!</strong> Mae glanhau fersiynau cynnwys hanesyddol wedi'u hanalluogi'n fyd-eang. Ni fydd y gosodiadau hyn yn dod i rym cyn iddo gael ei alluogi. + NODYN! Mae glanhau fersiynau cynnwys hanesyddol wedi'u hanalluogi'n fyd-eang. Ni fydd y gosodiadau hyn yn dod i rym cyn iddo gael ei alluogi. Mae newid math o ddata gyda gwerthoedd storio wedi'i analluogi. I ganiatáu hyn gallwch newid y gosodiad Umbraco:CMS:DataTypes:CanBeChanged yn appssettings.json. @@ -2063,9 +1886,9 @@ Er mwyn gweinyddu eich gwefan, agorwch swyddfa gefn Umbraco a dechreuwch ychwang Iaith cwympo yn ôl dim Cod ISO - <strong>%0%</strong> yn cael ei rannu ar draws ieithoedd a segmentau. - <strong>%0%</strong> yn cael ei rannu ar draws pob iaith. - <strong>%0%</strong> yn cael ei rannu ar draws pob segment. + %0% yn cael ei rannu ar draws ieithoedd a segmentau. + %0% yn cael ei rannu ar draws pob iaith. + %0% yn cael ei rannu ar draws pob segment. Wedi'i rannu: Ieithoedd Wedi'i rannu: Segments @@ -2085,8 +1908,6 @@ Er mwyn gweinyddu eich gwefan, agorwch swyddfa gefn Umbraco a dechreuwch ychwang Methwyd generadu modelau, gweler yr eithriadau yn y log Umbraco - Ychwanegu maes rolio yn ôl - Maes rolio yn ôl Ychwanegu gwerth diofyn Gwerth diofyn Maes rolio yn ôl @@ -2095,26 +1916,21 @@ Er mwyn gweinyddu eich gwefan, agorwch swyddfa gefn Umbraco a dechreuwch ychwang Amgodiad Dewis maes Trawsnewid torriadau llinellau - Iawn, trawsnewid torriadau llinellau Cyfnewid torriadau llinellau gyda tag html 'br' Meysydd bersonol Dyddiad yn unig - Fformat ac amgodiad Fformatio ar ffurf dyddiad - Fformatio'r gwerth ar ffurf dyddiad, neu dyddiad gyda amser, yn ôl y diwylliant gweithredol Amgodi HTML Bydd yn cyfnewid nodau arbennig gyda'u nodau HTML cyfatebol. Bydd yn cael ei fewnosod ar ôl y gwerth maes Bydd yn cael ei fewnosod cyn y gwerth maes Llythrennau bach - Newid allbwn Dim Sampl allbwn Mewnosod ar ôl maes Mewnosod cyn maes Ailadroddus Iawn, gwnewch yn ailadroddus - Gwahanwr Meysydd Safonol Llythrennau bras Amgodi URL @@ -2124,17 +1940,7 @@ Er mwyn gweinyddu eich gwefan, agorwch swyddfa gefn Umbraco a dechreuwch ychwang Dyddiad ac amser - Tasgau wedi'u neilltuo i chi - - wedi'u neilltuo i chi. Er mwyn gweld gwedd fanwl gan gynnwys sylwadau, cliciwch ar "Manylion" neu enw'r dudalen. - Gallwch hefyd lawrlwytho'r dudalen ar ffurf XML yn uniongyrchol gan glicio'r ddolen "Lawrlwytho Xml".
- Er mwyn cau tasg cyfieithu, ewch at y wedd fanylion a cliciwch ar y botwm "Cau". - ]]> -
- cau tasg Manylion cyfieithiad - Lawrlwytho pob tasg cyfieithu ar ffurf XML - Lawrlwytho XML Lawrlwytho XML DTD Meysydd Cynnwys is-dudalennau @@ -2155,20 +1961,9 @@ Er mwyn gweinyddu eich gwefan, agorwch swyddfa gefn Umbraco a dechreuwch ychwang Hwyl fawr oddi wrth y robot Umbraco ]]> - [%0%] Tasg cyfieithu ar gyfer %1% Dim defnyddwyr cyfieithu wedi'u darganfod. Creuwch ddefnyddiwr cyfieithu cyn i chi gychwyn anfon cynnwys am gyfieithiadau - Tasgau wedi'u creu gennych chi - - wedi'u creu gennych chi. Er mwyn gweld gwedd fanwl sy'n cynnwys sylwadau, - cliciwch ar "Manylion" neu enw'r dudalen. Gallwch hefyd lawrlwytho'r dudalen ar ffurf XML yn uniongyrchol gan glicio ar y ddolen "Lawrlwytho Xml". - Er mwyn cau tasgau cyfieithu, ewch at y wedd fanylion a cliciwch y botwm "Cau". - ]]> - Mae'r dudalen '%0%' wedi cael ei anfon am gyfieithiad - Dewiswch yr iaith y dylai'r cynnwys gael ei gyfieithu i Anfon y dudalen '%0%' am gyfieithiad - Wedi'i neilltuo gan - Tasg wedi'i hagor Cyfanswm o eiriau Cyfieithu i Cyfieithiad wedi'i gwblhau. @@ -2204,7 +1999,6 @@ Er mwyn gweinyddu eich gwefan, agorwch swyddfa gefn Umbraco a dechreuwch ychwang Pecynnau Rhan-weddi Ffeiliau Rhan-wedd Macro - Ffeiliau Python Gosod o ystorfa Gosod Runway Modylau Runway @@ -2212,8 +2006,6 @@ Er mwyn gweinyddu eich gwefan, agorwch swyddfa gefn Umbraco a dechreuwch ychwang Sgriptiau Taflenni arddull Templedi - Ffeiliau XSLT - Dadansoddeg Gwyliwr Log Defnyddwyr Gosodiadau @@ -2303,15 +2095,12 @@ Er mwyn gweinyddu eich gwefan, agorwch swyddfa gefn Umbraco a dechreuwch ychwang Rheoli defnyddwyr Enw Hawliau defnyddiwr - Hawliau grwpiau defnyddiwr Grŵp defnyddiwr - Grwpiau defnyddiwr wedi'i wahodd Mae gwahoddiad wedi cael ei anfon at y defnyddiwr newydd gyda manylion ar sut i fewngofnodi i Umbraco. Helo a chroeso i Umbraco! Mewn 1 munud yn unig, byddech chi'n barod i fynd, rydym dim ond angen gosod cyfrinair. Croeso i Umbraco! Yn anffodus, mae eich gwahoddiad wedi terfynu. Cysylltwch â'ch gweinyddwr a gofynnwch iddynt ail-anfon. Ysgrifennydd - Cyfieithydd Newid Eich proffil Eich hanes diweddar @@ -2414,7 +2203,6 @@ Er mwyn gweinyddu eich gwefan, agorwch swyddfa gefn Umbraco a dechreuwch ychwang ]]> - Gwahoddiad Yn ail-anfon y gwahoddiad... Dileu Defnyddiwr Ydych chi'n sicr eich bod eisiau dileu'r cyfrif defnyddiwr yma? @@ -2450,10 +2238,11 @@ Er mwyn gweinyddu eich gwefan, agorwch swyddfa gefn Umbraco a dechreuwch ychwang Mae'r darparwr dau-ffactor hwn bellach wedi'i analluogi Aeth rhywbeth o'i le wrth geisio analluogi'r darparwr dau ffactor hwn Ydych chi am analluogi'r darparwr dau ffactor hwn ar gyfer y defnyddiwr hwn? + Angenrheidiol - rhowch gyfeiriad e-bost ar gyfer y defnyddiwr hwn + Angenrheidiol - rhowch enw ar gyfer y defnyddiwr hwn Dilysiad - Dim dilysiad Dilysu fel cyfeiriad ebost Dilysu fel rhif Dilysu fel URL @@ -2497,89 +2286,47 @@ Er mwyn gweinyddu eich gwefan, agorwch swyddfa gefn Umbraco a dechreuwch ychwang 3: Configuration file path --> Gwerth wedi'i osod at y gwerth argymhellwyd: '%0%'. - Gwerth wedi'i osod at '%1%' ar gyfer XPath '%2%' yn y ffeil ffurfweddu '%3%'. Yn disgwyl y gwerth '%1%' ar gyfer '%2%' yn y ffeil ffurfweddu '%3%', ond darganfyddwyd '%0%'. Darganfyddwyd gwerth annisgwyl '%0%' ar gyfer '%2%' yn y ffeil ffurfweddu '%3%'. - Gwallau bersonol wedi gosod at '%0%'. - Gwallau bersonol wedi gosod at '%0%' yn bresennol. Argymhellwyd i osod hyn i '%1%' cyn mynd yn fyw. - Gwallau bersonol wedi gosod at '%0%' yn llwyddiannus. Gwallau Macro wedi gosod at '%0%'. Gwallau Macro wedi gosod at '%0%' a fydd yn atal rhai neu holl dudalennau yn eich safle rhag llwytho'n gyfan gwbl os oes unrhyw wallau o fewn macros. Bydd cywiro hyn yn gosod y gwerth at '%1%'. - Gwallau Macro wedi gosod at '%0%' yn llwyddiannus. - Ceisio sgipio Gwallau IIS Bersonol wedi'i osod at '%0%' ac rydych yn defnyddio fersiwn IIS '%1%'. - Ceisio sgipio Gwallau IIS Bersonol wedi'i osod at '%0%'. Argymhellwyd gosod hyn at '%1%' ar gyfer eich fersiwn IIS (%2%). - Ceisio sgipio Gwallau IIS Bersonol wedi'i osod at '%0%' yn llwyddiannus. - Ffeil ddim yn bodoli: '%0%'. - '%0%' yn y ffeil ffurfweddu '%1%'.]]> - Bu gwall, gwiriwch y log ar gyfer y gwall cyflawn: %0%. - Aelodau - Cyfanswm XML: %0%, Cyfanswm: %1%, Cyfanswm annilys: %2% - Cyfrwng - Cyfanswm XML: %0%, Cyfanswm: %1%, Cyfanswm annilys: %2% - Cynnwys - Cyfanswm XML: %0%, Cyfanswm wedi cyhoeddi: %1%, Cyfanswm annilys: %2% - Cronfa ddata - Mae'r sgema gronfa ddata yn gywir ar gyfer y fersiwn yma o Umbraco - %0% o broblemau wedi'u canfod gyda'ch sgema gronfa ddata (Gwiriwch y log am fanylion) - Darganfyddwyd gwallau wrth ddilysu'r sgema gronfa ddata yn erbyn y fersiwn bresennol o Umbraco. Mae tystysgrif eich gwefan yn ddilys. Gwall dilysu tystysgrif: '%0%' Mae tystysgrif SSL eich gwefan wedi terfynu. Mae tystysgrif SSL eich gwefan am derfynu mewn %0% diwrnod. Gwall yn pingio'r URL %0% - '%1%' Rydych yn bresennol %0% yn gweld y wefan yn defnyddio'r cynllun HTTPS. - Ni ellir diweddaru'r gosodiad 'umbracoUseSSL' yn eich ffeil web.config. Gwall: %0% - Galluogi HTTPS - Yn gosod umbracoSSL i true yn yr appSettings yn y ffeil web.config. - Mae'r appSetting 'umbracoUseSSL' yn awr wedi'i osod at 'true' yn eich ffeil web.config, bydd eich cwcis wedi eu marcio yn ddiogel. - Trwsio - Ni ellir trwsio gwiriad gyda math chymhariaeth gwerth o 'ShouldNotEqual'. - Ni ellir trwsio gwiriad gyda math chymhariaeth gwerth o 'ShouldEqual' gyda gwerth a ddarparwyd. - Gwerth i drwrsio gwiriad heb ei ddarparu. Modd casgliad dadfygio wedi'i analluogi. Modd casgliad dadfygio wedi'i alluogi. Argymhellwyd analluogi'r gosodiad yma cyn mynd yn fyw. - Modd casgliad dadfygio wedi'i analluogi yn llwyddiannus. - Modd olrhain wedi'i analluogi. - Modd olrhain wedi'i alluogi. Argymhellwyd analluogi'r gosodiad yma cyn mynd yn fyw. - Modd olrhain wedi'i analluogi yn llwyddiannus. - Mae gan pob ffolder yr hawliau cywir wedi'u gosod. - %0%.]]> - %0%. Os nad ydyn nhw'n cael eu ysgrifennu atynt, does dim angen unrhyw weithred.]]> - Mae gan pob ffeil yr hawliau cywir wedi'u gosod. - - %0%.]]> - %0%. Os nad ydyn nhw'n cael eu ysgrifennu atynt, does dim angen unrhyw weithred.]]> X-Frame-Options sy'n cael ei ddefnyddio i reoli os mae safle'n gallu cael ei osod o fewn IFRAME gan safle arall wedi'i ganfod.]]> X-Frame-Options sy'n cael ei ddefnyddio i reoli os mae safle'n gallu cael ei osod o fewn IFRAME gan safle arall wedi'i ganfod.]]> - Gosod Peniad o fewn Ffurfwedd - Ychwanegu gwerth at yr adran httpProtocol/customHeaders o'r ffeil web.config er mwyn atal y safle rhag cael ei ddangos o fewn IFRAME gan safleoedd eraill. - Gosodiad ar gyfer creu peniad sy'n atal y wefan rhag cael ei ddangos o fewn IFRAME ar safle arall wedi'i ychwanegu at eich ffeil web.config. - Ni ellir diweddaru'r ffeil web.config. Gwall: %0% X-Content-Type-Options sy'n cael ei ddefnyddio i amddiffyn yn erbyn gwendidau sniffio MIME wedi'i ganfod.]]> X-Content-Type-Options sy'n cael ei ddefnyddio i amddiffyn yn erbyn gwendidau sniffio MIME wedi'i ganfod.]]> - Ychwanegu gwerth at yr adran httpProtocol/customHeaders o'r ffeil web.config er mwyn amddiffyn yn erbyn gwendidau sniffio MIME. - Gosodiad ar gyfer creu peniad sy'n amddiffyn yn erbyn gwendidau sniffio MIME wedi'i ychwanegu at eich ffeil web.config. Strict-Transport-Security, hefyd wedi'i adnabod fel HSTS-header, wedi'i ganfod.]]> Strict-Transport-Security wedi'i ganfod.]]> - Ychwanegu'r peniad 'Strict-Transport-Security' gyda'r gwerth 'max-age=10886400; preload' i'r adran httpProtocol/customHeaders o'r ffeil web.config. Defnyddiwch y trwsiad hyn dim ond os bydd gennych chi eich parthau yn rhedeg gyda https am yr 18 wythnos nesaf (o leiaf). - Mae'r peniad HSTS wedi'i ychwanegu at y ffeil web.config. + + Strict-Transport-Security, a elwir hefyd yn HSTS-header. Ni ddylai'r pennyn hwn fod yn bresennol ar localhost.]]> + + + Strict-Transport-Security. Ni ddylai'r pennyn hwn fod yn bresennol ar localhost.]]> + X-XSS-Protection wedi'i ganfod.]]> X-XSS-Protection wedi'i ganfod.]]> - Ychwanegu'r peniad 'X-XSS-Protection' gyda'r gwerth '1; mode=block' at yr adran httpProtocol/customHeaders yn y ffeil web.config. - Mae'r peniad X-XSS-Protection wedi'i ychwanegu at y ffeil web.config. @@ -2590,7 +2337,6 @@ Er mwyn gweinyddu eich gwefan, agorwch swyddfa gefn Umbraco a dechreuwch ychwang %0%.]]>

Canlyniadau'r gwiriad Statws Iechyd Umbraco ar amserlen rhedwyd ar %0% am %1% fel y ganlyn:

%2%]]>
Statws Iechyd Umbraco: %0% - Gwiriwch Pob Grŵp Gwiriwch y grŵp y ddogfennaeth i gael mwy o wybodaeth am wiriadau iechyd arferu.

]]>
- Eich wefan gallu defnyddio y protocol gwarchodaeth TLS 1.2 wrth wneud cysylltiadau allanol i endpoints HTTPS - Nid yw'ch gwefan wedi'i ffurfweddu i ganiatáu protocol diogelwch TLS 1.2 wrth wneud cysylltiadau allanol: efallai na fydd modd cyrchu rhai endpoints HTTPS gan ddefnyddio protocol llai diogel. Mae gosodiad ap 'Umbraco:CMS:Global:UseHttps' wedi'i osod i 'false' yn eich ffeil appSettings.json. Unwaith y byddwch yn cyrchu'r wefan hon gan ddefnyddio'r cynllun HTTPS, dylid gosod hwnnw i 'true'. Mae'r gosodiad ap 'Umbraco:CMS:Global:UseHttps' wedi'i osod i '%0%' yn eich ffeil appSettings.json, mae eich cwcis %1% wedi'u marcio'n ddiogel. - Mae gosodiad yr ap 'Umbraco:CMS:WebRouting:UmbracoApplicationUrl' wedi'i osod i <strong>%0%</strong>. + Mae gosodiad yr ap 'Umbraco:CMS:WebRouting:UmbracoApplicationUrl' wedi'i osod i %0%. Nid yw gosodiad ap 'Umbraco:CMS:WebRouting:UmbracoApplicationUrl' wedi'i osod. Nid oedd modd dod o hyd i'r ffurfweddiad 'Umbraco:CMS:Global:Smtp'. Nid oedd modd dod o hyd i'r ffurfweddiad 'Umbraco:CMS:Global:Smtp:Host'. @@ -2618,8 +2362,6 @@ Er mwyn gweinyddu eich gwefan, agorwch swyddfa gefn Umbraco a dechreuwch ychwang Mae'r URLs ganlynol yn ailgyfeirio at yr eitem gynnwys yma: Dim ailgyfeiriadau wedi'u gwneud Pan mae tudalen wedi'i gyhoeddi yn cael ei ailenwi neu symud bydd ailgyfeiriad yn cael ei greu yn awtomatig at y dudalen newydd. - Dileu - Ydych chi'n sicr eich bod eisiau dileu'r ailgyfeiriad o '%0%' at '%1%'? URL ailgyfeirio wedi'i ddileu. Gwall yn dileu'r URL. Bydd hyn yn dileu'r ailgyfeiriad @@ -2642,8 +2384,6 @@ Er mwyn gweinyddu eich gwefan, agorwch swyddfa gefn Umbraco a dechreuwch ychwang Ni ellir adfer yr eitem yma yn awtomatig Nid oes unrhyw leoliad lle gellir adfer yr eitem hon yn awtomatig. Gallwch chi symud yr eitem â llaw gan ddefnyddio'r goeden isod. oedd adferwyd o dan - Does dim perthynas 'adfer' ar gael ar gyfer y nod yma. Defnyddiwch y ddewislen Symud i'w symud â llaw. - Mae'r eitem yr ydych eisiau adfer yr item oddi tan ('%0%') yn y bin ailgylchu. Defnyddiwch y ddewislen Symud i'w symud â llaw. Cyfeiriad @@ -2728,20 +2468,16 @@ Er mwyn gweinyddu eich gwefan, agorwch swyddfa gefn Umbraco a dechreuwch ychwang Ychwanegu capsiwn am y llun Chwilio'r coeden cynnwys Uchafswm + Ehangu eitemau plentyn ar gyfer + Agor nod cyd-destun ar gyfer Cyfeiriadau This Data Type has no references. Nid oes gan y Math o Ddata hwn unrhyw gyferiadau. Defnyddir mewn Mathau o Ddogfennau - Ddim cyfeiriadau i Fathau o Ddogfennau. Defnyddir mewm Mathau o Gyfrwng - Ddim cyfeiriadau i Fathau o Gyfrwng. Defnyddir mewn Mathau o Aelod - Ddim cyfeiriadau i Fathau o Aelod. Defnyddir gan - A ddefnyddir yn Ddogfennau - A ddefnyddir yn Aelodau - A ddefnyddir yn Cyfryngau Nid oes gan yr eitem hon unrhyw gyfeiriadau. Cyfeirir ato gan yr eitemau canlynol Mae'r eitemau canlynol yn dibynnu ar hyn @@ -2865,7 +2601,7 @@ Er mwyn gweinyddu eich gwefan, agorwch swyddfa gefn Umbraco a dechreuwch ychwang - Ni ddylech chi fyth adael i safle cynhyrchu redeg yn y modd dadfygio. Mae'r modd dadfygio yn gallu cael ei diffodd trwy ychwanegu'r gosodiad debug="false" ar yr elfen <grynhoi /> yn web.config. + Ni ddylech chi fyth adael i safle cynhyrchu redeg yn y modd dadfygio. Mae'r modd dadfygio yn gallu cael ei diffodd trwy ychwanegu'r gosodiad debug="false" ar yr elfen grynhoi yn web.config.

]]>
@@ -2875,7 +2611,7 @@ Er mwyn gweinyddu eich gwefan, agorwch swyddfa gefn Umbraco a dechreuwch ychwang Mae Umbraco ddim yn rhedeg mewn modd dadfygio ar hyn o bryd, felly nid allwch chi ddefnyddio'r proffiliwer adeiledig. Dyma sut y dylai fod ar gyfer safle cynhyrchu.

- Mae'r modd dadfygio yn gallu cael ei throi arno gan ychwanegu'r gosodiad debug="true" ar yr elfen <grynhoi /> yn web.config. + Mae'r modd dadfygio yn gallu cael ei throi arno gan ychwanegu'r gosodiad debug="true" ar yr elfen grynhoi yn web.config.

]]> @@ -2903,11 +2639,6 @@ Er mwyn gweinyddu eich gwefan, agorwch swyddfa gefn Umbraco a dechreuwch ychwang Gofynnwch gwestiwn yn y Fforwm Cymunedol ]]> - - fideos tiwtorial (mae rhai am ddim, ond bydd angen tanysgrifiad am rhai eraill) - ]]> - hoffer hybu cynhyrchiant a chefnogaeth fasnachol @@ -2919,7 +2650,9 @@ Er mwyn gweinyddu eich gwefan, agorwch swyddfa gefn Umbraco a dechreuwch ychwang ]]> - Gwyliwch ein <a class="btn-link -underline" href="https://umbra.co/ulb" target="_blank" rel="noopener">fideos tiwtorial rhad ac am ddim ar Ganolfan Ddysgu Umbraco</a> + fideos tiwtorial rhad ac am ddim ar Ganolfan Ddysgu Umbraco + ]]> @@ -2953,7 +2686,6 @@ Er mwyn gweinyddu eich gwefan, agorwch swyddfa gefn Umbraco a dechreuwch ychwang Maint y golygydd troshaen Ychwanegu golygfa arferu Ychwanegu gosodiadau - Trosysgrifo templed label %0%.]]> %0%.]]> Bydd cynnwys y bloc hwn yn dal i fod yn bresennol, ni fydd golygu'r cynnwys hwn ar gael mwyach a bydd yn cael ei ddangos fel cynnwys heb gefnogaeth. @@ -2973,15 +2705,15 @@ Er mwyn gweinyddu eich gwefan, agorwch swyddfa gefn Umbraco a dechreuwch ychwang Ychwanegu cynnwys Ychwanegu %0% Priodwedd '%0%' yn defnyddio'r golygydd '%1%' sydd ddim yn cael ei gefnogi mewn blociau. - Ydych chi'n siŵr eich bod am ddileu grŵp <strong>%0%</strong> and all the Block configurations of this? + Ydych chi'n siŵr eich bod am ddileu grŵp %0% and all the Block configurations of this? Bydd cynnwys y Blociau hyn yn dal i fod yn bresennol, ni fydd golygu'r cynnwys hwn ar gael mwyach a bydd yn cael ei ddangos fel cynnwys heb ei gefnogi. Nid oes modd ei olygu oherwydd nad yw ElementType yn bodoli. Cuddiwch y botwm golygu cynnwys a'r golygydd cynnwys rhag troshaen y Golygydd Bloc Gosod ffocws ar y bloc cynhwysydd Adnabyddiaeth Dilysiad - <strong>%0%</strong> rhaid bod yn bresennol o leiaf <strong>%2%</strong> time(s). - <strong>%0%</strong> rhaid i'r uchafswm fod yn bresennol <strong>%3%</strong> time(s). + %0% rhaid bod yn bresennol o leiaf %2% time(s). + %0% rhaid i'r uchafswm fod yn bresennol %3% time(s). Nifer y blociau Dim ond yn caniatáu mathau penodol o flociau Mathau o flociau a ganiateir @@ -3005,22 +2737,15 @@ Er mwyn gweinyddu eich gwefan, agorwch swyddfa gefn Umbraco a dechreuwch ychwang Diffiniwch faint o golofnau fydd ar gael ar gyfer ardaloedd. Os na chaiff ei ddiffinio, bydd nifer y colofnau a ddiffinnir ar gyfer y cynllun cyfan yn cael eu defnyddio. Ardaloedd Er mwyn galluogi blociau i nythu o fewn y bloc hwn, diffiniwch un neu fwy o ardaloedd ar gyfer blociau i nythu ynddynt. Ardaloedd yn dilyn eu cynllun eu hunain gwrach ei ddiffinio gan y Colofnau Grid ar gyfer Ardaloedd. Gellir addasu rhychwant pob colofn Ardal a rhychwant rhes trwy ddefnyddio'r teclyn trin graddfa yn y gornel dde isaf. - <strong>%0%</strong> ni chaniateir yn y fan hon. + %0% ni chaniateir yn y fan hon. Taflen arddull gosodiad diofyn Gwrthodwyd cynnwys a wrthodwyd Roedd y cynnwys a fewnosodwyd yn cynnwys cynnwys na caniateir, nad yw wedi'i greu. Hoffech chi gadw gweddill y cynnwys hwn beth bynnag? Bydd yr alias yn cael ei argraffu gan GetBlockGridHTML(), defnyddiwch yr alias i dargedu'r Elfen sy'n cynrychioli'r ardal hon. Ex. .umb-block-grid__area[data-area-alias="MyAreaAlias"] { ... } - Gorfodi lleoliad ar yr ochr chwith - Gorfodi lleoliad ar yr ochr dde - Dileu lleoliad gorfodol ar yr ochr chwith - Dileu lleoliad gorfodol ar yr ochr dde - Toglo lleoliad gorfodol ar yr ochr chwith - Toglo lleoliad gorfodol ar yr ochr dde Llusgwch i raddfa Creu Label Botwm Trosysgrifo'r label ar fotwm creu yr Ardal hon. Dangos opsiynau newid maint - Dangos golwg catalog Ychwanegu Bloc Ychwanegu grŵp Dewiswch grŵp neu Floc @@ -3035,6 +2760,15 @@ Er mwyn gweinyddu eich gwefan, agorwch swyddfa gefn Umbraco a dechreuwch ychwang Gosod Cyfluniad Sampl Bydd hyn yn ychwanegu Blociau sylfaenol ac yn eich helpu i ddechrau gyda'r Golygydd Grid Bloc. Fe gewch Blociau ar gyfer Pennawd, Testun Cyfoethog, Delwedd, yn ogystal â Chynllun Dwy Golofn. Gosod + Golygu mewnol + Yn galluogi golygu mewnol ar gyfer yr Eiddo cyntaf. Gellir golygu priodweddau ychwanegol yn y droshaen. + Yn ddiofyn, caniateir pob math bloc mewn Ardal, Defnyddiwch yr opsiwn hwn i ganiatáu mathau dethol yn unig. + Modd trefnu + Gadael modd trefnu + Rhaid i'r Enw Arall, yr Ardal hwn, fod yn unigryw i gymharu ag Ardaloedd eraill yn y Bloc hwn. + Ffurfweddu ardal + Dileu ardal + Ychwanegu opsiwn rhychwantu %0% colofn Beth yw Templedi Gynnwys @@ -3082,24 +2816,24 @@ Er mwyn gweinyddu eich gwefan, agorwch swyddfa gefn Umbraco a dechreuwch ychwang Lefel telemetreg wedi'i chadw! Er mwyn gwella Umbraco ac ychwanegu swyddogaethau newydd yn seiliedig ar wybodaeth mor berthnasol â phosibl, - <br>hoffem gasglu gwybodaeth system a defnydd o'ch gosodiad. - <br>Bydd data cyfanredol yn cael ei rannu'n rheolaidd yn ogystal â'r hyn a ddysgir o'r metrigau hyn. - <br>Gobeithio y byddwch yn ein helpu i gasglu rhywfaint o ddata gwerthfawr. - <br> - <br><strong>NI FYDDWN</strong> yn casglu unrhyw ddata personol megis cynnwys, cod, gwybodaeth defnyddiwr, a bydd yr holl ddata yn gwbl ddienw. +
hoffem gasglu gwybodaeth system a defnydd o'ch gosodiad. +
Bydd data cyfanredol yn cael ei rannu'n rheolaidd yn ogystal â'r hyn a ddysgir o'r metrigau hyn. +
Gobeithio y byddwch yn ein helpu i gasglu rhywfaint o ddata gwerthfawr. +
+
NI FYDDWN yn casglu unrhyw ddata personol megis cynnwys, cod, gwybodaeth defnyddiwr, a bydd yr holl ddata yn gwbl ddienw.
Byddwn ond yn anfon ID safle dienw i roi gwybod i ni bod y wefan yn bodoli. Byddwn yn anfon ID safle dienw, fersiwn Umbraco, a phecynnau wedi'u gosod Byddwn yn anfon: - <ul> - <li>ID safle dienw, fersiwn Umbraco, a phecynnau wedi'u gosod.</li> - <li>Nifer o: Nodau gwraidd, Nodau Cynnwys, Macros, Cyfryngau, Mathau o Ddogfen, Templedi, Ieithoedd, Parthau, Grŵp Defnyddwyr, Defnyddwyr, Aelodau, a Golygyddion Eiddo a ddefnyddir.</li> - <li>Gwybodaeth system: Webserver, gweinydd OS, fframwaith gweinydd, iaith gweinyddwr OS, a darparwr cronfa ddata.</li> - <li>Gosodiadau cyfluniad: Modd Modelsbuilder, os oes llwybr Umbraco arferol yn bodoli, amgylchedd ASP, ac os ydych chi yn y modd dadfygio.</li> - </ul> - <em>Efallai y byddwn yn newid yr hyn a anfonwn ar y lefel Fanwl yn y dyfodol. Os felly, fe'i rhestrir uchod. - <br>Drwy ddewis "Manwl" rydych yn cytuno i wybodaeth ddienw yn awr ac yn y dyfodol gael ei chasglu.</em> +
    +
  • ID safle dienw, fersiwn Umbraco, a phecynnau wedi'u gosod.
  • +
  • Nifer o: Nodau gwraidd, Nodau Cynnwys, Macros, Cyfryngau, Mathau o Ddogfen, Templedi, Ieithoedd, Parthau, Grŵp Defnyddwyr, Defnyddwyr, Aelodau, a Golygyddion Eiddo a ddefnyddir.
  • +
  • Gwybodaeth system: Webserver, gweinydd OS, fframwaith gweinydd, iaith gweinyddwr OS, a darparwr cronfa ddata.
  • +
  • Gosodiadau cyfluniad: Modd Modelsbuilder, os oes llwybr Umbraco arferol yn bodoli, amgylchedd ASP, ac os ydych chi yn y modd dadfygio.
  • +
+ Efallai y byddwn yn newid yr hyn a anfonwn ar y lefel Fanwl yn y dyfodol. Os felly, fe'i rhestrir uchod. +
Drwy ddewis "Manwl" rydych yn cytuno i wybodaeth ddienw yn awr ac yn y dyfodol gael ei chasglu.
diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/da.xml b/src/Umbraco.Core/EmbeddedResources/Lang/da.xml index 8aaaf2d09d..008faa39fe 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/da.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/da.xml @@ -157,6 +157,12 @@ Flere publiseringsmuligheder Indsæt + + Brugeren har slettet medie + Brugeren har flyttet medie + Brugeren har kopieret medie + Brugeren har gemt medie + For Brugeren har slettet indholdet diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/de.xml b/src/Umbraco.Core/EmbeddedResources/Lang/de.xml index 3fbf6b3d52..1dce3c2997 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/de.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/de.xml @@ -159,6 +159,12 @@ Mehr Veröffentlichungs Optionen Senden + + Medie gelöscht + Medie verschoben + Medie kopiert + Medie gesichert + Anzeigen als Inhalt gelöscht diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/en.xml b/src/Umbraco.Core/EmbeddedResources/Lang/en.xml index da5208e892..4213dcc16a 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/en.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/en.xml @@ -159,6 +159,12 @@ More publishing options Submit + + Media deleted + Media moved + Media copied + Media saved + Viewing for Content deleted @@ -2520,6 +2526,8 @@ To manage your website, simply open the Umbraco backoffice and start adding cont Add image caption Search content tree Maximum amount + Expand child items for + Open context node for References @@ -2672,7 +2680,7 @@ To manage your website, simply open the Umbraco backoffice and start adding cont Want to master Umbraco? Spend a few minutes learning some best practices by visiting the Umbraco Learning Base Youtube channel. Here you can find a bunch of video material covering many aspects of Umbraco.

+

Want to master Umbraco? Spend a few minutes learning some best practices by visiting the Umbraco Learning Base Youtube channel. Here you can find a bunch of video material covering many aspects of Umbraco.

]]>
To get you started diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml b/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml index 57cf89bc2d..8a1b63ed11 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml @@ -160,6 +160,12 @@ More publishing options Submit + + Media deleted + Media moved + Media copied + Media saved + Viewing for Content deleted @@ -1195,6 +1201,8 @@ To manage your website, simply open the Umbraco backoffice and start adding cont ]]> + Umbraco: Security Code + Your security code is: %0% One last step You have enabled 2-factor authentication and must verify your identity. Please choose a 2-factor provider @@ -2624,6 +2632,8 @@ To manage your website, simply open the Umbraco backoffice and start adding cont Add image caption Search content tree Maximum amount + Expand child items for + Open context node for References diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/fr.xml b/src/Umbraco.Core/EmbeddedResources/Lang/fr.xml index 604d51f23e..a753a054fe 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/fr.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/fr.xml @@ -150,6 +150,12 @@ Confirmer Options de publication supplémentaires + + Media supprimé + Media déplacé + Media copié + Media sauvegardé + Aperçu pour Contenu supprimé diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/hr.xml b/src/Umbraco.Core/EmbeddedResources/Lang/hr.xml index b609cf6dee..51847f3585 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/hr.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/hr.xml @@ -158,6 +158,12 @@ Više opcija za objavljivanje Pošalji + + Mediji je obrisan + Mediji premješten + Mediji kopiran + Mediji spremljen + Pregled za Sadržaj je obrisan @@ -2610,7 +2616,7 @@ Da bi upravljali svojom web lokacijom, jednostavno otvorite Umbraco backoffice i Želite savladati Umbraco? Provedite nekoliko minuta učeći najbolje prakse gledajući jedan od ovih videozapisa o korištenju Umbraco-a Umbraco Learning Base Youtube kanal. Ovdje možete pronaći gomilu video materijala koji pokriva mnoge aspekte Umbraco-a.

+

Želite savladati Umbraco? Provedite nekoliko minuta učeći najbolje prakse gledajući jedan od ovih videozapisa o korištenju Umbraco-a Umbraco Learning Base Youtube kanal. Ovdje možete pronaći gomilu video materijala koji pokriva mnoge aspekte Umbraco-a.

]]>
Za početak diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/it.xml b/src/Umbraco.Core/EmbeddedResources/Lang/it.xml index 8cf6ba397b..7c57a290fe 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/it.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/it.xml @@ -163,6 +163,12 @@ Invia Invia e chiudi + + Media eliminato + Media spostato + Media copiato + Media salvato + Visualizzazione per Contenuto eliminato diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/nl.xml b/src/Umbraco.Core/EmbeddedResources/Lang/nl.xml index 0452178f92..209b5e0d38 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/nl.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/nl.xml @@ -112,6 +112,12 @@ zijn op de huidige node, tenzij een domein hieronder ook van toepassing is.]]> Domeinen + + Media verwijderd + Media verplaatst + Media gekopieerd + Media bewaard + Tonen voor Inhoud verwijderd @@ -537,6 +543,7 @@ Er zijn geen woordenboekitems. + Er zijn geen woordenboekitems gevonden. Woordenboekitem aanmaken @@ -738,7 +745,7 @@ Geschiedenis Icoon Id - Import + Importeren Alleen in deze map zoeken Info Binnenste marge diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/sv.xml b/src/Umbraco.Core/EmbeddedResources/Lang/sv.xml index 0d8050da52..966089415a 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/sv.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/sv.xml @@ -67,6 +67,12 @@ eller ärv kulturen från föregående noder. Appliceras även
på befintlig nod.]]>
+ + Media raderat + Media flyttat + Media kopierat + Media sparat + Visar för Innehållet raderat @@ -103,6 +109,12 @@ Du har gjort ändringar i detta innehåll. Är du säker på att du vill ta bort dem? Ignorera skapandet + %0% måste vara närvarande åtminstone %2% time(s).]]> + %0% måste maximalt finnas %3% time(s).]]> + + + %1% mer.]]> + %1% för många.]]> %0%]]> @@ -240,6 +252,60 @@ Komma igång Installera Umbraco Forms + + Backa + Aktiv layout: + Hoppa till + grupp + godkänd + varning + underkänd + förslag + Godkänd + Underkänd + Öppna sökfunktion (backoffice) + Öppna/stäng hjälpfunktion + Öppna/stäng personliga inställningar + Redigera språk och värdnamn för %0% + Skapa en ny nod under %0% + Ändra behörigheter för %0% + Redigera behörigheter för %0% + Ändra sortering av %0% + Skapa innehållsmall baserad på %0% + Öppna kontextmeny för + Aktuellt språk + Byt språk till + Skapa ny mapp + Del av vy + Del av vy (makro) + Medlem + Datatyp + Sök bland omdirigeringar + Sök bland användargrupper + Sök bland användare + Skapa post + Skapa + Redigera + Namn + Lägg till ny rad + Visa fler alternativ + Sök i Umbraco backoffice + Sök efter innehåll, media etc i hela Umbraco. + När det finns automatförslag, använd pil upp eller ner, eller använd tabbtangenten. Använd enter för att välja. + + Sökväg: + Hittad i + Har översättning + Saknar översättning + Post i ordlista + Välj ett av alternativen för att redigera noden. + Utför %0% på noden %1% + Lägg till bildtext + Sök i innehållsträdet + Maximalt värde + Visa underliggande noder för + Öppna kontext för + Stanna Ignorera ändringar diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/tr.xml b/src/Umbraco.Core/EmbeddedResources/Lang/tr.xml index 36978a08e6..1e1504f44a 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/tr.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/tr.xml @@ -156,6 +156,12 @@ Daha fazla yayınlama seçeneği Gönder + + Medya silindi + Medya taşındı + Medya kopyalandı + Medya kaydedildi + Görüntüleniyor İçerik silindi diff --git a/src/Umbraco.Core/ExpressionHelper.cs b/src/Umbraco.Core/ExpressionHelper.cs index 79e03d7b93..4d437e809f 100644 --- a/src/Umbraco.Core/ExpressionHelper.cs +++ b/src/Umbraco.Core/ExpressionHelper.cs @@ -167,7 +167,7 @@ public static class ExpressionHelper } /// - /// Gets a from an provided it refers to a method call. + /// Gets a from an of provided it refers to a method call. /// /// /// From expression. @@ -254,7 +254,7 @@ public static class ExpressionHelper } /// - /// Gets a from an provided it refers to member + /// Gets a from an of provided it refers to member /// access. /// /// diff --git a/src/Umbraco.Core/Extensions/ContentExtensions.cs b/src/Umbraco.Core/Extensions/ContentExtensions.cs index ba736c5b13..7d0bba26f8 100644 --- a/src/Umbraco.Core/Extensions/ContentExtensions.cs +++ b/src/Umbraco.Core/Extensions/ContentExtensions.cs @@ -320,6 +320,8 @@ public static class ContentExtensions /// Stores a file. /// /// A content item. + /// The media file manager. + /// The content type base service provider. /// The property alias. /// The name of the file. /// A stream containing the file data. diff --git a/src/Umbraco.Core/Extensions/DictionaryExtensions.cs b/src/Umbraco.Core/Extensions/DictionaryExtensions.cs index 3bbd3bdcb9..ca224a0f61 100644 --- a/src/Umbraco.Core/Extensions/DictionaryExtensions.cs +++ b/src/Umbraco.Core/Extensions/DictionaryExtensions.cs @@ -11,7 +11,7 @@ using Umbraco.Cms.Core; namespace Umbraco.Extensions; /// -/// Extension methods for Dictionary & ConcurrentDictionary +/// Extension methods for Dictionary & ConcurrentDictionary. /// public static class DictionaryExtensions { @@ -254,7 +254,7 @@ public static class DictionaryExtensions /// /// Converts a dictionary object to a query string representation such as: - /// firstname=shannon&lastname=deminick + /// firstname=shannon&lastname=deminick. /// /// /// diff --git a/src/Umbraco.Core/Extensions/EnumerableExtensions.cs b/src/Umbraco.Core/Extensions/EnumerableExtensions.cs index 6628dc4f3d..e2c0936fa4 100644 --- a/src/Umbraco.Core/Extensions/EnumerableExtensions.cs +++ b/src/Umbraco.Core/Extensions/EnumerableExtensions.cs @@ -353,7 +353,8 @@ public static class EnumerableExtensions /// /// Transforms an enumerable. /// - /// + /// + /// /// /// /// diff --git a/src/Umbraco.Core/Extensions/PublishedContentExtensions.cs b/src/Umbraco.Core/Extensions/PublishedContentExtensions.cs index b97c1c8161..ff4fd499f9 100644 --- a/src/Umbraco.Core/Extensions/PublishedContentExtensions.cs +++ b/src/Umbraco.Core/Extensions/PublishedContentExtensions.cs @@ -1253,7 +1253,7 @@ public static class PublishedContentExtensions /// /// /// This is the same as calling - /// with maxLevel + /// with maxLevel /// set to 1. /// public static IPublishedContent Root(this IPublishedContent content) => content.AncestorOrSelf(1); @@ -1270,7 +1270,7 @@ public static class PublishedContentExtensions /// /// /// This is the same as calling - /// with + /// with /// maxLevel set to 1. /// public static T? Root(this IPublishedContent content) diff --git a/src/Umbraco.Core/Extensions/PublishedElementExtensions.cs b/src/Umbraco.Core/Extensions/PublishedElementExtensions.cs index 440962cd76..c85178c85c 100644 --- a/src/Umbraco.Core/Extensions/PublishedElementExtensions.cs +++ b/src/Umbraco.Core/Extensions/PublishedElementExtensions.cs @@ -134,27 +134,6 @@ public static class PublishedElementExtensions #endregion - #region CheckVariation - /// - /// Method to check if VariationContext culture differs from culture parameter, if so it will update the VariationContext for the PublishedValueFallback. - /// - /// The requested PublishedValueFallback. - /// The requested culture. - /// The requested segment. - /// - private static void EventuallyUpdateVariationContext(IPublishedValueFallback publishedValueFallback, string? culture, string? segment) - { - IVariationContextAccessor? variationContextAccessor = publishedValueFallback.VariationContextAccessor; - - //If there is a difference in requested culture and the culture that is set in the VariationContext, it will pick wrong localized content. - //This happens for example using links to localized content in a RichText Editor. - if (!string.IsNullOrEmpty(culture) && variationContextAccessor?.VariationContext?.Culture != culture) - { - variationContextAccessor!.VariationContext = new VariationContext(culture, segment); - } - } - #endregion - #region Value /// @@ -195,8 +174,6 @@ public static class PublishedElementExtensions { IPublishedProperty? property = content.GetProperty(alias); - EventuallyUpdateVariationContext(publishedValueFallback, culture, segment); - // if we have a property, and it has a value, return that value if (property != null && property.HasValue(culture, segment)) { diff --git a/src/Umbraco.Core/Extensions/RequestHandlerSettingsExtension.cs b/src/Umbraco.Core/Extensions/RequestHandlerSettingsExtension.cs index 8699950b37..383ab7f5bf 100644 --- a/src/Umbraco.Core/Extensions/RequestHandlerSettingsExtension.cs +++ b/src/Umbraco.Core/Extensions/RequestHandlerSettingsExtension.cs @@ -28,7 +28,7 @@ public static class RequestHandlerSettingsExtension return RequestHandlerSettings.DefaultCharCollection; } - /// Merges CharCollection and UserDefinedCharCollection, prioritizing UserDefinedCharCollection. + // Merges CharCollection and UserDefinedCharCollection, prioritizing UserDefinedCharCollection. return MergeUnique(requestHandlerSettings.UserDefinedCharCollection, RequestHandlerSettings.DefaultCharCollection); } diff --git a/src/Umbraco.Core/Extensions/StringExtensions.cs b/src/Umbraco.Core/Extensions/StringExtensions.cs index c1abeb8650..e7849eef12 100644 --- a/src/Umbraco.Core/Extensions/StringExtensions.cs +++ b/src/Umbraco.Core/Extensions/StringExtensions.cs @@ -194,7 +194,7 @@ public static class StringExtensions /// /// /// This methods ensures that the resulting URL is structured correctly, that there's only one '?' and that things are - /// delimited properly with '&' + /// delimited properly with '&' /// public static string AppendQueryStringToUrl(this string url, params string[] queryStrings) { diff --git a/src/Umbraco.Core/Extensions/XmlExtensions.cs b/src/Umbraco.Core/Extensions/XmlExtensions.cs index bb9e6c69b5..96e642db75 100644 --- a/src/Umbraco.Core/Extensions/XmlExtensions.cs +++ b/src/Umbraco.Core/Extensions/XmlExtensions.cs @@ -28,7 +28,7 @@ public static class XmlExtensions /// /// /// If - /// + /// /// is null, or is empty, or contains only one single /// value which itself is null, then variables are ignored. /// @@ -51,7 +51,7 @@ public static class XmlExtensions /// /// /// If - /// + /// /// is null, or is empty, or contains only one single /// value which itself is null, then variables are ignored. /// @@ -74,7 +74,7 @@ public static class XmlExtensions /// /// /// If - /// + /// /// is null, or is empty, or contains only one single /// value which itself is null, then variables are ignored. /// @@ -102,7 +102,7 @@ public static class XmlExtensions /// /// /// If - /// + /// /// is null, or is empty, or contains only one single /// value which itself is null, then variables are ignored. /// @@ -130,7 +130,7 @@ public static class XmlExtensions /// /// /// If - /// + /// /// is null, or is empty, or contains only one single /// value which itself is null, then variables are ignored. /// @@ -153,7 +153,7 @@ public static class XmlExtensions /// /// /// If - /// + /// /// is null, or is empty, or contains only one single /// value which itself is null, then variables are ignored. /// @@ -176,7 +176,7 @@ public static class XmlExtensions /// /// /// If - /// + /// /// is null, or is empty, or contains only one single /// value which itself is null, then variables are ignored. /// @@ -203,7 +203,7 @@ public static class XmlExtensions /// /// /// If - /// + /// /// is null, or is empty, or contains only one single /// value which itself is null, then variables are ignored. /// diff --git a/src/Umbraco.Core/IO/MediaFileManager.cs b/src/Umbraco.Core/IO/MediaFileManager.cs index c222c01744..f67c5363c1 100644 --- a/src/Umbraco.Core/IO/MediaFileManager.cs +++ b/src/Umbraco.Core/IO/MediaFileManager.cs @@ -121,7 +121,6 @@ public sealed class MediaFileManager /// /// The file path if a file was found /// - /// /// /// /// diff --git a/src/Umbraco.Core/IO/MediaPathSchemes/UniqueMediaPathScheme.cs b/src/Umbraco.Core/IO/MediaPathSchemes/UniqueMediaPathScheme.cs index 7b7061506d..353636c186 100644 --- a/src/Umbraco.Core/IO/MediaPathSchemes/UniqueMediaPathScheme.cs +++ b/src/Umbraco.Core/IO/MediaPathSchemes/UniqueMediaPathScheme.cs @@ -22,14 +22,14 @@ public class UniqueMediaPathScheme : IMediaPathScheme /// /// /// - /// Returning null so that does *not* + /// Returning null so that does *not* /// delete any directory. This is because the above shortening of the Guid to 8 chars /// means we're increasing the risk of collision, and we don't want to delete files /// belonging to other media items. /// /// /// And, at the moment, we cannot delete directory "only if it is empty" because of - /// race conditions. We'd need to implement locks in for + /// race conditions. We'd need to implement locks in for /// this. /// /// diff --git a/src/Umbraco.Core/Logging/DisposableTimer.cs b/src/Umbraco.Core/Logging/DisposableTimer.cs index 02bb52a19d..087ae7adb6 100644 --- a/src/Umbraco.Core/Logging/DisposableTimer.cs +++ b/src/Umbraco.Core/Logging/DisposableTimer.cs @@ -113,7 +113,7 @@ public class DisposableTimer : DisposableObjectSlim /// /// Disposes resources. /// - /// Overrides abstract class which handles required locking. + /// Overrides abstract class which handles required locking. protected override void DisposeResources() { Stopwatch.Stop(); diff --git a/src/Umbraco.Core/Logging/ProfilingLogger.cs b/src/Umbraco.Core/Logging/ProfilingLogger.cs index 9c5c8bc17e..53a9ee4f58 100644 --- a/src/Umbraco.Core/Logging/ProfilingLogger.cs +++ b/src/Umbraco.Core/Logging/ProfilingLogger.cs @@ -141,7 +141,7 @@ public sealed class ProfilingLogger : IProfilingLogger public void LogTrace(string messageTemplate, params object[] propertyValues) => Logger.LogTrace(messageTemplate, propertyValues); - ////> + /// public bool IsEnabled(LogLevel logLevel) { switch (logLevel) diff --git a/src/Umbraco.Core/Media/IEmbedProvider.cs b/src/Umbraco.Core/Media/IEmbedProvider.cs index 6760243ce6..06eb547365 100644 --- a/src/Umbraco.Core/Media/IEmbedProvider.cs +++ b/src/Umbraco.Core/Media/IEmbedProvider.cs @@ -15,7 +15,7 @@ public interface IEmbedProvider /// /// A collection of querystring request parameters to append to the API URL /// - /// ?key=value&key2=value2 + /// ?key=value&key2=value2 Dictionary RequestParams { get; } string? GetMarkup(string url, int maxWidth = 0, int maxHeight = 0); diff --git a/src/Umbraco.Core/Models/Blocks/BlockGridItem.cs b/src/Umbraco.Core/Models/Blocks/BlockGridItem.cs index eb554ef90d..abe8cc89a0 100644 --- a/src/Umbraco.Core/Models/Blocks/BlockGridItem.cs +++ b/src/Umbraco.Core/Models/Blocks/BlockGridItem.cs @@ -20,9 +20,7 @@ namespace Umbraco.Cms.Core.Models.Blocks /// The content. /// The settings UDI. /// The settings. - /// The number of rows to span - /// The number of columns to span - /// contentUdi + /// contentUdi /// or /// content public BlockGridItem(Udi contentUdi, IPublishedElement content, Udi settingsUdi, IPublishedElement settings) @@ -114,8 +112,6 @@ namespace Umbraco.Cms.Core.Models.Blocks /// The content. /// The settings UDI. /// The settings. - /// The number of rows to span - /// The number of columns to span public BlockGridItem(Udi contentUdi, T content, Udi settingsUdi, IPublishedElement settings) : base(contentUdi, content, settingsUdi, settings) { @@ -147,8 +143,6 @@ namespace Umbraco.Cms.Core.Models.Blocks /// The content. /// The settings udi. /// The settings. - /// The number of rows to span - /// The number of columns to span public BlockGridItem(Udi contentUdi, TContent content, Udi settingsUdi, TSettings settings) : base(contentUdi, content, settingsUdi, settings) { diff --git a/src/Umbraco.Core/Models/Blocks/IBlockReference.cs b/src/Umbraco.Core/Models/Blocks/IBlockReference.cs index 3a51649cca..647d1f5b2f 100644 --- a/src/Umbraco.Core/Models/Blocks/IBlockReference.cs +++ b/src/Umbraco.Core/Models/Blocks/IBlockReference.cs @@ -44,7 +44,8 @@ public interface IBlockReference : IBlockReference /// /// Represents a data item reference with content and settings for a Block editor implementation. /// -/// The type of the content. +/// The type of the content. +/// The type of the settings. public interface IBlockReference : IBlockReference { /// diff --git a/src/Umbraco.Core/Models/ContentRepositoryExtensions.cs b/src/Umbraco.Core/Models/ContentRepositoryExtensions.cs index d76194aa64..5b7d68a72b 100644 --- a/src/Umbraco.Core/Models/ContentRepositoryExtensions.cs +++ b/src/Umbraco.Core/Models/ContentRepositoryExtensions.cs @@ -59,6 +59,7 @@ public static class ContentRepositoryExtensions /// /// /// + /// /// /// This is so that in an operation where (for example) 2 languages are updates like french and english, it is possible /// that diff --git a/src/Umbraco.Core/Models/CultureImpact.cs b/src/Umbraco.Core/Models/CultureImpact.cs index 684f1d058c..9ce9d9fe6a 100644 --- a/src/Umbraco.Core/Models/CultureImpact.cs +++ b/src/Umbraco.Core/Models/CultureImpact.cs @@ -182,7 +182,6 @@ public sealed class CultureImpact /// /// The culture code. /// A value indicating whether the culture is the default culture. - /// A value indicating if publishing invariant properties from non-default language. [Obsolete("Use ICultureImpactService instead.")] public static CultureImpact Explicit(string? culture, bool isDefault) { @@ -211,7 +210,6 @@ public sealed class CultureImpact /// The culture code. /// A value indicating whether the culture is the default culture. /// The content item. - /// A value indicating if publishing invariant properties from non-default language. /// /// Validates that the culture is compatible with the variation. /// diff --git a/src/Umbraco.Core/Models/DeliveryApi/ProtectedAccess.cs b/src/Umbraco.Core/Models/DeliveryApi/ProtectedAccess.cs new file mode 100644 index 0000000000..017e5f4e29 --- /dev/null +++ b/src/Umbraco.Core/Models/DeliveryApi/ProtectedAccess.cs @@ -0,0 +1,16 @@ +namespace Umbraco.Cms.Core.Models.DeliveryApi; + +public sealed class ProtectedAccess +{ + public static ProtectedAccess None => new(null, null); + + public ProtectedAccess(Guid? memberKey, string[]? memberRoles) + { + MemberKey = memberKey; + MemberRoles = memberRoles; + } + + public Guid? MemberKey { get; } + + public string[]? MemberRoles { get; } +} diff --git a/src/Umbraco.Core/Models/IMediaUrlGenerator.cs b/src/Umbraco.Core/Models/IMediaUrlGenerator.cs index a0af9dcc0e..d54a354f38 100644 --- a/src/Umbraco.Core/Models/IMediaUrlGenerator.cs +++ b/src/Umbraco.Core/Models/IMediaUrlGenerator.cs @@ -10,6 +10,7 @@ public interface IMediaUrlGenerator /// /// The property editor alias /// The value of the property + /// The media path /// /// True if a media path was returned /// diff --git a/src/Umbraco.Core/Models/Media.cs b/src/Umbraco.Core/Models/Media.cs index d0cf05b8b9..5ee78006eb 100644 --- a/src/Umbraco.Core/Models/Media.cs +++ b/src/Umbraco.Core/Models/Media.cs @@ -58,7 +58,7 @@ public class Media : ContentBase, IMedia /// /// Changes the for the current Media object /// - /// New MediaType for this Media + /// New MediaType for this Media /// Leaves PropertyTypes intact after change internal void ChangeContentType(IMediaType mediaType) => ChangeContentType(mediaType, false); @@ -66,7 +66,7 @@ public class Media : ContentBase, IMedia /// Changes the for the current Media object and removes PropertyTypes, /// which are not part of the new MediaType. /// - /// New MediaType for this Media + /// New MediaType for this Media /// Boolean indicating whether to clear PropertyTypes upon change internal void ChangeContentType(IMediaType mediaType, bool clearProperties) { diff --git a/src/Umbraco.Core/Models/Membership/ContentPermissionSet.cs b/src/Umbraco.Core/Models/Membership/ContentPermissionSet.cs index 613a873d7a..c10a3b3da6 100644 --- a/src/Umbraco.Core/Models/Membership/ContentPermissionSet.cs +++ b/src/Umbraco.Core/Models/Membership/ContentPermissionSet.cs @@ -3,7 +3,7 @@ using Umbraco.Cms.Core.Models.Entities; namespace Umbraco.Cms.Core.Models.Membership; /// -/// Represents an -> user group & permission key value pair collection +/// Represents an -> user group & permission key value pair collection /// /// /// This implements purely so it can be used with the repository layer which is why it's diff --git a/src/Umbraco.Core/Models/Membership/EntityPermissionSet.cs b/src/Umbraco.Core/Models/Membership/EntityPermissionSet.cs index 0ae0dbf335..fce893c710 100644 --- a/src/Umbraco.Core/Models/Membership/EntityPermissionSet.cs +++ b/src/Umbraco.Core/Models/Membership/EntityPermissionSet.cs @@ -1,7 +1,7 @@ namespace Umbraco.Cms.Core.Models.Membership; /// -/// Represents an entity -> user group & permission key value pair collection +/// Represents an entity -> user group & permission key value pair collection /// public class EntityPermissionSet { @@ -20,7 +20,7 @@ public class EntityPermissionSet public virtual int EntityId { get; } /// - /// The key/value pairs of user group id & single permission + /// The key/value pairs of user group id & single permission /// public EntityPermissionCollection PermissionsSet { get; } diff --git a/src/Umbraco.Core/Models/PagedResult.cs b/src/Umbraco.Core/Models/PagedResult.cs index 6dbe6dd703..d60fb707ca 100644 --- a/src/Umbraco.Core/Models/PagedResult.cs +++ b/src/Umbraco.Core/Models/PagedResult.cs @@ -5,7 +5,6 @@ namespace Umbraco.Cms.Core.Models; /// /// Represents a paged result for a model collection /// -/// [DataContract(Name = "pagedCollection", Namespace = "")] public abstract class PagedResult { diff --git a/src/Umbraco.Core/Models/PublishedContent/HttpContextVariationContextAccessor.cs b/src/Umbraco.Core/Models/PublishedContent/HttpContextVariationContextAccessor.cs index 6d8fe9e547..277484bea7 100644 --- a/src/Umbraco.Core/Models/PublishedContent/HttpContextVariationContextAccessor.cs +++ b/src/Umbraco.Core/Models/PublishedContent/HttpContextVariationContextAccessor.cs @@ -3,7 +3,7 @@ using Umbraco.Cms.Core.Cache; namespace Umbraco.Cms.Core.Models.PublishedContent; /// -/// Implements on top of . +/// Implements on top of . /// public class HttpContextVariationContextAccessor : IVariationContextAccessor { diff --git a/src/Umbraco.Core/Models/PublishedContent/IPublishedValueFallback.cs b/src/Umbraco.Core/Models/PublishedContent/IPublishedValueFallback.cs index 111d747ec1..839b73ea51 100644 --- a/src/Umbraco.Core/Models/PublishedContent/IPublishedValueFallback.cs +++ b/src/Umbraco.Core/Models/PublishedContent/IPublishedValueFallback.cs @@ -5,6 +5,7 @@ namespace Umbraco.Cms.Core.Models.PublishedContent; /// public interface IPublishedValueFallback { + [Obsolete("Scheduled for removal in v14")] /// /// VariationContextAccessor that is not required to be implemented, therefore throws NotImplementedException as default. /// diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedValueFallback.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedValueFallback.cs index 1d4c512f78..0524ee98a9 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedValueFallback.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedValueFallback.cs @@ -20,6 +20,7 @@ public class PublishedValueFallback : IPublishedValueFallback _variationContextAccessor = variationContextAccessor; } + [Obsolete("Scheduled for removal in v14")] public IVariationContextAccessor VariationContextAccessor { get { return _variationContextAccessor; } } /// diff --git a/src/Umbraco.Core/Models/Range.cs b/src/Umbraco.Core/Models/Range.cs index 5be2d068d4..2c287191bb 100644 --- a/src/Umbraco.Core/Models/Range.cs +++ b/src/Umbraco.Core/Models/Range.cs @@ -6,7 +6,9 @@ namespace Umbraco.Cms.Core.Models; /// Represents a range with a minimum and maximum value. /// /// The type of the minimum and maximum values. -/// +/// +/// See also of +/// public class Range : IEquatable> where T : IComparable { diff --git a/src/Umbraco.Core/Notifications/ContentPublishedNotification.cs b/src/Umbraco.Core/Notifications/ContentPublishedNotification.cs index 58bb76280b..ef911832d2 100644 --- a/src/Umbraco.Core/Notifications/ContentPublishedNotification.cs +++ b/src/Umbraco.Core/Notifications/ContentPublishedNotification.cs @@ -24,7 +24,6 @@ public sealed class ContentPublishedNotification : EnumerableObjectNotification< public ContentPublishedNotification(IEnumerable target, EventMessages messages, bool includeDescendants) : base(target, messages) => IncludeDescendants = includeDescendants; - /// public IEnumerable PublishedEntities => Target; public bool IncludeDescendants { get; } diff --git a/src/Umbraco.Core/Notifications/ModelBindingErrorNotification.cs b/src/Umbraco.Core/Notifications/ModelBindingErrorNotification.cs index 0048699e09..be9271b805 100644 --- a/src/Umbraco.Core/Notifications/ModelBindingErrorNotification.cs +++ b/src/Umbraco.Core/Notifications/ModelBindingErrorNotification.cs @@ -3,7 +3,7 @@ using System.Text; namespace Umbraco.Cms.Core.Notifications; /// -/// Contains event data for the event. +/// Contains event data for the event. /// public class ModelBindingErrorNotification : INotification { diff --git a/src/Umbraco.Core/Notifications/UmbracoApplicationStartingNotification.cs b/src/Umbraco.Core/Notifications/UmbracoApplicationStartingNotification.cs index 49eaac02f2..9172359eb0 100644 --- a/src/Umbraco.Core/Notifications/UmbracoApplicationStartingNotification.cs +++ b/src/Umbraco.Core/Notifications/UmbracoApplicationStartingNotification.cs @@ -1,10 +1,10 @@ namespace Umbraco.Cms.Core.Notifications; /// -/// Notification that occurs at the very end of the Umbraco boot process (after all s are +/// Notification that occurs at the very end of the Umbraco boot process (after all s are /// initialized). /// - /// + /// public class UmbracoApplicationStartingNotification : IUmbracoApplicationLifetimeNotification { /// diff --git a/src/Umbraco.Core/Notifications/UmbracoApplicationStoppingNotification.cs b/src/Umbraco.Core/Notifications/UmbracoApplicationStoppingNotification.cs index 8face75954..d33233d438 100644 --- a/src/Umbraco.Core/Notifications/UmbracoApplicationStoppingNotification.cs +++ b/src/Umbraco.Core/Notifications/UmbracoApplicationStoppingNotification.cs @@ -2,9 +2,9 @@ namespace Umbraco.Cms.Core.Notifications; /// - /// Notification that occurs when Umbraco is shutting down (after all s are terminated). + /// Notification that occurs when Umbraco is shutting down (after all s are terminated). /// - /// + /// public class UmbracoApplicationStoppingNotification : IUmbracoApplicationLifetimeNotification { /// diff --git a/src/Umbraco.Core/Packaging/IPackageInstallation.cs b/src/Umbraco.Core/Packaging/IPackageInstallation.cs index 7fc714bfdb..a6de843402 100644 --- a/src/Umbraco.Core/Packaging/IPackageInstallation.cs +++ b/src/Umbraco.Core/Packaging/IPackageInstallation.cs @@ -22,7 +22,7 @@ public interface IPackageInstallation /// /// Reads the package xml and returns the model /// - /// + /// /// CompiledPackage ReadPackage(XDocument? packageXmlFile); } diff --git a/src/Umbraco.Core/Persistence/Repositories/ICacheInstructionRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ICacheInstructionRepository.cs index f11ddf10e3..bdd6526ed7 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ICacheInstructionRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ICacheInstructionRepository.cs @@ -3,7 +3,7 @@ using Umbraco.Cms.Core.Models; namespace Umbraco.Cms.Core.Persistence.Repositories; /// -/// Represents a repository for entities. +/// Represents a repository for entities. /// public interface ICacheInstructionRepository : IRepository { diff --git a/src/Umbraco.Core/Persistence/Repositories/IDocumentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IDocumentRepository.cs index 15312ccbf2..b51f02fd54 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IDocumentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IDocumentRepository.cs @@ -36,9 +36,8 @@ public interface IDocumentRepository : IContentRepository, IReadR /// Gets objects having an expiration date before (lower than, or equal to) a specified date. /// /// - /// The content returned from this method may be culture variant, in which case the resulting - /// should be queried - /// for which culture(s) have been scheduled. + /// The content returned from this method may be culture variant, in which case you can use + /// to get the status for a specific culture. /// IEnumerable GetContentForExpiration(DateTime date); @@ -46,9 +45,8 @@ public interface IDocumentRepository : IContentRepository, IReadR /// Gets objects having a release date before (lower than, or equal to) a specified date. /// /// - /// The content returned from this method may be culture variant, in which case the resulting - /// should be queried - /// for which culture(s) have been scheduled. + /// The content returned from this method may be culture variant, in which case you can use + /// to get the status for a specific culture. /// IEnumerable GetContentForRelease(DateTime date); diff --git a/src/Umbraco.Core/PropertyEditors/DefaultPropertyIndexValueFactory.cs b/src/Umbraco.Core/PropertyEditors/DefaultPropertyIndexValueFactory.cs index 0193f45778..28f41c5a20 100644 --- a/src/Umbraco.Core/PropertyEditors/DefaultPropertyIndexValueFactory.cs +++ b/src/Umbraco.Core/PropertyEditors/DefaultPropertyIndexValueFactory.cs @@ -5,7 +5,7 @@ namespace Umbraco.Cms.Core.PropertyEditors; /// /// Provides a default implementation for -/// , returning a single field to index containing the property value. +/// , returning a single field to index containing the property value. /// public class DefaultPropertyIndexValueFactory : IPropertyIndexValueFactory { diff --git a/src/Umbraco.Core/PropertyEditors/VoidEditor.cs b/src/Umbraco.Core/PropertyEditors/VoidEditor.cs index f272dc49bd..507d20d386 100644 --- a/src/Umbraco.Core/PropertyEditors/VoidEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/VoidEditor.cs @@ -17,7 +17,7 @@ public class VoidEditor : DataEditor /// Initializes a new instance of the class. /// /// An optional alias suffix. - /// A logger factory. + /// A data value editor factory. /// /// The default alias of the editor is "Umbraco.Void". When a suffix is provided, /// it is appended to the alias. Eg if the suffix is "Foo" the alias is "Umbraco.Void.Foo". @@ -39,7 +39,7 @@ public class VoidEditor : DataEditor /// /// Initializes a new instance of the class. /// - /// A logger factory. + /// A data value editor factory. /// The alias of the editor is "Umbraco.Void". public VoidEditor( IDataValueEditorFactory dataValueEditorFactory) diff --git a/src/Umbraco.Core/PublishedCache/IPublishedContentCache.cs b/src/Umbraco.Core/PublishedCache/IPublishedContentCache.cs index 7526226302..6d5fa9b4e8 100644 --- a/src/Umbraco.Core/PublishedCache/IPublishedContentCache.cs +++ b/src/Umbraco.Core/PublishedCache/IPublishedContentCache.cs @@ -19,7 +19,7 @@ public interface IPublishedContentCache : IPublishedCache /// /// /// If - /// + /// /// is null then the settings value is used. /// /// The value of overrides defaults. @@ -40,7 +40,7 @@ public interface IPublishedContentCache : IPublishedCache /// /// /// If - /// + /// /// is null then the settings value is used. /// /// Considers published or unpublished content depending on defaults. diff --git a/src/Umbraco.Core/Routing/DefaultUrlProvider.cs b/src/Umbraco.Core/Routing/DefaultUrlProvider.cs index 97e2f73e88..c30d5453aa 100644 --- a/src/Umbraco.Core/Routing/DefaultUrlProvider.cs +++ b/src/Umbraco.Core/Routing/DefaultUrlProvider.cs @@ -47,7 +47,6 @@ public class DefaultUrlProvider : IUrlProvider /// /// Gets the other URLs of a published content. /// - /// The Umbraco context. /// The published content id. /// The current absolute URL. /// The other URLs for the published content. diff --git a/src/Umbraco.Core/Routing/IPublishedRequest.cs b/src/Umbraco.Core/Routing/IPublishedRequest.cs index 645de414d7..fa3d2779a4 100644 --- a/src/Umbraco.Core/Routing/IPublishedRequest.cs +++ b/src/Umbraco.Core/Routing/IPublishedRequest.cs @@ -1,5 +1,6 @@ using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Extensions; namespace Umbraco.Cms.Core.Routing; @@ -101,7 +102,7 @@ public interface IPublishedRequest /// This flag is based on previous Umbraco versions but it is not clear how this flag can be set by developers /// since /// collission checking only occurs in the back office which is launched by - /// + /// /// for which events do not execute. /// /// diff --git a/src/Umbraco.Core/Routing/IPublishedRequestBuilder.cs b/src/Umbraco.Core/Routing/IPublishedRequestBuilder.cs index f6cdafee78..77f8d8d53a 100644 --- a/src/Umbraco.Core/Routing/IPublishedRequestBuilder.cs +++ b/src/Umbraco.Core/Routing/IPublishedRequestBuilder.cs @@ -2,6 +2,7 @@ using System.Globalization; using System.Net; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Extensions; namespace Umbraco.Cms.Core.Routing; @@ -162,7 +163,7 @@ public interface IPublishedRequestBuilder /// This flag is based on previous Umbraco versions but it is not clear how this flag can be set by developers /// since /// collission checking only occurs in the back office which is launched by - /// + /// /// for which events do not execute. /// /// diff --git a/src/Umbraco.Core/Routing/IPublishedRouter.cs b/src/Umbraco.Core/Routing/IPublishedRouter.cs index 5434c46447..53a07ff325 100644 --- a/src/Umbraco.Core/Routing/IPublishedRouter.cs +++ b/src/Umbraco.Core/Routing/IPublishedRouter.cs @@ -26,6 +26,7 @@ public interface IPublishedRouter /// Updates the request to use the specified item, or NULL /// /// The request. + /// The published content. /// /// /// A new based on values from the original diff --git a/src/Umbraco.Core/Routing/UrlProvider.cs b/src/Umbraco.Core/Routing/UrlProvider.cs index 97385a144b..f6c8691622 100644 --- a/src/Umbraco.Core/Routing/UrlProvider.cs +++ b/src/Umbraco.Core/Routing/UrlProvider.cs @@ -21,7 +21,6 @@ namespace Umbraco.Cms.Core.Routing /// The list of URL providers. /// The list of media URL providers. /// The current variation accessor. - /// public UrlProvider(IUmbracoContextAccessor umbracoContextAccessor, IOptions routingSettings, UrlProviderCollection urlProviders, MediaUrlProviderCollection mediaUrlProviders, IVariationContextAccessor variationContextAccessor) { _umbracoContextAccessor = umbracoContextAccessor ?? throw new ArgumentNullException(nameof(umbracoContextAccessor)); diff --git a/src/Umbraco.Core/Scoping/ILockingMechanism.cs b/src/Umbraco.Core/Scoping/ILockingMechanism.cs index 22dded1652..449c4a01ed 100644 --- a/src/Umbraco.Core/Scoping/ILockingMechanism.cs +++ b/src/Umbraco.Core/Scoping/ILockingMechanism.cs @@ -6,6 +6,7 @@ public interface ILockingMechanism : IDisposable /// Read-locks some lock objects lazily. /// /// Instance id of the scope who is requesting the lock + /// Timeout for the lock /// Array of lock object identifiers. void ReadLock(Guid instanceId, TimeSpan? timeout = null, params int[] lockIds); @@ -15,6 +16,7 @@ public interface ILockingMechanism : IDisposable /// Write-locks some lock objects lazily. /// /// Instance id of the scope who is requesting the lock + /// Timeout for the lock /// Array of object identifiers. void WriteLock(Guid instanceId, TimeSpan? timeout = null, params int[] lockIds); @@ -24,6 +26,7 @@ public interface ILockingMechanism : IDisposable /// Eagerly acquires a read-lock /// /// + /// Timeout for the lock /// void EagerReadLock(Guid instanceId, TimeSpan? timeout = null, params int[] lockIds); @@ -33,6 +36,7 @@ public interface ILockingMechanism : IDisposable /// Eagerly acquires a write-lock /// /// + /// Timeout for the lock /// void EagerWriteLock(Guid instanceId, TimeSpan? timeout = null, params int[] lockIds); diff --git a/src/Umbraco.Core/Security/ContentPermissions.cs b/src/Umbraco.Core/Security/ContentPermissions.cs index db27d100c6..d43e527a62 100644 --- a/src/Umbraco.Core/Security/ContentPermissions.cs +++ b/src/Umbraco.Core/Security/ContentPermissions.cs @@ -151,8 +151,6 @@ public class ContentPermissions /// /// /// - /// - /// /// The item resolved if one was found for the id /// /// @@ -218,9 +216,6 @@ public class ContentPermissions /// /// /// - /// - /// - /// /// The item resolved if one was found for the id /// /// diff --git a/src/Umbraco.Core/Security/IIdentityUserLogin.cs b/src/Umbraco.Core/Security/IIdentityUserLogin.cs index 51035b724c..69b01fdf5a 100644 --- a/src/Umbraco.Core/Security/IIdentityUserLogin.cs +++ b/src/Umbraco.Core/Security/IIdentityUserLogin.cs @@ -5,7 +5,6 @@ namespace Umbraco.Cms.Core.Security; /// /// An external login provider linked to a user /// -/// The PK type for the user public interface IIdentityUserLogin : IEntity, IRememberBeingDirty { /// diff --git a/src/Umbraco.Core/Security/IPublicAccessChecker.cs b/src/Umbraco.Core/Security/IPublicAccessChecker.cs index d830d757f1..82957b37de 100644 --- a/src/Umbraco.Core/Security/IPublicAccessChecker.cs +++ b/src/Umbraco.Core/Security/IPublicAccessChecker.cs @@ -1,6 +1,21 @@ +using System.Security.Claims; + namespace Umbraco.Cms.Core.Security; public interface IPublicAccessChecker { + /// + /// Tests the current member access level to a given content item. + /// + /// The ID of the content item. + /// The access level for the content item. Task HasMemberAccessToContentAsync(int publishedContentId); + + /// + /// Tests member access level to a given content item. + /// + /// The ID of the content item. + /// The member claims to test against the content item. + /// The access level for the content item. + Task HasMemberAccessToContentAsync(int publishedContentId, ClaimsPrincipal claimsPrincipal) => Task.FromResult(PublicAccessStatus.AccessDenied); } diff --git a/src/Umbraco.Core/Security/LegacyPasswordSecurity.cs b/src/Umbraco.Core/Security/LegacyPasswordSecurity.cs index 3b53509240..a960fd7998 100644 --- a/src/Umbraco.Core/Security/LegacyPasswordSecurity.cs +++ b/src/Umbraco.Core/Security/LegacyPasswordSecurity.cs @@ -218,10 +218,9 @@ public class LegacyPasswordSecurity } /// - /// Return the hash algorithm to use based on the + /// Return the hash algorithm to use based on the provided . /// /// The hashing algorithm name. - /// /// private HashAlgorithm GetHashAlgorithm(string algorithm) { diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs index 1d04b2950a..430a2573af 100644 --- a/src/Umbraco.Core/Services/ContentService.cs +++ b/src/Umbraco.Core/Services/ContentService.cs @@ -207,7 +207,7 @@ public class ContentService : RepositoryService, IContentService /// /// Used to bulk update the permissions set for a content item. This will replace all permissions - /// assigned to an entity with a list of user id & permission pairs. + /// assigned to an entity with a list of user id & permission pairs. /// /// public void SetPermissions(EntityPermissionSet permissionSet) diff --git a/src/Umbraco.Core/Services/ICacheInstructionService.cs b/src/Umbraco.Core/Services/ICacheInstructionService.cs index 0b71bde66d..25b52c09e3 100644 --- a/src/Umbraco.Core/Services/ICacheInstructionService.cs +++ b/src/Umbraco.Core/Services/ICacheInstructionService.cs @@ -36,7 +36,9 @@ public interface ICacheInstructionService /// /// Processes and then prunes pending database cache instructions. /// - /// Flag indicating if process is shutting now and operations should exit. + /// Cache refreshers. + /// Server role. + /// Cancellation token. /// Local identity of the executing AppDomain. /// Date of last prune operation. /// Id of the latest processed instruction diff --git a/src/Umbraco.Core/Services/IContentService.cs b/src/Umbraco.Core/Services/IContentService.cs index 1eb2db83bf..0d3cc80b82 100644 --- a/src/Umbraco.Core/Services/IContentService.cs +++ b/src/Umbraco.Core/Services/IContentService.cs @@ -76,12 +76,10 @@ public interface IContentService : IContentServiceBase /// IContent? GetById(int id); - new - /// /// Gets a document. /// - IContent? GetById(Guid key); + new IContent? GetById(Guid key); /// /// Gets publish/unpublish schedule for a content node. @@ -167,9 +165,8 @@ public interface IContentService : IContentServiceBase /// /// An Enumerable list of objects /// - /// The content returned from this method may be culture variant, in which case the resulting - /// should be queried - /// for which culture(s) have been scheduled. + /// The content returned from this method may be culture variant, in which case you can use + /// to get the status for a specific culture. /// IEnumerable GetContentForExpiration(DateTime date); @@ -178,9 +175,8 @@ public interface IContentService : IContentServiceBase /// /// An Enumerable list of objects /// - /// The content returned from this method may be culture variant, in which case the resulting - /// should be queried - /// for which culture(s) have been scheduled. + /// The content returned from this method may be culture variant, in which case you can use + /// to get the status for a specific culture. /// IEnumerable GetContentForRelease(DateTime date); diff --git a/src/Umbraco.Core/Services/IExternalLoginWithKeyService.cs b/src/Umbraco.Core/Services/IExternalLoginWithKeyService.cs index 54f827c899..42f0708aaa 100644 --- a/src/Umbraco.Core/Services/IExternalLoginWithKeyService.cs +++ b/src/Umbraco.Core/Services/IExternalLoginWithKeyService.cs @@ -35,7 +35,7 @@ public interface IExternalLoginWithKeyService : IService /// /// Saves the external login tokens associated with the user /// - /// + /// /// The user or member key associated with the logins /// /// diff --git a/src/Umbraco.Core/Services/IMediaService.cs b/src/Umbraco.Core/Services/IMediaService.cs index 86440b1119..1e433814f7 100644 --- a/src/Umbraco.Core/Services/IMediaService.cs +++ b/src/Umbraco.Core/Services/IMediaService.cs @@ -90,9 +90,6 @@ public interface IMediaService : IContentServiceBase /// Page number /// Page size /// Total records query would return without paging - /// Field to order by - /// Direction to order by - /// Flag to indicate when ordering by system field /// /// /// An Enumerable list of objects diff --git a/src/Umbraco.Core/Services/IMemberService.cs b/src/Umbraco.Core/Services/IMemberService.cs index b2b2b5d8d5..a1be0b4a4c 100644 --- a/src/Umbraco.Core/Services/IMemberService.cs +++ b/src/Umbraco.Core/Services/IMemberService.cs @@ -178,7 +178,7 @@ public interface IMemberService : IMembershipMemberService /// /// If no alias is supplied then the count for all Member will be returned /// Optional alias for the MemberType when counting number of Members - /// with number of Members + /// with number of Members int Count(string? memberTypeAlias = null); /// @@ -204,7 +204,7 @@ public interface IMemberService : IMembershipMemberService /// /// Gets a Member by its integer id /// - /// Id + /// Id /// /// /// @@ -278,7 +278,7 @@ public interface IMemberService : IMembershipMemberService /// Gets a list of Members based on a property search /// /// Alias of the PropertyType to search for - /// Value to match + /// Value to match /// /// The type of match to make as . Default is /// @@ -295,7 +295,7 @@ public interface IMemberService : IMembershipMemberService /// Gets a list of Members based on a property search /// /// Alias of the PropertyType to search for - /// Value to match + /// Value to match /// /// The type of match to make as . Default is /// @@ -309,7 +309,7 @@ public interface IMemberService : IMembershipMemberService /// Gets a list of Members based on a property search /// /// Alias of the PropertyType to search for - /// Value to match + /// Value to match /// /// /// diff --git a/src/Umbraco.Core/Services/IMembershipMemberService.cs b/src/Umbraco.Core/Services/IMembershipMemberService.cs index 553441f572..99e64a3686 100644 --- a/src/Umbraco.Core/Services/IMembershipMemberService.cs +++ b/src/Umbraco.Core/Services/IMembershipMemberService.cs @@ -48,7 +48,7 @@ public interface IMembershipMemberService : IService /// but that is how MS have made theirs so we'll follow that principal. /// /// to count by - /// with number of Members or Users for passed in type + /// with number of Members or Users for passed in type int GetCount(MemberCountType countType); /// diff --git a/src/Umbraco.Core/Services/IPackagingService.cs b/src/Umbraco.Core/Services/IPackagingService.cs index 40f39628be..f4102c08de 100644 --- a/src/Umbraco.Core/Services/IPackagingService.cs +++ b/src/Umbraco.Core/Services/IPackagingService.cs @@ -9,14 +9,14 @@ public interface IPackagingService : IService /// /// Returns a result from an umbraco package file (zip) /// - /// + /// /// CompiledPackage GetCompiledPackageInfo(XDocument packageXml); /// /// Installs the data, entities, objects contained in an umbraco package file (zip) /// - /// + /// /// InstallationSummary InstallCompiledPackageData(FileInfo packageXmlFile, int userId = Constants.Security.SuperUserId); diff --git a/src/Umbraco.Core/Services/IRelationService.cs b/src/Umbraco.Core/Services/IRelationService.cs index 6f8fa9b75a..5d50466d0f 100644 --- a/src/Umbraco.Core/Services/IRelationService.cs +++ b/src/Umbraco.Core/Services/IRelationService.cs @@ -167,7 +167,8 @@ public interface IRelationService : IService /// /// /// - /// + /// + /// /// IEnumerable GetPagedByRelationTypeId(int relationTypeId, long pageIndex, int pageSize, out long totalRecords, Ordering? ordering = null); @@ -213,6 +214,7 @@ public interface IRelationService : IService /// /// /// + /// /// An enumerable list of IEnumerable GetPagedParentEntitiesByChildId(int id, long pageIndex, int pageSize, out long totalChildren, params UmbracoObjectTypes[] entityTypes); @@ -223,6 +225,7 @@ public interface IRelationService : IService /// /// /// + /// /// An enumerable list of IEnumerable GetPagedChildEntitiesByParentId(int id, long pageIndex, int pageSize, out long totalChildren, params UmbracoObjectTypes[] entityTypes); diff --git a/src/Umbraco.Core/Services/ITagService.cs b/src/Umbraco.Core/Services/ITagService.cs index 5e2f164a35..93a9d2a0c4 100644 --- a/src/Umbraco.Core/Services/ITagService.cs +++ b/src/Umbraco.Core/Services/ITagService.cs @@ -1,9 +1,9 @@ -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; namespace Umbraco.Cms.Core.Services; /// -/// Tag service to query for tags in the tags db table. The tags returned are only relevant for published content & +/// Tag service to query for tags in the tags db table. The tags returned are only relevant for published content & /// saved media or members /// /// diff --git a/src/Umbraco.Core/Services/ITrackedReferencesService.cs b/src/Umbraco.Core/Services/ITrackedReferencesService.cs index 5ffe5e3651..7ea2e07e03 100644 --- a/src/Umbraco.Core/Services/ITrackedReferencesService.cs +++ b/src/Umbraco.Core/Services/ITrackedReferencesService.cs @@ -55,7 +55,6 @@ public interface ITrackedReferencesService /// A boolean indicating whether to filter only the RelationTypes which are /// dependencies (isDependency field is set to true). /// - /// The total amount of items. /// A paged result of objects. PagedModel GetPagedRelationsForItem(int id, long skip, long take, bool filterMustBeIsDependency) => throw new NotImplementedException(); @@ -69,7 +68,6 @@ public interface ITrackedReferencesService /// A boolean indicating whether to filter only the RelationTypes which are /// dependencies (isDependency field is set to true). /// - /// The total amount of items. /// A paged result of objects. PagedModel GetPagedDescendantsInReferences(int parentId, long skip, long take, bool filterMustBeIsDependency) => throw new NotImplementedException(); @@ -83,7 +81,6 @@ public interface ITrackedReferencesService /// A boolean indicating whether to filter only the RelationTypes which are /// dependencies (isDependency field is set to true). /// - /// The total amount of items. /// A paged result of objects. PagedModel GetPagedItemsWithRelations(int[] ids, long skip, long take, bool filterMustBeIsDependency) => throw new NotImplementedException(); } diff --git a/src/Umbraco.Core/Services/MemberService.cs b/src/Umbraco.Core/Services/MemberService.cs index 3e5464edd9..43b5b8f28b 100644 --- a/src/Umbraco.Core/Services/MemberService.cs +++ b/src/Umbraco.Core/Services/MemberService.cs @@ -56,7 +56,7 @@ namespace Umbraco.Cms.Core.Services /// but that is how MS have made theirs so we'll follow that principal. /// /// to count by - /// with number of Members for passed in type + /// with number of Members for passed in type public int GetCount(MemberCountType countType) { using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true); @@ -87,7 +87,7 @@ namespace Umbraco.Cms.Core.Services /// /// If no alias is supplied then the count for all Member will be returned /// Optional alias for the MemberType when counting number of Members - /// with number of Members + /// with number of Members public int Count(string? memberTypeAlias = null) { using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true); @@ -155,7 +155,6 @@ namespace Umbraco.Cms.Core.Services /// Email of the to create /// This value should be the encoded/encrypted/hashed value for the password that will be stored in the database /// Alias of the Type - /// Is the member approved /// IMember IMembershipMemberService.CreateWithIdentity(string username, string email, string passwordValue, string memberTypeAlias) => CreateMemberWithIdentity(username, email, username, passwordValue, memberTypeAlias); @@ -186,15 +185,16 @@ namespace Umbraco.Cms.Core.Services => CreateMemberWithIdentity(username, email, name, string.Empty, memberTypeAlias, isApproved); /// - /// Creates and persists a Member + /// Creates and persists a Member. /// /// Using this method will persist the Member object before its returned - /// meaning that it will have an Id available (unlike the CreateMember method) - /// Username of the Member to create - /// Email of the Member to create - /// Name of the Member to create - /// Alias of the MemberType the Member should be based on - /// Optional IsApproved of the Member to create + /// meaning that it will have an Id available (unlike the method). + /// Username of the Member to create. + /// Email of the Member to create. + /// Name of the Member to create. + /// Password value of the Member to create. + /// Alias of the MemberType the Member should be based on. + /// Optional IsApproved of the Member to create. /// public IMember CreateMemberWithIdentity(string username, string email, string name, string passwordValue, string memberTypeAlias, bool isApproved = true) { @@ -230,6 +230,7 @@ namespace Umbraco.Cms.Core.Services /// Username of the Member to create /// Email of the Member to create /// MemberType the Member should be based on + /// Is the member approved. /// public IMember CreateMemberWithIdentity(string username, string email, IMemberType memberType, bool isApproved) => CreateMemberWithIdentity(username, email, username, string.Empty, memberType, isApproved); @@ -246,6 +247,7 @@ namespace Umbraco.Cms.Core.Services /// Email of the Member to create /// Name of the Member to create /// MemberType the Member should be based on + /// Is the member approved /// public IMember CreateMemberWithIdentity(string username, string email, string name, IMemberType memberType, bool isApproved) => CreateMemberWithIdentity(username, email, name, string.Empty, memberType, isApproved); @@ -260,6 +262,7 @@ namespace Umbraco.Cms.Core.Services /// Name of the Member to create /// This value should be the encoded/encrypted/hashed value for the password that will be stored in the database /// MemberType the Member should be based on + /// Is the member approved /// private IMember CreateMemberWithIdentity(string username, string email, string name, string passwordValue, IMemberType memberType, bool isApproved = true) { @@ -296,7 +299,7 @@ namespace Umbraco.Cms.Core.Services /// /// Gets a Member by its integer id /// - /// Id + /// Id /// public IMember? GetById(int id) { @@ -357,7 +360,9 @@ namespace Umbraco.Cms.Core.Services using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true); scope.ReadLock(Constants.Locks.MemberTree); IQuery? query1 = memberTypeAlias == null ? null : Query()?.Where(x => x.ContentTypeAlias == memberTypeAlias); - IQuery? query2 = filter == null ? null : Query()?.Where(x => (x.Name != null && x.Name.Contains(filter)) || x.Username.Contains(filter) || x.Email.Contains(filter)); + int.TryParse(filter, out int filterAsIntId);//considering id,key & name as filter param + Guid.TryParse(filter, out Guid filterAsGuid); + IQuery? query2 = filter == null ? null : Query()?.Where(x => (x.Name != null && x.Name.Contains(filter)) || x.Username.Contains(filter) || x.Email.Contains(filter) || x.Id == filterAsIntId || x.Key == filterAsGuid ); return _memberRepository.GetPage(query1, pageIndex, pageSize, out totalRecords, query2, Ordering.By(orderBy, orderDirection, isCustomField: !orderBySystemField)); } @@ -580,7 +585,7 @@ namespace Umbraco.Cms.Core.Services /// Gets a list of Members based on a property search /// /// Alias of the PropertyType to search for - /// Value to match + /// Value to match /// The type of match to make as . Default is /// public IEnumerable? GetMembersByPropertyValue(string propertyTypeAlias, string value, StringPropertyMatchType matchType = StringPropertyMatchType.Exact) @@ -614,7 +619,7 @@ namespace Umbraco.Cms.Core.Services /// Gets a list of Members based on a property search /// /// Alias of the PropertyType to search for - /// Value to match + /// Value to match /// The type of match to make as . Default is /// public IEnumerable? GetMembersByPropertyValue(string propertyTypeAlias, int value, ValuePropertyMatchType matchType = ValuePropertyMatchType.Exact) @@ -651,7 +656,7 @@ namespace Umbraco.Cms.Core.Services /// Gets a list of Members based on a property search /// /// Alias of the PropertyType to search for - /// Value to match + /// Value to match /// public IEnumerable? GetMembersByPropertyValue(string propertyTypeAlias, bool value) { diff --git a/src/Umbraco.Core/Services/TagService.cs b/src/Umbraco.Core/Services/TagService.cs index c75863f6de..d2995414d3 100644 --- a/src/Umbraco.Core/Services/TagService.cs +++ b/src/Umbraco.Core/Services/TagService.cs @@ -7,7 +7,7 @@ using Umbraco.Cms.Core.Scoping; namespace Umbraco.Cms.Core.Services; /// -/// Tag service to query for tags in the tags db table. The tags returned are only relevant for published content & +/// Tag service to query for tags in the tags db table. The tags returned are only relevant for published content & /// saved media or members /// /// diff --git a/src/Umbraco.Core/Services/UserService.cs b/src/Umbraco.Core/Services/UserService.cs index 69e6351fbd..7f839e81d1 100644 --- a/src/Umbraco.Core/Services/UserService.cs +++ b/src/Umbraco.Core/Services/UserService.cs @@ -140,7 +140,7 @@ internal class UserService : RepositoryService, IUserService /// /// Gets a User by its integer id /// - /// Id + /// Id /// /// /// @@ -535,7 +535,7 @@ internal class UserService : RepositoryService, IUserService /// but that is how MS have made theirs so we'll follow that principal. /// /// to count by - /// with number of Users for passed in type + /// with number of Users for passed in type public int GetCount(MemberCountType countType) { using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) @@ -945,19 +945,12 @@ internal class UserService : RepositoryService, IUserService } /// - /// Saves a UserGroup + /// Saves a UserGroup. /// - /// UserGroup to save + /// UserGroup to save. /// /// If null than no changes are made to the users who are assigned to this group, however if a value is passed in - /// than all users will be removed from this group and only these users will be added - /// - /// Default is - /// True - /// otherwise set to - /// False - /// to not raise events - /// + /// than all users will be removed from this group and only these users will be added. public void Save(IUserGroup userGroup, int[]? userIds = null) { EventMessages evtMsgs = EventMessagesFactory.Get(); diff --git a/src/Umbraco.Core/Sync/ElectedServerRoleAccessor.cs b/src/Umbraco.Core/Sync/ElectedServerRoleAccessor.cs index 09c904b7bc..5bc37ee0ee 100644 --- a/src/Umbraco.Core/Sync/ElectedServerRoleAccessor.cs +++ b/src/Umbraco.Core/Sync/ElectedServerRoleAccessor.cs @@ -19,7 +19,6 @@ public sealed class ElectedServerRoleAccessor : IServerRoleAccessor /// Initializes a new instance of the class. /// /// The registration service. - /// Some options. public ElectedServerRoleAccessor(IServerRegistrationService registrationService) => _registrationService = registrationService ?? throw new ArgumentNullException(nameof(registrationService)); diff --git a/src/Umbraco.Core/Sync/RefreshInstruction.cs b/src/Umbraco.Core/Sync/RefreshInstruction.cs index 3fbf4bea50..5060e8854a 100644 --- a/src/Umbraco.Core/Sync/RefreshInstruction.cs +++ b/src/Umbraco.Core/Sync/RefreshInstruction.cs @@ -62,6 +62,9 @@ public class RefreshInstruction /// /// A private constructor to create a new instance /// + /// + /// + /// /// /// When the refresh method is we know how many Ids are being refreshed /// so we know the instruction diff --git a/src/Umbraco.Core/Templates/HtmlLocalLinkParser.cs b/src/Umbraco.Core/Templates/HtmlLocalLinkParser.cs index 1030705051..059f5f9cef 100644 --- a/src/Umbraco.Core/Templates/HtmlLocalLinkParser.cs +++ b/src/Umbraco.Core/Templates/HtmlLocalLinkParser.cs @@ -65,7 +65,6 @@ public sealed class HtmlLocalLinkParser /// Parses the string looking for the {localLink} syntax and updates them to their correct links. /// /// - /// /// public string EnsureInternalLinks(string text) { diff --git a/src/Umbraco.Core/Trees/ISearchableTree.cs b/src/Umbraco.Core/Trees/ISearchableTree.cs index 42883d0f87..1f24d33f66 100644 --- a/src/Umbraco.Core/Trees/ISearchableTree.cs +++ b/src/Umbraco.Core/Trees/ISearchableTree.cs @@ -16,7 +16,6 @@ public interface ISearchableTree : IDiscoverable /// /// /// - /// /// /// A starting point for the search, generally a node id, but for members this is a member type alias /// diff --git a/src/Umbraco.Core/UdiParser.cs b/src/Umbraco.Core/UdiParser.cs index 30448e1b45..1442d43eb2 100644 --- a/src/Umbraco.Core/UdiParser.cs +++ b/src/Umbraco.Core/UdiParser.cs @@ -70,13 +70,13 @@ public sealed class UdiParser /// The string to convert. /// An Udi instance that contains the value that was parsed. /// A boolean value indicating whether the string could be parsed. - public static bool TryParse(string? s, [MaybeNullWhen(false)] out T udi) - where T : Udi? + public static bool TryParse(string? s, [NotNullWhen(true)] out T? udi) + where T : Udi { var result = ParseInternal(s, true, false, out Udi? parsed); - if (result && parsed is T) + if (result && parsed is T t) { - udi = (T)parsed; + udi = t; return true; } diff --git a/src/Umbraco.Core/Web/HybridUmbracoContextAccessor.cs b/src/Umbraco.Core/Web/HybridUmbracoContextAccessor.cs index 509a746b30..910f65f4e5 100644 --- a/src/Umbraco.Core/Web/HybridUmbracoContextAccessor.cs +++ b/src/Umbraco.Core/Web/HybridUmbracoContextAccessor.cs @@ -17,7 +17,7 @@ public class HybridUmbracoContextAccessor : HybridAccessorBase, } /// - /// Tries to get the object. + /// Tries to get the object. /// public bool TryGetUmbracoContext([MaybeNullWhen(false)] out IUmbracoContext umbracoContext) { @@ -27,12 +27,12 @@ public class HybridUmbracoContextAccessor : HybridAccessorBase, } /// - /// Clears the current object. + /// Clears the current object. /// public void Clear() => Value = null; /// - /// Sets the object. + /// Sets the object. /// /// public void Set(IUmbracoContext umbracoContext) => Value = umbracoContext; diff --git a/src/Umbraco.Core/WebAssets/IRuntimeMinifier.cs b/src/Umbraco.Core/WebAssets/IRuntimeMinifier.cs index 813618738b..75505eb16e 100644 --- a/src/Umbraco.Core/WebAssets/IRuntimeMinifier.cs +++ b/src/Umbraco.Core/WebAssets/IRuntimeMinifier.cs @@ -14,6 +14,7 @@ public interface IRuntimeMinifier /// Creates a css bundle /// /// + /// /// /// /// All files must be absolute paths, relative paths will throw @@ -36,7 +37,7 @@ public interface IRuntimeMinifier /// Creates a JS bundle /// /// - /// + /// /// /// /// All files must be absolute paths, relative paths will throw diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs index 5416310566..d6531ca6e4 100644 --- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs +++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs @@ -432,6 +432,7 @@ public static partial class UmbracoBuilderExtensions builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); + builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); diff --git a/src/Umbraco.Infrastructure/Examine/Deferred/DeliveryApiContentIndexDeferredBase.cs b/src/Umbraco.Infrastructure/Examine/Deferred/DeliveryApiContentIndexDeferredBase.cs index cd9b0b85b0..b339bfa21e 100644 --- a/src/Umbraco.Infrastructure/Examine/Deferred/DeliveryApiContentIndexDeferredBase.cs +++ b/src/Umbraco.Infrastructure/Examine/Deferred/DeliveryApiContentIndexDeferredBase.cs @@ -18,6 +18,6 @@ internal abstract class DeliveryApiContentIndexDeferredBase } // NOTE: the delivery api index implementation takes care of deleting descendants, so we don't have to do that here - index.DeleteFromIndex(ids.Select(id => id.ToString())); + index.DeleteFromIndex(ids); } } diff --git a/src/Umbraco.Infrastructure/Examine/Deferred/DeliveryApiContentIndexHandlePublicAccessChanges.cs b/src/Umbraco.Infrastructure/Examine/Deferred/DeliveryApiContentIndexHandlePublicAccessChanges.cs index e5db4b6f1e..01449f37a6 100644 --- a/src/Umbraco.Infrastructure/Examine/Deferred/DeliveryApiContentIndexHandlePublicAccessChanges.cs +++ b/src/Umbraco.Infrastructure/Examine/Deferred/DeliveryApiContentIndexHandlePublicAccessChanges.cs @@ -1,5 +1,7 @@ using Examine; using Examine.Search; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Infrastructure.HostedServices; using Umbraco.Extensions; @@ -10,49 +12,126 @@ internal sealed class DeliveryApiContentIndexHandlePublicAccessChanges : Deliver { private readonly IPublicAccessService _publicAccessService; private readonly DeliveryApiIndexingHandler _deliveryApiIndexingHandler; + private readonly IDeliveryApiContentIndexHelper _deliveryApiContentIndexHelper; private readonly IBackgroundTaskQueue _backgroundTaskQueue; + private readonly DeliveryApiSettings _deliveryApiSettings; + private readonly IContentService _contentService; + private readonly IDeliveryApiContentIndexValueSetBuilder _deliveryApiContentIndexValueSetBuilder; public DeliveryApiContentIndexHandlePublicAccessChanges( IPublicAccessService publicAccessService, DeliveryApiIndexingHandler deliveryApiIndexingHandler, + IContentService contentService, + IDeliveryApiContentIndexValueSetBuilder deliveryApiContentIndexValueSetBuilder, + IDeliveryApiContentIndexHelper deliveryApiContentIndexHelper, + DeliveryApiSettings deliveryApiSettings, IBackgroundTaskQueue backgroundTaskQueue) { _publicAccessService = publicAccessService; _deliveryApiIndexingHandler = deliveryApiIndexingHandler; + _contentService = contentService; + _deliveryApiContentIndexValueSetBuilder = deliveryApiContentIndexValueSetBuilder; + _deliveryApiContentIndexHelper = deliveryApiContentIndexHelper; + _deliveryApiSettings = deliveryApiSettings; _backgroundTaskQueue = backgroundTaskQueue; } + // NOTE: at the time of implementing this, the distributed notifications for public access changes only ever + // sends out "refresh all" notifications, which means we can't be clever about minimizing the work + // effort to handle public access changes. instead we have to grab all protected content definitions + // and handle every last one with every notification. public void Execute() => _backgroundTaskQueue.QueueBackgroundWorkItem(_ => { - // NOTE: at the time of implementing this, the distributed notifications for public access changes only ever - // sends out "refresh all" notifications, which means we can't be clever about minimizing the work - // effort to handle public access changes. instead we have to grab all protected content definitions - // and handle every last one with every notification. + IIndex index = _deliveryApiIndexingHandler.GetIndex() ?? + throw new InvalidOperationException("Could not obtain the delivery API content index"); - // NOTE: eventually the Delivery API will support protected content, but for now we need to ensure that the - // index does not contain any protected content. this also means that whenever content is unprotected, - // one must trigger a manual republish of said content for it to be re-added to the index. not exactly - // an optimal solution, but it's the best we can do at this point, given the limitations outlined above - // and without prematurely assuming the future implementation details of protected content handling. - - var protectedContentIds = _publicAccessService.GetAll().Select(entry => entry.ProtectedNodeId).ToArray(); - if (protectedContentIds.Any() is false) + if (_deliveryApiSettings.MemberAuthorizationIsEnabled() is false) { + EnsureProtectedContentIsRemovedFromIndex(index); return Task.CompletedTask; } - IIndex index = _deliveryApiIndexingHandler.GetIndex() ?? - throw new InvalidOperationException("Could not obtain the delivery API content index"); + EnsureProtectedContentIsUpToDateInIndex(index); + return Task.CompletedTask; + }); + + private void EnsureProtectedContentIsRemovedFromIndex(IIndex index) + { + var protectedContentIds = _publicAccessService.GetAll().Select(entry => entry.ProtectedNodeId).ToArray(); + if (protectedContentIds.Any() is false) + { + return; + } List indexIds = FindIndexIdsForContentIds(protectedContentIds, index); if (indexIds.Any() is false) { - return Task.CompletedTask; + return; } RemoveFromIndex(indexIds, index); - return Task.CompletedTask; - }); + } + + private void EnsureProtectedContentIsUpToDateInIndex(IIndex index) + { + // first we need to re-index all the content items that are currently known to be protected in the index, + // as their protection might have been revoked or altered. + var protectedContentIdsInIndex = FindContentIdsForProtectedContent(index); + foreach (var contentId in protectedContentIdsInIndex) + { + UpdateIndex(contentId, index); + } + + // then we have to re-index any protected content items that were not part of the first operation. + var unhandledProtectedContentIds = _publicAccessService + .GetAll() + .Select(entry => entry.ProtectedNodeId) + .Except(protectedContentIdsInIndex) + .ToArray(); + + foreach (var contentId in unhandledProtectedContentIds) + { + UpdateIndexWithDescendants(contentId, index); + } + } + + private void UpdateIndexWithDescendants(int contentId, IIndex index) + { + if (UpdateIndex(contentId, index)) + { + _deliveryApiContentIndexHelper.EnumerateApplicableDescendantsForContentIndex( + contentId, + descendants => + { + foreach (IContent descendant in descendants) + { + UpdateIndex(descendant, index); + } + }); + } + } + + private bool UpdateIndex(int contentId, IIndex index) + { + IContent? content = _contentService.GetById(contentId); + return content is not null && UpdateIndex(content, index); + } + + private bool UpdateIndex(IContent content, IIndex index) + { + if (content.Trashed) + { + return false; + } + + ValueSet[] valueSets = _deliveryApiContentIndexValueSetBuilder.GetValueSets(content).ToArray(); + if (valueSets.Any()) + { + index.IndexItems(valueSets); + } + + return true; + } private List FindIndexIdsForContentIds(int[] contentIds, IIndex index) { @@ -87,4 +166,29 @@ internal sealed class DeliveryApiContentIndexHandlePublicAccessChanges : Deliver return ids; } + private int[] FindContentIdsForProtectedContent(IIndex index) + { + const int pageSize = 500; + + var ids = new List(); + + var page = 0; + var total = long.MaxValue; + + while (page * pageSize < total) + { + ISearchResults? results = index.Searcher + .CreateQuery() + .Field(UmbracoExamineFieldNames.DeliveryApiContentIndex.Protected, "y") + .SelectField(UmbracoExamineFieldNames.DeliveryApiContentIndex.Id) + .Execute(QueryOptions.SkipTake(page * pageSize, pageSize)); + total = results.TotalItemCount; + + ids.AddRange(results.Select(result => int.Parse(result[UmbracoExamineFieldNames.DeliveryApiContentIndex.Id]))); + + page++; + } + + return ids.Distinct().ToArray(); + } } diff --git a/src/Umbraco.Infrastructure/Examine/DeliveryApiContentIndexFieldDefinitionBuilder.cs b/src/Umbraco.Infrastructure/Examine/DeliveryApiContentIndexFieldDefinitionBuilder.cs index 0baaa4636d..ca9cc5e260 100644 --- a/src/Umbraco.Infrastructure/Examine/DeliveryApiContentIndexFieldDefinitionBuilder.cs +++ b/src/Umbraco.Infrastructure/Examine/DeliveryApiContentIndexFieldDefinitionBuilder.cs @@ -36,6 +36,8 @@ internal sealed class DeliveryApiContentIndexFieldDefinitionBuilder : IDeliveryA fieldDefinitions.Add(new(UmbracoExamineFieldNames.DeliveryApiContentIndex.ContentTypeId, FieldDefinitionTypes.Raw)); fieldDefinitions.Add(new(UmbracoExamineFieldNames.DeliveryApiContentIndex.Culture, FieldDefinitionTypes.Raw)); fieldDefinitions.Add(new(UmbracoExamineFieldNames.DeliveryApiContentIndex.Published, FieldDefinitionTypes.Raw)); + fieldDefinitions.Add(new(UmbracoExamineFieldNames.DeliveryApiContentIndex.Protected, FieldDefinitionTypes.Raw)); + fieldDefinitions.Add(new(UmbracoExamineFieldNames.DeliveryApiContentIndex.ProtectedAccess, FieldDefinitionTypes.Raw)); fieldDefinitions.Add(new(UmbracoExamineFieldNames.IndexPathFieldName, FieldDefinitionTypes.Raw)); fieldDefinitions.Add(new(UmbracoExamineFieldNames.NodeNameFieldName, FieldDefinitionTypes.Raw)); } diff --git a/src/Umbraco.Infrastructure/Examine/DeliveryApiContentIndexValueSetBuilder.cs b/src/Umbraco.Infrastructure/Examine/DeliveryApiContentIndexValueSetBuilder.cs index 5e4796f3b2..abf19b6bfe 100644 --- a/src/Umbraco.Infrastructure/Examine/DeliveryApiContentIndexValueSetBuilder.cs +++ b/src/Umbraco.Infrastructure/Examine/DeliveryApiContentIndexValueSetBuilder.cs @@ -1,6 +1,7 @@ using Examine; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; +using Umbraco.Cms.Core; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.DeliveryApi; using Umbraco.Cms.Core.Models; @@ -16,6 +17,7 @@ internal sealed class DeliveryApiContentIndexValueSetBuilder : IDeliveryApiConte private readonly IPublicAccessService _publicAccessService; private readonly ILogger _logger; private readonly IDeliveryApiContentIndexFieldDefinitionBuilder _deliveryApiContentIndexFieldDefinitionBuilder; + private readonly IMemberService _memberService; private DeliveryApiSettings _deliveryApiSettings; public DeliveryApiContentIndexValueSetBuilder( @@ -24,12 +26,14 @@ internal sealed class DeliveryApiContentIndexValueSetBuilder : IDeliveryApiConte IPublicAccessService publicAccessService, ILogger logger, IDeliveryApiContentIndexFieldDefinitionBuilder deliveryApiContentIndexFieldDefinitionBuilder, - IOptionsMonitor deliveryApiSettings) + IOptionsMonitor deliveryApiSettings, + IMemberService memberService) { _contentIndexHandlerCollection = contentIndexHandlerCollection; _publicAccessService = publicAccessService; _logger = logger; _deliveryApiContentIndexFieldDefinitionBuilder = deliveryApiContentIndexFieldDefinitionBuilder; + _memberService = memberService; _contentService = contentService; _deliveryApiSettings = deliveryApiSettings.CurrentValue; deliveryApiSettings.OnChange(settings => _deliveryApiSettings = settings); @@ -60,6 +64,13 @@ internal sealed class DeliveryApiContentIndexValueSetBuilder : IDeliveryApiConte [UmbracoExamineFieldNames.NodeNameFieldName] = new object[] { content.GetPublishName(culture) ?? content.GetCultureName(culture) ?? string.Empty }, // primarily needed for backoffice index browsing }; + if (_deliveryApiSettings.MemberAuthorizationIsEnabled()) + { + var protectedAccessValue = ProtectedAccessValue(content, out var isProtected); + indexValues[UmbracoExamineFieldNames.DeliveryApiContentIndex.Protected] = new object[] { isProtected ? "y" : "n" }; // required for querying protected content + indexValues[UmbracoExamineFieldNames.DeliveryApiContentIndex.ProtectedAccess] = protectedAccessValue; // required for querying protected content + } + AddContentIndexHandlerFields(content, culture, fieldDefinitions, indexValues); yield return new ValueSet(DeliveryApiContentIndexUtilites.IndexId(content, indexCulture), IndexTypes.Content, content.ContentType.Alias, indexValues); @@ -112,6 +123,38 @@ internal sealed class DeliveryApiContentIndexValueSetBuilder : IDeliveryApiConte return cultures; } + private string[] ProtectedAccessValue(IContent content, out bool isProtected) + { + PublicAccessEntry? publicAccessEntry = _publicAccessService.GetEntryForContent(content.Path); + isProtected = publicAccessEntry is not null; + + if (publicAccessEntry is null) + { + return Array.Empty(); + } + + return publicAccessEntry + .Rules + // prefix member roles with "r:" and member keys with "u:" for clarity + .Select(r => + { + if (r.RuleValue.IsNullOrWhiteSpace()) + { + return null; + } + + if (r.RuleType is Constants.Conventions.PublicAccess.MemberRoleRuleType) + { + return $"r:{r.RuleValue}"; + } + + IMember? member = _memberService.GetByUsername(r.RuleValue); + return member is not null ? $"u:{member.Key}" : null; + }) + .WhereNotNull() + .ToArray(); + } + private void AddContentIndexHandlerFields(IContent content, string? culture, FieldDefinitionCollection fieldDefinitions, Dictionary> indexValues) { foreach (IContentIndexHandler handler in _contentIndexHandlerCollection) @@ -154,8 +197,8 @@ internal sealed class DeliveryApiContentIndexValueSetBuilder : IDeliveryApiConte return false; } - // is the content protected? - if (_publicAccessService.IsProtected(content.Path).Success) + // is the content protected and Delivery API member authorization disabled? + if (_deliveryApiSettings.MemberAuthorizationIsEnabled() is false && _publicAccessService.IsProtected(content.Path).Success) { return false; } diff --git a/src/Umbraco.Infrastructure/Examine/DeliveryApiIndexingHandler.cs b/src/Umbraco.Infrastructure/Examine/DeliveryApiIndexingHandler.cs index 197ab58be0..750cb4780b 100644 --- a/src/Umbraco.Infrastructure/Examine/DeliveryApiIndexingHandler.cs +++ b/src/Umbraco.Infrastructure/Examine/DeliveryApiIndexingHandler.cs @@ -1,6 +1,8 @@ using Examine; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Services.Changes; @@ -17,6 +19,7 @@ internal sealed class DeliveryApiIndexingHandler : IDeliveryApiIndexingHandler private readonly IExamineManager _examineManager; private readonly ICoreScopeProvider _scopeProvider; private readonly ILogger _logger; + private DeliveryApiSettings _deliveryApiSettings; private readonly Lazy _enabled; // these dependencies are for the deferred handling (we don't want those handlers registered in the DI) @@ -31,6 +34,7 @@ internal sealed class DeliveryApiIndexingHandler : IDeliveryApiIndexingHandler IExamineManager examineManager, ICoreScopeProvider scopeProvider, ILogger logger, + IOptionsMonitor deliveryApiSettings, IContentService contentService, IPublicAccessService publicAccessService, IDeliveryApiContentIndexValueSetBuilder deliveryApiContentIndexValueSetBuilder, @@ -47,6 +51,8 @@ internal sealed class DeliveryApiIndexingHandler : IDeliveryApiIndexingHandler _deliveryApiContentIndexHelper = deliveryApiContentIndexHelper; _backgroundTaskQueue = backgroundTaskQueue; _enabled = new Lazy(IsEnabled); + _deliveryApiSettings = deliveryApiSettings.CurrentValue; + deliveryApiSettings.OnChange(settings => _deliveryApiSettings = settings); } /// @@ -83,6 +89,10 @@ internal sealed class DeliveryApiIndexingHandler : IDeliveryApiIndexingHandler var deferred = new DeliveryApiContentIndexHandlePublicAccessChanges( _publicAccessService, this, + _contentService, + _deliveryApiContentIndexValueSetBuilder, + _deliveryApiContentIndexHelper, + _deliveryApiSettings, _backgroundTaskQueue); Execute(deferred); } diff --git a/src/Umbraco.Infrastructure/Examine/IValueSetBuilder.cs b/src/Umbraco.Infrastructure/Examine/IValueSetBuilder.cs index d9c5fe9566..b6857b8ca7 100644 --- a/src/Umbraco.Infrastructure/Examine/IValueSetBuilder.cs +++ b/src/Umbraco.Infrastructure/Examine/IValueSetBuilder.cs @@ -3,13 +3,13 @@ using Examine; namespace Umbraco.Cms.Infrastructure.Examine; /// -/// Creates a collection of to be indexed based on a collection of +/// Creates a collection of to be indexed based on a collection of . /// /// public interface IValueSetBuilder { /// - /// Creates a collection of to be indexed based on a collection of + /// Creates a collection of to be indexed based on a collection of . /// /// /// diff --git a/src/Umbraco.Infrastructure/Examine/IndexPopulator.cs b/src/Umbraco.Infrastructure/Examine/IndexPopulator.cs index db3fe2373f..ce9ce604f4 100644 --- a/src/Umbraco.Infrastructure/Examine/IndexPopulator.cs +++ b/src/Umbraco.Infrastructure/Examine/IndexPopulator.cs @@ -4,7 +4,7 @@ using Umbraco.Cms.Core.Collections; namespace Umbraco.Cms.Infrastructure.Examine; /// -/// An that is automatically associated to any index of type +/// An that is automatically associated to any index of type /// /// public abstract class IndexPopulator : IndexPopulator diff --git a/src/Umbraco.Infrastructure/Examine/UmbracoExamineFieldNames.cs b/src/Umbraco.Infrastructure/Examine/UmbracoExamineFieldNames.cs index 5376e91897..b0cb7c73e8 100644 --- a/src/Umbraco.Infrastructure/Examine/UmbracoExamineFieldNames.cs +++ b/src/Umbraco.Infrastructure/Examine/UmbracoExamineFieldNames.cs @@ -50,5 +50,15 @@ public static class UmbracoExamineFieldNames /// Whether or not the content exists in a published state /// public const string Published = "published"; + + /// + /// Whether or not the content is protected + /// + public const string Protected = "protected"; + + /// + /// The allowed members and member roles (for protected content) + /// + public const string ProtectedAccess = "protectedAccess"; } } diff --git a/src/Umbraco.Infrastructure/HostedServices/OpenIddictCleanup.cs b/src/Umbraco.Infrastructure/HostedServices/OpenIddictCleanup.cs new file mode 100644 index 0000000000..c65f687e16 --- /dev/null +++ b/src/Umbraco.Infrastructure/HostedServices/OpenIddictCleanup.cs @@ -0,0 +1,64 @@ +using System.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using OpenIddict.Abstractions; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Services; + +namespace Umbraco.Cms.Infrastructure.HostedServices; + +// port of the OpenIddict Quartz job for cleaning up - see https://github.com/openiddict/openiddict-core/tree/dev/src/OpenIddict.Quartz +public class OpenIddictCleanup : RecurringHostedServiceBase +{ + // keep tokens and authorizations in the database for 7 days + // - NOTE: this is NOT the same as access token lifetime, which is likely very short + private const int LifespanInSeconds = 7 * 24 * 60 * 60; + + private readonly ILogger _logger; + private readonly IServiceProvider _provider; + private readonly IRuntimeState _runtimeState; + + public OpenIddictCleanup( + ILogger logger, IServiceProvider provider, IRuntimeState runtimeState) + : base(logger, TimeSpan.FromHours(1), TimeSpan.FromMinutes(5)) + { + _logger = logger; + _provider = provider; + _runtimeState = runtimeState; + } + + public override async Task PerformExecuteAsync(object? state) + { + if (_runtimeState.Level < RuntimeLevel.Run) + { + return; + } + + // hosted services are registered as singletons, but this particular one consumes scoped services... so + // we have to fetch the service dependencies manually using a new scope per invocation. + IServiceScope scope = _provider.CreateScope(); + DateTimeOffset threshold = DateTimeOffset.UtcNow - TimeSpan.FromSeconds(LifespanInSeconds); + + try + { + IOpenIddictTokenManager tokenManager = scope.ServiceProvider.GetService() + ?? throw new ConfigurationErrorsException($"Could not retrieve an {nameof(IOpenIddictTokenManager)} service from the current scope"); + await tokenManager.PruneAsync(threshold); + } + catch (Exception exception) + { + _logger.LogError(exception, "Unable to prune OpenIddict tokens"); + } + + try + { + IOpenIddictAuthorizationManager authorizationManager = scope.ServiceProvider.GetService() + ?? throw new ConfigurationErrorsException($"Could not retrieve an {nameof(IOpenIddictAuthorizationManager)} service from the current scope"); + await authorizationManager.PruneAsync(threshold); + } + catch (Exception exception) + { + _logger.LogError(exception, "Unable to prune OpenIddict authorizations"); + } + } +} diff --git a/src/Umbraco.Infrastructure/HostedServices/RecurringHostedServiceBase.cs b/src/Umbraco.Infrastructure/HostedServices/RecurringHostedServiceBase.cs index be50c069ed..c100da0ab2 100644 --- a/src/Umbraco.Infrastructure/HostedServices/RecurringHostedServiceBase.cs +++ b/src/Umbraco.Infrastructure/HostedServices/RecurringHostedServiceBase.cs @@ -12,7 +12,7 @@ namespace Umbraco.Cms.Infrastructure.HostedServices; /// Provides a base class for recurring background tasks implemented as hosted services. /// /// -/// See: https://docs.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-3.1&tabs=visual-studio#timed-background-tasks +/// See: . /// public abstract class RecurringHostedServiceBase : IHostedService, IDisposable { diff --git a/src/Umbraco.Infrastructure/Logging/Serilog/LoggerConfigExtensions.cs b/src/Umbraco.Infrastructure/Logging/Serilog/LoggerConfigExtensions.cs index 59e4eeb105..81afad16f8 100644 --- a/src/Umbraco.Infrastructure/Logging/Serilog/LoggerConfigExtensions.cs +++ b/src/Umbraco.Infrastructure/Logging/Serilog/LoggerConfigExtensions.cs @@ -124,9 +124,9 @@ namespace Umbraco.Extensions /// Outputs a .txt format log at /App_Data/Logs/ /// /// A Serilog LoggerConfiguration - /// + /// /// The log level you wish the JSON file to collect - default is Verbose (highest) - /// The number of days to keep log files. Default is set to null which means all logs are kept + /// [Obsolete("Will be removed in Umbraco 13.")] public static LoggerConfiguration OutputDefaultTextFile( this LoggerConfiguration logConfig, diff --git a/src/Umbraco.Infrastructure/Migrations/Install/DatabaseSchemaCreator.cs b/src/Umbraco.Infrastructure/Migrations/Install/DatabaseSchemaCreator.cs index b847743076..d422ea1445 100644 --- a/src/Umbraco.Infrastructure/Migrations/Install/DatabaseSchemaCreator.cs +++ b/src/Umbraco.Infrastructure/Migrations/Install/DatabaseSchemaCreator.cs @@ -520,19 +520,8 @@ public class DatabaseSchemaCreator } /// - /// Drops the table for the specified . + /// Drops the table for the specified /// - /// The type representing the DTO/table. - /// - /// - /// schemaHelper.DropTable<MyDto>); - /// - /// - /// - /// If has been decorated with an , the name from that - /// attribute will be used for the table name. If the attribute is not present, the name - /// will be used instead. - /// public void DropTable(string? tableName) { var sql = new Sql(string.Format(SqlSyntax.DropTable, SqlSyntax.GetQuotedTableName(tableName))); diff --git a/src/Umbraco.Infrastructure/Migrations/MigrationExpressionBase.cs b/src/Umbraco.Infrastructure/Migrations/MigrationExpressionBase.cs index 08b1b2b1ab..a2029cbef8 100644 --- a/src/Umbraco.Infrastructure/Migrations/MigrationExpressionBase.cs +++ b/src/Umbraco.Infrastructure/Migrations/MigrationExpressionBase.cs @@ -33,7 +33,7 @@ public abstract class MigrationExpressionBase : IMigrationExpression public List Expressions => _expressions ??= new List(); /// - /// This might be useful in the future if we add it to the interface, but for now it's used to hack the DeleteAppTables & DeleteForeignKeyExpression + /// This might be useful in the future if we add it to the interface, but for now it's used to hack the DeleteAppTables & DeleteForeignKeyExpression /// to ensure they are not executed twice. /// internal string? Name { get; set; } diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_8_0_0/FallbackLanguage.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_8_0_0/FallbackLanguage.cs index ee0d5157ae..1c1e043748 100644 --- a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_8_0_0/FallbackLanguage.cs +++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_8_0_0/FallbackLanguage.cs @@ -5,11 +5,11 @@ using Umbraco.Extensions; namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_8_0_0; -[Obsolete("This is not used anymore and will be removed in Umbraco 13")] /// /// Adds a new, self-joined field to umbracoLanguages to hold the fall-back language for /// a given language. /// +[Obsolete("This is not used anymore and will be removed in Umbraco 13")] public class FallbackLanguage : MigrationBase { public FallbackLanguage(IMigrationContext context) diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_8_0_0/UserForeignKeys.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_8_0_0/UserForeignKeys.cs index 06feca0411..2ec20c744d 100644 --- a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_8_0_0/UserForeignKeys.cs +++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_8_0_0/UserForeignKeys.cs @@ -3,10 +3,10 @@ using Umbraco.Cms.Infrastructure.Persistence.Dtos; namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_8_0_0; -[Obsolete("This is not used anymore and will be removed in Umbraco 13")] /// /// Creates/Updates non mandatory FK columns to the user table /// +[Obsolete("This is not used anymore and will be removed in Umbraco 13")] public class UserForeignKeys : MigrationBase { public UserForeignKeys(IMigrationContext context) diff --git a/src/Umbraco.Infrastructure/Models/MediaWithCrops.cs b/src/Umbraco.Infrastructure/Models/MediaWithCrops.cs index 04e1a6825d..c346eddb05 100644 --- a/src/Umbraco.Infrastructure/Models/MediaWithCrops.cs +++ b/src/Umbraco.Infrastructure/Models/MediaWithCrops.cs @@ -63,7 +63,7 @@ public class MediaWithCrops : MediaWithCrops public new T Content { get; } /// - /// Performs an implicit conversion from to . + /// Performs an implicit conversion from to . /// /// The media with crops. /// diff --git a/src/Umbraco.Infrastructure/ModelsBuilder/Building/TextBuilder.cs b/src/Umbraco.Infrastructure/ModelsBuilder/Building/TextBuilder.cs index 0fa866ec23..22160b0ef4 100644 --- a/src/Umbraco.Infrastructure/ModelsBuilder/Building/TextBuilder.cs +++ b/src/Umbraco.Infrastructure/ModelsBuilder/Building/TextBuilder.cs @@ -35,6 +35,7 @@ public class TextBuilder : Builder /// Initializes a new instance of the class with a list of models to generate /// and the result of code parsing. /// + /// The models builder configuration. /// The list of models to generate. public TextBuilder(ModelsBuilderSettings config, IList typeModels) : base(config, typeModels) diff --git a/src/Umbraco.Infrastructure/Persistence/DatabaseProviderMetadataExtensions.cs b/src/Umbraco.Infrastructure/Persistence/DatabaseProviderMetadataExtensions.cs index 09c0a121dc..c00f5a226e 100644 --- a/src/Umbraco.Infrastructure/Persistence/DatabaseProviderMetadataExtensions.cs +++ b/src/Umbraco.Infrastructure/Persistence/DatabaseProviderMetadataExtensions.cs @@ -1,3 +1,4 @@ +using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Install.Models; using Umbraco.Cms.Infrastructure.Persistence.SqlSyntax; diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityRepositoryBase.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityRepositoryBase.cs index 611d89b6cf..25d53e00ae 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityRepositoryBase.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityRepositoryBase.cs @@ -37,7 +37,7 @@ public abstract class EntityRepositoryBase : RepositoryBase, IRead protected ILogger> Logger { get; } /// - /// Gets the isolated cache for the + /// Gets the isolated cache for the /// protected IAppPolicyCache GlobalIsolatedCache => AppCaches.IsolatedCaches.GetOrCreate(); @@ -185,7 +185,7 @@ public abstract class EntityRepositoryBase : RepositoryBase, IRead => PerformCount(query); /// - /// Get the entity id for the + /// Get the entity id for the . /// protected virtual TId GetEntityId(TEntity entity) => (TId)(object)entity.Id; diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/PermissionRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/PermissionRepository.cs index 85a168997d..79be0f93b0 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/PermissionRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/PermissionRepository.cs @@ -17,7 +17,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement; /// /// /// -/// This repo implements the base class so that permissions can be +/// This repo implements the base class so that permissions can be /// queued to be persisted /// like the normal repository pattern but the standard repository Get commands don't apply and will throw /// diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RedirectUrlRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RedirectUrlRepository.cs index 1c2da42561..7e4c56f117 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RedirectUrlRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RedirectUrlRepository.cs @@ -1,5 +1,4 @@ using System.Security.Cryptography; -using System.Threading.Tasks; using Microsoft.Extensions.Logging; using NPoco; using Umbraco.Cms.Core; @@ -44,7 +43,7 @@ internal class RedirectUrlRepository : EntityRepositoryBase, return dto == null ? null : Map(dto); } - public async Task GetMostRecentUrlAsync(string url) + public async Task GetMostRecentUrlAsync(string url) { Sql sql = GetMostRecentSql(url); List dtos = await Database.FetchAsync(sql); @@ -71,7 +70,7 @@ internal class RedirectUrlRepository : EntityRepositoryBase, Sql sql = GetMostRecentUrlSql(url, culture); List dtos = Database.Fetch(sql); - RedirectUrlDto? dto = dtos.FirstOrDefault(f => f.Culture == culture.ToLower()); + RedirectUrlDto? dto = dtos.FirstOrDefault(f => culture.InvariantEquals(f.Culture)); if (dto == null) { @@ -102,7 +101,7 @@ internal class RedirectUrlRepository : EntityRepositoryBase, Sql sql = GetMostRecentUrlSql(url, culture); List dtos = await Database.FetchAsync(sql); - RedirectUrlDto? dto = dtos.FirstOrDefault(f => f.Culture == culture.ToLower()); + RedirectUrlDto? dto = dtos.FirstOrDefault(f => culture.InvariantEquals(f.Culture)); if (dto == null) { diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserRepository.cs index cf2329570e..eda072f049 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserRepository.cs @@ -145,7 +145,7 @@ internal class UserRepository : EntityRepositoryBase, IUserRepositor /// /// /// - /// Can be used for slightly faster user lookups if the result doesn't require security data (i.e. groups, apps & start nodes). + /// Can be used for slightly faster user lookups if the result doesn't require security data (i.e. groups, apps & start nodes). /// This is really only used for a shim in order to upgrade to 7.6. /// /// @@ -160,7 +160,7 @@ internal class UserRepository : EntityRepositoryBase, IUserRepositor /// /// /// This is really only used for a shim in order to upgrade to 7.6 but could be used - /// for slightly faster user lookups if the result doesn't require security data (i.e. groups, apps & start nodes) + /// for slightly faster user lookups if the result doesn't require security data (i.e. groups, apps & start nodes) /// /// /// A non cached instance diff --git a/src/Umbraco.Infrastructure/Persistence/UmbracoDatabase.cs b/src/Umbraco.Infrastructure/Persistence/UmbracoDatabase.cs index 4c88e7659a..af8eb8e1fe 100644 --- a/src/Umbraco.Infrastructure/Persistence/UmbracoDatabase.cs +++ b/src/Umbraco.Infrastructure/Persistence/UmbracoDatabase.cs @@ -390,7 +390,7 @@ public class UmbracoDatabase : Database, IUmbracoDatabase public new T ExecuteScalar(string sql, params object[] args) => ExecuteScalar(new Sql(sql, args)); - /// + /// public new T ExecuteScalar(Sql sql) => ExecuteScalar(sql.SQL, CommandType.Text, sql.Arguments); diff --git a/src/Umbraco.Infrastructure/Persistence/UmbracoDatabaseExtensions.cs b/src/Umbraco.Infrastructure/Persistence/UmbracoDatabaseExtensions.cs index 78bcc34f2b..3ba2f88daf 100644 --- a/src/Umbraco.Infrastructure/Persistence/UmbracoDatabaseExtensions.cs +++ b/src/Umbraco.Infrastructure/Persistence/UmbracoDatabaseExtensions.cs @@ -2,6 +2,7 @@ using NPoco; using Umbraco.Cms.Core.Persistence; using Umbraco.Cms.Infrastructure.Persistence.Dtos; using Umbraco.Cms.Infrastructure.Persistence.SqlSyntax; +using Umbraco.Cms.Infrastructure.Runtime; using Umbraco.Extensions; namespace Umbraco.Cms.Infrastructure.Persistence; @@ -21,7 +22,7 @@ internal static class UmbracoDatabaseExtensions /// /// Gets a dictionary of key/values directly from the database, no scope, nothing. /// - /// Used by to determine the runtime state. + /// Used by to determine the runtime state. public static IReadOnlyDictionary? GetFromKeyValueTable( this IUmbracoDatabase? database, string keyPrefix) diff --git a/src/Umbraco.Infrastructure/PropertyEditors/BlockEditorPropertyValueEditor.cs b/src/Umbraco.Infrastructure/PropertyEditors/BlockEditorPropertyValueEditor.cs index fbf2239828..c524c2c39b 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/BlockEditorPropertyValueEditor.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/BlockEditorPropertyValueEditor.cs @@ -119,7 +119,6 @@ internal abstract class BlockEditorPropertyValueEditor : DataValueEditor, IDataV /// Ensure that sub-editor values are translated through their ToEditor methods /// /// - /// /// /// /// diff --git a/src/Umbraco.Infrastructure/PropertyEditors/ImageCropperConfiguration.cs b/src/Umbraco.Infrastructure/PropertyEditors/ImageCropperConfiguration.cs index 3714a965e5..0f844b94b8 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/ImageCropperConfiguration.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/ImageCropperConfiguration.cs @@ -33,6 +33,7 @@ internal static class ImageCropperConfigurationExtensions /// /// Applies the configuration to ensure only valid crops are kept and have the correct width/height. /// + /// /// The configuration. public static void ApplyConfiguration(this ImageCropperValue imageCropperValue, ImageCropperConfiguration? configuration) { diff --git a/src/Umbraco.Infrastructure/Security/BackOfficeIdentityUser.cs b/src/Umbraco.Infrastructure/Security/BackOfficeIdentityUser.cs index b617ce5a05..008178cc19 100644 --- a/src/Umbraco.Infrastructure/Security/BackOfficeIdentityUser.cs +++ b/src/Umbraco.Infrastructure/Security/BackOfficeIdentityUser.cs @@ -108,7 +108,11 @@ public class BackOfficeIdentityUser : UmbracoIdentityUser /// /// Used to construct a new instance without an identity /// + /// + /// /// This is allowed to be null (but would need to be filled in if trying to persist this instance) + /// + /// public static BackOfficeIdentityUser CreateNew(GlobalSettings globalSettings, string? username, string email, string culture, string? name = null) { if (string.IsNullOrWhiteSpace(username)) diff --git a/src/Umbraco.Infrastructure/Security/DeleteExternalLoginsOnMemberDeletedHandler.cs b/src/Umbraco.Infrastructure/Security/DeleteExternalLoginsOnMemberDeletedHandler.cs index 4294287041..fc066e7796 100644 --- a/src/Umbraco.Infrastructure/Security/DeleteExternalLoginsOnMemberDeletedHandler.cs +++ b/src/Umbraco.Infrastructure/Security/DeleteExternalLoginsOnMemberDeletedHandler.cs @@ -14,7 +14,7 @@ public class DeleteExternalLoginsOnMemberDeletedHandler : INotificationHandler - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// public DeleteExternalLoginsOnMemberDeletedHandler(IExternalLoginWithKeyService externalLoginWithKeyService) => _externalLoginWithKeyService = externalLoginWithKeyService; diff --git a/src/Umbraco.Infrastructure/Security/IMemberApplicationManager.cs b/src/Umbraco.Infrastructure/Security/IMemberApplicationManager.cs new file mode 100644 index 0000000000..6d576d0f09 --- /dev/null +++ b/src/Umbraco.Infrastructure/Security/IMemberApplicationManager.cs @@ -0,0 +1,8 @@ +namespace Umbraco.Cms.Infrastructure.Security; + +public interface IMemberApplicationManager +{ + Task EnsureMemberApplicationAsync(IEnumerable loginRedirectUrls, IEnumerable logoutRedirectUrls, CancellationToken cancellationToken = default); + + Task DeleteMemberApplicationAsync(CancellationToken cancellationToken = default); +} diff --git a/src/Umbraco.Infrastructure/Security/IUmbracoUserManager.cs b/src/Umbraco.Infrastructure/Security/IUmbracoUserManager.cs index 714db070bb..1cbd8eb51f 100644 --- a/src/Umbraco.Infrastructure/Security/IUmbracoUserManager.cs +++ b/src/Umbraco.Infrastructure/Security/IUmbracoUserManager.cs @@ -18,7 +18,7 @@ public interface IUmbracoUserManager : IDisposable Task GetUserIdAsync(TUser user); /// - /// Get the from a + /// Get the from a /// /// The /// A representing the result of the asynchronous operation. diff --git a/src/Umbraco.Infrastructure/Security/OpenIdDictApplicationManagerBase.cs b/src/Umbraco.Infrastructure/Security/OpenIdDictApplicationManagerBase.cs new file mode 100644 index 0000000000..7376859055 --- /dev/null +++ b/src/Umbraco.Infrastructure/Security/OpenIdDictApplicationManagerBase.cs @@ -0,0 +1,37 @@ +using OpenIddict.Abstractions; + +namespace Umbraco.Cms.Infrastructure.Security; + +public abstract class OpenIdDictApplicationManagerBase +{ + private readonly IOpenIddictApplicationManager _applicationManager; + + protected OpenIdDictApplicationManagerBase(IOpenIddictApplicationManager applicationManager) + => _applicationManager = applicationManager; + + protected async Task CreateOrUpdate(OpenIddictApplicationDescriptor clientDescriptor, CancellationToken cancellationToken) + { + var identifier = clientDescriptor.ClientId ?? + throw new ApplicationException($"ClientId is missing for application: {clientDescriptor.DisplayName ?? "(no name)"}"); + var client = await _applicationManager.FindByClientIdAsync(identifier, cancellationToken); + if (client is null) + { + await _applicationManager.CreateAsync(clientDescriptor, cancellationToken); + } + else + { + await _applicationManager.UpdateAsync(client, clientDescriptor, cancellationToken); + } + } + + protected async Task Delete(string identifier, CancellationToken cancellationToken) + { + var client = await _applicationManager.FindByClientIdAsync(identifier, cancellationToken); + if (client is null) + { + return; + } + + await _applicationManager.DeleteAsync(client, cancellationToken); + } +} diff --git a/src/Umbraco.Infrastructure/Services/CacheInstructionService.cs b/src/Umbraco.Infrastructure/Services/CacheInstructionService.cs index 3d7c751c58..2458705309 100644 --- a/src/Umbraco.Infrastructure/Services/CacheInstructionService.cs +++ b/src/Umbraco.Infrastructure/Services/CacheInstructionService.cs @@ -338,9 +338,14 @@ namespace Umbraco.Cms /// /// Processes the instruction batch and checks for errors. /// + /// + /// + /// /// /// Tracks which instructions have already been processed to avoid duplicates /// + /// + /// /// /// Returns true if all instructions in the batch were processed, otherwise false if they could not be due to the app being shut down /// diff --git a/src/Umbraco.Infrastructure/Umbraco.Infrastructure.csproj b/src/Umbraco.Infrastructure/Umbraco.Infrastructure.csproj index d4c4afcf71..0c1a5b8d1c 100644 --- a/src/Umbraco.Infrastructure/Umbraco.Infrastructure.csproj +++ b/src/Umbraco.Infrastructure/Umbraco.Infrastructure.csproj @@ -26,6 +26,8 @@ + + diff --git a/src/Umbraco.Web.BackOffice/Authorization/UserGroupHandler.cs b/src/Umbraco.Web.BackOffice/Authorization/UserGroupHandler.cs index 595dcf8663..82a01ab73e 100644 --- a/src/Umbraco.Web.BackOffice/Authorization/UserGroupHandler.cs +++ b/src/Umbraco.Web.BackOffice/Authorization/UserGroupHandler.cs @@ -36,6 +36,7 @@ public class UserGroupHandler : MustSatisfyRequirementAuthorizationHandlerService for media related operations. /// Service for entity related operations. /// Accessor for back-office security. + /// App caches. public UserGroupHandler( IHttpContextAccessor httpContextAccessor, IUserService userService, diff --git a/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs b/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs index cce95fd609..fe105bee67 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs @@ -653,7 +653,6 @@ public class AuthenticationController : UmbracoApiControllerBase /// Return the for the given /// /// - /// /// private UserDetail? GetUserDetail(IUser? user) { diff --git a/src/Umbraco.Web.BackOffice/Controllers/CodeFileController.cs b/src/Umbraco.Web.BackOffice/Controllers/CodeFileController.cs index 28a6012901..0e4cc3de6d 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/CodeFileController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/CodeFileController.cs @@ -633,7 +633,7 @@ public class CodeFileController : BackOfficeNotificationsController /// The style sheet data /// The style sheet combined from the CSS and the rules /// - /// Any "umbraco style rules" in the CSS will be removed and replaced with the rules passed in + /// Any "umbraco style rules" in the CSS will be removed and replaced with the rules passed in /// public string? PostInterpolateStylesheetRules(StylesheetData data) { diff --git a/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs b/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs index 1d899bb3fe..d8c306bff4 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs @@ -1962,7 +1962,7 @@ public class ContentController : ContentControllerBase /// /// /// - /// The culture used in the localization message, null by default which means will be used. + /// The culture used in the localization message, null by default which means will be used. /// private void AddVariantValidationError(string? culture, string? segment, string localizationArea, string localizationAlias, string? cultureToken = null) { diff --git a/src/Umbraco.Web.BackOffice/Controllers/ContentControllerBase.cs b/src/Umbraco.Web.BackOffice/Controllers/ContentControllerBase.cs index 36a60843fb..e97d7dd055 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/ContentControllerBase.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/ContentControllerBase.cs @@ -72,7 +72,6 @@ public abstract class ContentControllerBase : BackOfficeNotificationsController /// Handles if the content for the specified ID isn't found /// /// The content ID to find - /// Whether to throw an exception /// The error response protected NotFoundObjectResult HandleContentNotFound(object id) { diff --git a/src/Umbraco.Web.BackOffice/Controllers/EntityController.cs b/src/Umbraco.Web.BackOffice/Controllers/EntityController.cs index 036397cb4d..fedf8ceba4 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/EntityController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/EntityController.cs @@ -1251,7 +1251,7 @@ public class EntityController : UmbracoAuthorizedJsonController /// /// The type of entity. /// - /// Optional filter - Format like: "BoolVariable==true&IntVariable>=6". Invalid filters are + /// Optional filter - Format like: "BoolVariable==true&IntVariable>=6". Invalid filters are /// ignored. /// /// diff --git a/src/Umbraco.Web.BackOffice/Controllers/ImagesController.cs b/src/Umbraco.Web.BackOffice/Controllers/ImagesController.cs index 787aa0070c..90ef6e6cf4 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/ImagesController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/ImagesController.cs @@ -149,6 +149,11 @@ public class ImagesController : UmbracoAuthorizedApiController /// /// /// + /// + /// + /// + /// + /// /// /// /// If there is no media, image property or image file is found then this will return not found. diff --git a/src/Umbraco.Web.BackOffice/Controllers/LogViewerController.cs b/src/Umbraco.Web.BackOffice/Controllers/LogViewerController.cs index 74615e1fa8..c0a6f0312c 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/LogViewerController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/LogViewerController.cs @@ -13,7 +13,7 @@ using Umbraco.Cms.Web.Common.Authorization; namespace Umbraco.Cms.Web.BackOffice.Controllers; /// -/// Backoffice controller supporting the dashboard for viewing logs with some simple graphs & filtering +/// Backoffice controller supporting the dashboard for viewing logs with some simple graphs & filtering /// [PluginController(Constants.Web.Mvc.BackOfficeApiArea)] [Authorize(Policy = AuthorizationPolicies.SectionAccessSettings)] diff --git a/src/Umbraco.Web.BackOffice/Controllers/MacroRenderingController.cs b/src/Umbraco.Web.BackOffice/Controllers/MacroRenderingController.cs index b2261acb2f..efa322a88c 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/MacroRenderingController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/MacroRenderingController.cs @@ -80,7 +80,7 @@ public class MacroRenderingController : UmbracoAuthorizedJsonController /// /// /// To send a dictionary as a GET parameter the query should be structured like: - /// ?macroAlias=Test&pageId=3634¯oParams[0].key=myKey¯oParams[0].value=myVal¯oParams[1].key=anotherKey + /// ?macroAlias=Test&pageId=3634&macroParams[0].key=myKey&macroParams[0].value=myVal&macroParams[1].key=anotherKey /// ¯oParams[1].value=anotherVal /// /// diff --git a/src/Umbraco.Web.BackOffice/Controllers/MacrosController.cs b/src/Umbraco.Web.BackOffice/Controllers/MacrosController.cs index 34a28dd874..799b97144d 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/MacrosController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/MacrosController.cs @@ -286,7 +286,7 @@ public class MacrosController : BackOfficeNotificationsController /// Finds all the macro partials /// /// - /// The . + /// The . /// private IEnumerable FindPartialViewsFiles() { @@ -302,7 +302,7 @@ public class MacrosController : BackOfficeNotificationsController /// Finds all macro partials in the views folder /// /// - /// The . + /// The . /// private IEnumerable FindPartialViewFilesInViewsFolder() { @@ -369,7 +369,7 @@ public class MacrosController : BackOfficeNotificationsController /// The prefix virtual path. /// /// - /// The . + /// The . /// private IEnumerable FindPartialViewFilesInFolder(string orgPath, string path, string prefixVirtualPath) { diff --git a/src/Umbraco.Web.BackOffice/Controllers/MediaController.cs b/src/Umbraco.Web.BackOffice/Controllers/MediaController.cs index 1230774e92..9c37bb103b 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/MediaController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/MediaController.cs @@ -184,13 +184,12 @@ public class MediaController : ContentControllerBase return NotFound(); } - IMedia emptyContent = _mediaService.CreateMedia("", parentId, contentType.Alias, - _backofficeSecurityAccessor.BackOfficeSecurity?.GetUserId().Result ?? -1); + IMedia emptyContent = _mediaService.CreateMedia("", parentId, contentType.Alias, _backofficeSecurityAccessor.BackOfficeSecurity?.GetUserId().Result ?? -1); MediaItemDisplay? mapped = _umbracoMapper.Map(emptyContent); if (mapped is not null) { - //remove the listview app if it exists + // remove the listview app if it exists mapped.ContentApps = mapped.ContentApps.Where(x => x.Alias != "umbListView").ToList(); } @@ -205,8 +204,7 @@ public class MediaController : ContentControllerBase { var apps = new List { - ListViewContentAppFactory.CreateContentApp(_dataTypeService, _propertyEditors, "recycleBin", "media", - Constants.DataTypes.DefaultMediaListView) + ListViewContentAppFactory.CreateContentApp(_dataTypeService, _propertyEditors, "recycleBin", "media", Constants.DataTypes.DefaultMediaListView) }; apps[0].Active = true; var display = new MediaItemDisplay @@ -239,7 +237,8 @@ public class MediaController : ContentControllerBase if (foundMedia == null) { HandleContentNotFound(id); - //HandleContentNotFound will throw an exception + + // HandleContentNotFound will throw an exception return null; } @@ -304,11 +303,10 @@ public class MediaController : ContentControllerBase /// /// /// - public PagedResult> GetChildFolders(int id, int pageNumber = 1, - int pageSize = 1000) + public PagedResult> GetChildFolders(int id, int pageNumber = 1, int pageSize = 1000) { - //Suggested convention for folder mediatypes - we can make this more or less complicated as long as we document it... - //if you create a media type, which has an alias that ends with ...Folder then its a folder: ex: "secureFolder", "bannerFolder", "Folder" + // Suggested convention for folder mediatypes - we can make this more or less complicated as long as we document it... + // if you create a media type, which has an alias that ends with ...Folder then its a folder: ex: "secureFolder", "bannerFolder", "Folder" var folderTypes = _mediaTypeService .GetAll() .Where(x => x.Alias.EndsWith("Folder")) @@ -321,7 +319,8 @@ public class MediaController : ContentControllerBase } IEnumerable children = _mediaService.GetPagedChildren(id, pageNumber - 1, pageSize, out long total, - //lookup these content types + + // lookup these content types _sqlContext.Query().Where(x => folderTypes.Contains(x.ContentTypeId)), Ordering.By("Name")); @@ -337,6 +336,7 @@ public class MediaController : ContentControllerBase /// [FilterAllowedOutgoingMedia(typeof(IEnumerable>))] public IEnumerable> GetRootMedia() => + // TODO: Add permissions check! _mediaService.GetRootMedia()? .Select(_umbracoMapper.Map>).WhereNotNull() ?? @@ -358,7 +358,7 @@ public class MediaController : ContentControllerBase return HandleContentNotFound(id); } - //if the current item is in the recycle bin + // if the current item is in the recycle bin if (foundMedia.Trashed == false) { Attempt moveResult = _mediaService.MoveToRecycleBin(foundMedia, @@ -390,8 +390,10 @@ public class MediaController : ContentControllerBase { // Authorize... var requirement = new MediaPermissionsResourceRequirement(); - AuthorizationResult authorizationResult = await _authorizationService.AuthorizeAsync(User, - new MediaPermissionsResource(_mediaService.GetById(move.Id)), requirement); + AuthorizationResult authorizationResult = await _authorizationService.AuthorizeAsync( + User, + new MediaPermissionsResource(_mediaService.GetById(move.Id)), + requirement); if (!authorizationResult.Succeeded) { return Forbid(); @@ -404,18 +406,19 @@ public class MediaController : ContentControllerBase return convertToActionResult.Convert(); } - var destinationParentID = move.ParentId; - var sourceParentID = toMove?.ParentId; + var destinationParentId = move.ParentId; + var sourceParentId = toMove?.ParentId; var moveResult = toMove is null ? false - : _mediaService.Move(toMove, move.ParentId, - _backofficeSecurityAccessor.BackOfficeSecurity?.GetUserId().Result ?? -1); + : _mediaService.Move(toMove, move.ParentId, _backofficeSecurityAccessor.BackOfficeSecurity?.GetUserId().Result ?? -1); - if (sourceParentID == destinationParentID) + if (sourceParentId == destinationParentId) { - return ValidationProblem(new SimpleNotificationModel(new BackOfficeNotification("", - _localizedTextService.Localize("media", "moveToSameFolderFailed"), NotificationStyle.Error))); + return ValidationProblem(new SimpleNotificationModel(new BackOfficeNotification( + string.Empty, + _localizedTextService.Localize("media", "moveToSameFolderFailed"), + NotificationStyle.Error))); } if (moveResult == false) @@ -436,9 +439,9 @@ public class MediaController : ContentControllerBase public ActionResult? PostSave( [ModelBinder(typeof(MediaItemBinder))] MediaItemSave contentItem) { - //Recent versions of IE/Edge may send in the full client side file path instead of just the file name. - //To ensure similar behavior across all browsers no matter what they do - we strip the FileName property of all - //uploaded files to being *only* the actual file name (as it should be). + // Recent versions of IE/Edge may send in the full client side file path instead of just the file name. + // To ensure similar behavior across all browsers no matter what they do - we strip the FileName property of all + // uploaded files to being *only* the actual file name (as it should be). if (contentItem.UploadedFiles != null && contentItem.UploadedFiles.Any()) { foreach (ContentPropertyFile file in contentItem.UploadedFiles) @@ -447,14 +450,14 @@ public class MediaController : ContentControllerBase } } - //If we've reached here it means: + // If we've reached here it means: // * Our model has been bound // * and validated // * any file attachments have been saved to their temporary location for us to use // * we have a reference to the DTO object and the persisted object // * Permissions are valid - //Don't update the name if it is empty + // Don't update the name if it is empty if (contentItem.Name.IsNullOrWhiteSpace() == false && contentItem.PersistedContent is not null) { contentItem.PersistedContent.Name = contentItem.Name; @@ -467,14 +470,14 @@ public class MediaController : ContentControllerBase (save, property, v) => property?.SetValue(v), //set prop val null); // media are all invariant - //we will continue to save if model state is invalid, however we cannot save if critical data is missing. - //TODO: Allowing media to be saved when it is invalid is odd - media doesn't have a publish phase so suddenly invalid data is allowed to be 'live' + // we will continue to save if model state is invalid, however we cannot save if critical data is missing. + // TODO: Allowing media to be saved when it is invalid is odd - media doesn't have a publish phase so suddenly invalid data is allowed to be 'live' if (!ModelState.IsValid) { - //check for critical data validation issues, we can't continue saving if this data is invalid + // check for critical data validation issues, we can't continue saving if this data is invalid if (!RequiredForPersistenceAttribute.HasRequiredValuesForPersistence(contentItem)) { - //ok, so the absolute mandatory data is invalid and it's new, we cannot actually continue! + // ok, so the absolute mandatory data is invalid and it's new, we cannot actually continue! // add the model state to the outgoing object and throw validation response MediaItemDisplay? forDisplay = _umbracoMapper.Map(contentItem.PersistedContent); return ValidationProblem(forDisplay, ModelState); @@ -486,20 +489,19 @@ public class MediaController : ContentControllerBase return null; } - //save the item - Attempt saveStatus = _mediaService.Save(contentItem.PersistedContent, - _backofficeSecurityAccessor.BackOfficeSecurity?.GetUserId().Result ?? -1); + // save the item + Attempt saveStatus = _mediaService.Save(contentItem.PersistedContent, _backofficeSecurityAccessor.BackOfficeSecurity?.GetUserId().Result ?? -1); - //return the updated model + // return the updated model MediaItemDisplay? display = _umbracoMapper.Map(contentItem.PersistedContent); - //lastly, if it is not valid, add the model state to the outgoing object and throw a 403 + // lastly, if it is not valid, add the model state to the outgoing object and throw a 403 if (!ModelState.IsValid) { return ValidationProblem(display, ModelState, StatusCodes.Status403Forbidden); } - //put the correct msgs in + // put the correct msgs in switch (contentItem.Action) { case ContentSaveAction.Save: @@ -514,7 +516,7 @@ public class MediaController : ContentControllerBase { AddCancelMessage(display); - //If the item is new and the operation was cancelled, we need to return a different + // If the item is new and the operation was cancelled, we need to return a different // status code so the UI can handle it since it won't be able to redirect since there // is no Id to redirect to! if (saveStatus.Result?.Result == OperationResultType.FailedCancelledByEvent && @@ -555,7 +557,7 @@ public class MediaController : ContentControllerBase return NotFound(); } - //if there's nothing to sort just return ok + // if there's nothing to sort just return ok if (sorted.IdSortOrder?.Length == 0) { return Ok(); @@ -564,8 +566,7 @@ public class MediaController : ContentControllerBase // Authorize... var requirement = new MediaPermissionsResourceRequirement(); var resource = new MediaPermissionsResource(sorted.ParentId); - AuthorizationResult authorizationResult = - await _authorizationService.AuthorizeAsync(User, resource, requirement); + AuthorizationResult authorizationResult = await _authorizationService.AuthorizeAsync(User, resource, requirement); if (!authorizationResult.Succeeded) { return Forbid(); @@ -596,7 +597,7 @@ public class MediaController : ContentControllerBase public async Task> PostAddFolder(PostedFolder folder) { ActionResult? parentIdResult = await GetParentIdAsIntAsync(folder.ParentId, true); - if (!(parentIdResult?.Result is null)) + if (parentIdResult?.Result is not null) { return new ActionResult(parentIdResult.Result); } @@ -633,19 +634,20 @@ public class MediaController : ContentControllerBase try { var root = _hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.TempFileUploads); - //ensure it exists + + // ensure it exists Directory.CreateDirectory(root); - //must have a file + // must have a file if (file is null || file.Count == 0) { _postAddFileSemaphore.Release(); return NotFound("No file was uploaded"); } - //get the string json from the request + // get the string json from the request ActionResult? parentIdResult = await GetParentIdAsIntAsync(currentFolder, true); - if (!(parentIdResult?.Result is null)) + if (parentIdResult?.Result is not null) { _postAddFileSemaphore.Release(); return parentIdResult.Result; @@ -660,7 +662,7 @@ public class MediaController : ContentControllerBase var tempFiles = new PostedFiles(); - //in case we pass a path with a folder in it, we will create it and upload media to it. + // in case we pass a path with a folder in it, we will create it and upload media to it. if (!string.IsNullOrEmpty(path)) { if (!IsFolderCreationAllowedHere(parentId.Value)) @@ -677,16 +679,16 @@ public class MediaController : ContentControllerBase var folderName = folders[i]; IMedia? folderMediaItem; - //if uploading directly to media root and not a subfolder + // if uploading directly to media root and not a subfolder if (parentId == Constants.System.Root) { - //look for matching folder + // look for matching folder folderMediaItem = _mediaService.GetRootMedia()?.FirstOrDefault(x => x.Name == folderName && x.ContentType.Alias == Constants.Conventions.MediaTypes.Folder); if (folderMediaItem == null) { - //if null, create a folder + // if null, create a folder folderMediaItem = _mediaService.CreateMedia(folderName, -1, Constants.Conventions.MediaTypes.Folder); _mediaService.Save(folderMediaItem); @@ -694,10 +696,10 @@ public class MediaController : ContentControllerBase } else { - //get current parent + // get current parent IMedia? mediaRoot = _mediaService.GetById(parentId.Value); - //if the media root is null, something went wrong, we'll abort + // if the media root is null, something went wrong, we'll abort if (mediaRoot == null) { _postAddFileSemaphore.Release(); @@ -706,19 +708,18 @@ public class MediaController : ContentControllerBase " returned null"); } - //look for matching folder + // look for matching folder folderMediaItem = FindInChildren(mediaRoot.Id, folderName, Constants.Conventions.MediaTypes.Folder); if (folderMediaItem == null) { - //if null, create a folder - folderMediaItem = _mediaService.CreateMedia(folderName, mediaRoot, - Constants.Conventions.MediaTypes.Folder); + // if null, create a folder + folderMediaItem = _mediaService.CreateMedia(folderName, mediaRoot, Constants.Conventions.MediaTypes.Folder); _mediaService.Save(folderMediaItem); } } - //set the media root to the folder id so uploaded files will end there. + // set the media root to the folder id so uploaded files will end there. parentId = folderMediaItem.Id; } } @@ -758,7 +759,7 @@ public class MediaController : ContentControllerBase } } - //Only set the permission-based mediaType if we only allow 1 specific file under this parent. + // Only set the permission-based mediaType if we only allow 1 specific file under this parent. if (allowedContentTypes.Count == 1 && mediaTypeItem != null) { mediaTypeAlias = mediaTypeItem.Alias; @@ -771,7 +772,7 @@ public class MediaController : ContentControllerBase allowedContentTypes.UnionWith(typesAllowedAtRoot); } - //get the files + // get the files foreach (IFormFile formFile in file) { var fileName = formFile.FileName.Trim(Constants.CharArrays.DoubleQuote).TrimEnd(); @@ -787,16 +788,16 @@ public class MediaController : ContentControllerBase continue; } - using var stream = new MemoryStream(); - await formFile.CopyToAsync(stream); - if (_fileStreamSecurityValidator != null && _fileStreamSecurityValidator.IsConsideredSafe(stream) == false) - { - tempFiles.Notifications.Add(new BackOfficeNotification( - _localizedTextService.Localize("speechBubbles", "operationFailedHeader"), - _localizedTextService.Localize("media", "fileSecurityValidationFailure"), - NotificationStyle.Warning)); - continue; - } + using var stream = new MemoryStream(); + await formFile.CopyToAsync(stream); + if (_fileStreamSecurityValidator != null && _fileStreamSecurityValidator.IsConsideredSafe(stream) == false) + { + tempFiles.Notifications.Add(new BackOfficeNotification( + _localizedTextService.Localize("speechBubbles", "operationFailedHeader"), + _localizedTextService.Localize("media", "fileSecurityValidationFailure"), + NotificationStyle.Warning)); + continue; + } if (string.IsNullOrEmpty(mediaTypeAlias)) { @@ -830,29 +831,34 @@ public class MediaController : ContentControllerBase continue; } + if (allowedContentTypes.Any(x => x.Alias == mediaTypeItem.Alias) == false) + { + continue; + } + mediaTypeAlias = mediaTypeItem.Alias; break; } - // If media type is still File then let's check if it's an imageor a custom image type. + // If media type is still File then let's check if it's an image or a custom image type. if (mediaTypeAlias == Constants.Conventions.MediaTypes.File && _imageUrlGenerator.IsSupportedImageFormat(ext)) { if (allowedContentTypes.Any(mt => mt.Alias == Constants.Conventions.MediaTypes.Image)) - { - mediaTypeAlias = Constants.Conventions.MediaTypes.Image; - } - else - { - IMediaType? customType = allowedContentTypes.FirstOrDefault(mt => - mt.CompositionPropertyTypes.Any(pt => - pt.PropertyEditorAlias == Constants.PropertyEditors.Aliases.ImageCropper)); - - if (customType is not null) { - mediaTypeAlias = customType.Alias; + mediaTypeAlias = Constants.Conventions.MediaTypes.Image; + } + else + { + IMediaType? customType = allowedContentTypes.FirstOrDefault(mt => + mt.CompositionPropertyTypes.Any(pt => + pt.PropertyEditorAlias == Constants.PropertyEditors.Aliases.ImageCropper)); + + if (customType is not null) + { + mediaTypeAlias = customType.Alias; + } } - } } } else @@ -872,31 +878,28 @@ public class MediaController : ContentControllerBase var mediaItemName = fileName.ToFriendlyName(); - IMedia createdMediaItem = _mediaService.CreateMedia(mediaItemName, parentId.Value, mediaTypeAlias, - _backofficeSecurityAccessor.BackOfficeSecurity?.CurrentUser?.Id ?? -1); + IMedia createdMediaItem = _mediaService.CreateMedia(mediaItemName, parentId.Value, mediaTypeAlias, _backofficeSecurityAccessor.BackOfficeSecurity?.CurrentUser?.Id ?? -1); + createdMediaItem.SetValue(_mediaFileManager, _mediaUrlGenerators, _shortStringHelper, _contentTypeBaseServiceProvider, Constants.Conventions.Media.File, fileName, stream); - - createdMediaItem.SetValue(_mediaFileManager, _mediaUrlGenerators, _shortStringHelper, - _contentTypeBaseServiceProvider, Constants.Conventions.Media.File, fileName, stream); - - - Attempt saveResult = _mediaService.Save(createdMediaItem, + Attempt saveResult = _mediaService.Save( + createdMediaItem, _backofficeSecurityAccessor.BackOfficeSecurity?.CurrentUser?.Id ?? -1); if (saveResult == false) { - AddCancelMessage(tempFiles, + AddCancelMessage( + tempFiles, _localizedTextService.Localize("speechBubbles", "operationCancelledText") + " -- " + mediaItemName); } } - //Different response if this is a 'blueimp' request + // Different response if this is a 'blueimp' request if (HttpContext.Request.Query.Any(x => x.Key == "origin")) { KeyValuePair origin = HttpContext.Request.Query.First(x => x.Key == "origin"); if (origin.Value == "blueimp") { _postAddFileSemaphore.Release(); - return new JsonResult(tempFiles); //Don't output the angular xsrf stuff, blue imp doesn't like that + return new JsonResult(tempFiles); // Don't output the angular xsrf stuff, blue imp doesn't like that } } @@ -943,7 +946,11 @@ public class MediaController : ContentControllerBase var total = long.MaxValue; while (page * pageSize < total) { - IEnumerable children = _mediaService.GetPagedChildren(mediaId, page++, pageSize, out total, + IEnumerable children = _mediaService.GetPagedChildren( + mediaId, + page++, + pageSize, + out total, _sqlContext.Query().Where(x => x.Name == nameToFind)); IMedia? match = children.FirstOrDefault(c => c.ContentType.Alias == contentTypeAlias); if (match != null) @@ -973,7 +980,7 @@ public class MediaController : ContentControllerBase parentId = parentUdi?.Guid.ToString(); } - //if it's not an INT then we'll check for GUID + // if it's not an INT then we'll check for GUID if (int.TryParse(parentId, NumberStyles.Integer, CultureInfo.InvariantCulture, out int intParentId) == false) { // if a guid then try to look up the entity @@ -997,7 +1004,7 @@ public class MediaController : ContentControllerBase } // Authorize... - //ensure the user has access to this folder by parent id! + // ensure the user has access to this folder by parent id! if (validatePermissions) { var requirement = new MediaPermissionsResourceRequirement(); @@ -1038,14 +1045,13 @@ public class MediaController : ContentControllerBase if (model.ParentId < 0) { - //cannot move if the content item is not allowed at the root unless there are - //none allowed at root (in which case all should be allowed at root) + // cannot move if the content item is not allowed at the root unless there are + // none allowed at root (in which case all should be allowed at root) IMediaTypeService mediaTypeService = _mediaTypeService; if (toMove.ContentType.AllowedAsRoot == false && mediaTypeService.GetAll().Any(ct => ct.AllowedAsRoot)) { var notificationModel = new SimpleNotificationModel(); - notificationModel.AddErrorNotification(_localizedTextService.Localize("moveOrCopy", "notAllowedAtRoot"), - ""); + notificationModel.AddErrorNotification(_localizedTextService.Localize("moveOrCopy", "notAllowedAtRoot"), string.Empty); return ValidationProblem(notificationModel); } } @@ -1057,7 +1063,7 @@ public class MediaController : ContentControllerBase return NotFound(); } - //check if the item is allowed under this one + // check if the item is allowed under this one IMediaType? parentContentType = _mediaTypeService.Get(parent.ContentTypeId); if (parentContentType?.AllowedContentTypes?.Select(x => x.Id).ToArray() .Any(x => x.Value == toMove.ContentType.Id) == false) @@ -1069,12 +1075,11 @@ public class MediaController : ContentControllerBase } // Check on paths - if (string.Format(",{0},", parent.Path) - .IndexOf(string.Format(",{0},", toMove.Id), StringComparison.Ordinal) > -1) + if ($",{parent.Path}," + .IndexOf($",{toMove.Id},", StringComparison.Ordinal) > -1) { var notificationModel = new SimpleNotificationModel(); - notificationModel.AddErrorNotification(_localizedTextService.Localize("moveOrCopy", "notAllowedByPath"), - ""); + notificationModel.AddErrorNotification(_localizedTextService.Localize("moveOrCopy", "notAllowedByPath"), string.Empty); return ValidationProblem(notificationModel); } } @@ -1099,7 +1104,8 @@ public class MediaController : ContentControllerBase /// Returns the child media objects - using the entity INT id /// [FilterAllowedOutgoingMedia(typeof(IEnumerable>), "Items")] - public PagedResult> GetChildren(int id, + public PagedResult> GetChildren( + int id, int pageNumber = 0, int pageSize = 0, string orderBy = "SortOrder", @@ -1107,7 +1113,7 @@ public class MediaController : ContentControllerBase bool orderBySystemField = true, string filter = "") { - //if a request is made for the root node data but the user's start node is not the default, then + // if a request is made for the root node data but the user's start node is not the default, then // we need to return their start nodes if (id == Constants.System.Root && UserStartNodes.Length > 0 && UserStartNodes.Contains(Constants.System.Root) == false) @@ -1137,7 +1143,6 @@ public class MediaController : ContentControllerBase } // else proceed as usual - long totalChildren; List children; if (pageNumber > 0 && pageSize > 0) @@ -1145,22 +1150,27 @@ public class MediaController : ContentControllerBase IQuery? queryFilter = null; if (filter.IsNullOrWhiteSpace() == false) { - //add the default text filter + int.TryParse(filter, out int filterAsIntId); + Guid.TryParse(filter, out Guid filterAsGuid); + // add the default text filter queryFilter = _sqlContext.Query() .Where(x => x.Name != null) - .Where(x => x.Name!.Contains(filter)); + .Where(x => x.Name!.Contains(filter) + || x.Id == filterAsIntId || x.Key == filterAsGuid); } children = _mediaService .GetPagedChildren( - id, pageNumber - 1, pageSize, + id, + pageNumber - 1, + pageSize, out totalChildren, queryFilter, Ordering.By(orderBy, orderDirection, isCustomField: !orderBySystemField)).ToList(); } else { - //better to not use this without paging where possible, currently only the sort dialog does + // better to not use this without paging where possible, currently only the sort dialog does children = _mediaService.GetPagedChildren(id, 0, int.MaxValue, out var total).ToList(); totalChildren = children.Count; } @@ -1234,8 +1244,7 @@ public class MediaController : ContentControllerBase IEntitySlim? entity = _entityService.Get(guidUdi.Guid); if (entity != null) { - return GetChildren(entity.Id, pageNumber, pageSize, orderBy, orderDirection, orderBySystemField, - filter); + return GetChildren(entity.Id, pageNumber, pageSize, orderBy, orderDirection, orderBySystemField, filter); } } diff --git a/src/Umbraco.Web.BackOffice/Controllers/MediaTypeController.cs b/src/Umbraco.Web.BackOffice/Controllers/MediaTypeController.cs index 09b24b11e4..6377d119c3 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/MediaTypeController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/MediaTypeController.cs @@ -135,7 +135,7 @@ public class MediaTypeController : ContentTypeControllerBase /// /// Returns a media type by alias /// - /// /// Alias of the media type + /// Alias of the media type /// public IEnumerable GetAllFiltered([FromQuery] string[] aliases) { diff --git a/src/Umbraco.Web.BackOffice/Extensions/HtmlHelperBackOfficeExtensions.cs b/src/Umbraco.Web.BackOffice/Extensions/HtmlHelperBackOfficeExtensions.cs index 1773969563..41ecde4760 100644 --- a/src/Umbraco.Web.BackOffice/Extensions/HtmlHelperBackOfficeExtensions.cs +++ b/src/Umbraco.Web.BackOffice/Extensions/HtmlHelperBackOfficeExtensions.cs @@ -16,17 +16,7 @@ public static class HtmlHelperBackOfficeExtensions /// Outputs a script tag containing the bare minimum (non secure) server vars for use with the angular app /// /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// + /// /// /// /// These are the bare minimal server variables that are required for the application to start without being @@ -52,6 +42,7 @@ public static class HtmlHelperBackOfficeExtensions /// /// /// + /// /// public static async Task AngularValueExternalLoginInfoScriptAsync(this IHtmlHelper html, IBackOfficeExternalLoginProviders externalLogins, diff --git a/src/Umbraco.Web.BackOffice/Extensions/ModelStateExtensions.cs b/src/Umbraco.Web.BackOffice/Extensions/ModelStateExtensions.cs index 915d94a68b..1b8effa5cd 100644 --- a/src/Umbraco.Web.BackOffice/Extensions/ModelStateExtensions.cs +++ b/src/Umbraco.Web.BackOffice/Extensions/ModelStateExtensions.cs @@ -36,6 +36,7 @@ public static class ModelStateExtensions /// /// /// The culture for the property, if the property is invariant than this is empty + /// internal static void AddPropertyError(this ModelStateDictionary modelState, ValidationResult result, string propertyAlias, string culture = "", string segment = "") => modelState.AddPropertyValidationError(new ContentPropertyValidationResult(result, culture, segment), diff --git a/src/Umbraco.Web.BackOffice/Filters/ContentSaveValidationAttribute.cs b/src/Umbraco.Web.BackOffice/Filters/ContentSaveValidationAttribute.cs index fb8629c461..003b6676fe 100644 --- a/src/Umbraco.Web.BackOffice/Filters/ContentSaveValidationAttribute.cs +++ b/src/Umbraco.Web.BackOffice/Filters/ContentSaveValidationAttribute.cs @@ -1,4 +1,4 @@ -using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.Extensions.Logging; @@ -131,7 +131,6 @@ internal sealed class ContentSaveValidationAttribute : TypeFilterAttribute /// /// /// - /// private async Task ValidateUserAccessAsync( ContentItemSave? contentItem, ActionExecutingContext actionContext) diff --git a/src/Umbraco.Web.BackOffice/Install/CreateUnattendedUserNotificationHandler.cs b/src/Umbraco.Web.BackOffice/Install/CreateUnattendedUserNotificationHandler.cs index 1ac7f20819..11648a2618 100644 --- a/src/Umbraco.Web.BackOffice/Install/CreateUnattendedUserNotificationHandler.cs +++ b/src/Umbraco.Web.BackOffice/Install/CreateUnattendedUserNotificationHandler.cs @@ -30,6 +30,7 @@ public class CreateUnattendedUserNotificationHandler : INotificationAsyncHandler /// Listening for when the UnattendedInstallNotification fired after a sucessfulk /// /// + /// public async Task HandleAsync(UnattendedInstallNotification notification, CancellationToken cancellationToken) { UnattendedSettings? unattendedSettings = _unattendedSettings.Value; diff --git a/src/Umbraco.Web.BackOffice/Security/IBackOfficeAntiforgery.cs b/src/Umbraco.Web.BackOffice/Security/IBackOfficeAntiforgery.cs index c4e0c1d91c..594ab55576 100644 --- a/src/Umbraco.Web.BackOffice/Security/IBackOfficeAntiforgery.cs +++ b/src/Umbraco.Web.BackOffice/Security/IBackOfficeAntiforgery.cs @@ -11,8 +11,7 @@ public interface IBackOfficeAntiforgery /// /// Validates the headers/cookies passed in for the request /// - /// - /// + /// /// Task> ValidateRequestAsync(HttpContext httpContext); @@ -20,7 +19,5 @@ public interface IBackOfficeAntiforgery /// Generates tokens to use for the cookie and header antiforgery values /// /// - /// - /// void GetAndStoreTokens(HttpContext httpContext); } diff --git a/src/Umbraco.Web.BackOffice/Security/IBackOfficeExternalLoginProviders.cs b/src/Umbraco.Web.BackOffice/Security/IBackOfficeExternalLoginProviders.cs index 78ae41d66e..6d0a699f9a 100644 --- a/src/Umbraco.Web.BackOffice/Security/IBackOfficeExternalLoginProviders.cs +++ b/src/Umbraco.Web.BackOffice/Security/IBackOfficeExternalLoginProviders.cs @@ -22,7 +22,6 @@ public interface IBackOfficeExternalLoginProviders /// Returns the authentication type for the last registered external login (oauth) provider that specifies an /// auto-login redirect option /// - /// /// string? GetAutoLoginProvider(); diff --git a/src/Umbraco.Web.BackOffice/Security/PasswordChanger.cs b/src/Umbraco.Web.BackOffice/Security/PasswordChanger.cs index 8b74f6d2c3..db6832db39 100644 --- a/src/Umbraco.Web.BackOffice/Security/PasswordChanger.cs +++ b/src/Umbraco.Web.BackOffice/Security/PasswordChanger.cs @@ -17,7 +17,7 @@ internal class PasswordChanger : IPasswordChanger where TUser : Um private readonly ILogger> _logger; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// Password changing functionality /// /// Logger for this class diff --git a/src/Umbraco.Web.BackOffice/Trees/ApplicationTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/ApplicationTreeController.cs index 461d1fc82f..817f742137 100644 --- a/src/Umbraco.Web.BackOffice/Trees/ApplicationTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/ApplicationTreeController.cs @@ -32,6 +32,7 @@ public class ApplicationTreeController : UmbracoAuthorizedApiController private readonly IControllerFactory _controllerFactory; private readonly IActionDescriptorCollectionProvider _actionDescriptorCollectionProvider; + /// /// Initializes a new instance of the class. /// public ApplicationTreeController( diff --git a/src/Umbraco.Web.BackOffice/Trees/MediaTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/MediaTreeController.cs index 00b62a17f7..0fbf606db8 100644 --- a/src/Umbraco.Web.BackOffice/Trees/MediaTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/MediaTreeController.cs @@ -84,7 +84,7 @@ public class MediaTreeController : ContentTreeControllerBase, ISearchableTree, I /// /// Creates a tree node for a content item based on an UmbracoEntity /// - /// + /// /// /// /// diff --git a/src/Umbraco.Web.Common/Extensions/FormCollectionExtensions.cs b/src/Umbraco.Web.Common/Extensions/FormCollectionExtensions.cs index e8961dba0a..dbf0d17143 100644 --- a/src/Umbraco.Web.Common/Extensions/FormCollectionExtensions.cs +++ b/src/Umbraco.Web.Common/Extensions/FormCollectionExtensions.cs @@ -9,7 +9,7 @@ public static class FormCollectionExtensions { /// /// Converts a dictionary object to a query string representation such as: - /// firstname=shannon&lastname=deminick + /// firstname=shannon&lastname=deminick /// /// /// Any keys found in this collection will be removed from the output diff --git a/src/Umbraco.Web.Common/Extensions/UmbracoApplicationBuilder.Identity.cs b/src/Umbraco.Web.Common/Extensions/UmbracoApplicationBuilder.Identity.cs index ec3f0f5055..0c9f56a010 100644 --- a/src/Umbraco.Web.Common/Extensions/UmbracoApplicationBuilder.Identity.cs +++ b/src/Umbraco.Web.Common/Extensions/UmbracoApplicationBuilder.Identity.cs @@ -3,6 +3,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Security; +using Umbraco.Cms.Web.Common.Security; namespace Umbraco.Extensions; @@ -39,6 +40,17 @@ public static partial class UmbracoApplicationBuilderExtensions return builder; } + public static IUmbracoBuilder SetMemberSignInManager(this IUmbracoBuilder builder) + where TSignInManager : SignInManager, IMemberSignInManager + { + Type customType = typeof(TSignInManager); + Type signInManagerType = typeof(SignInManager); + builder.Services.Replace(ServiceDescriptor.Scoped(typeof(IMemberSignInManager), customType)); + builder.Services.AddScoped(customType, services => services.GetRequiredService(signInManagerType)); + builder.Services.Replace(ServiceDescriptor.Scoped(signInManagerType, customType)); + return builder; + } + public static IUmbracoBuilder SetMemberUserStore(this IUmbracoBuilder builder) where TUserStore : MemberUserStore { diff --git a/src/Umbraco.Web.Common/FileProviders/WebRootFileProviderFactory.cs b/src/Umbraco.Web.Common/FileProviders/WebRootFileProviderFactory.cs index 64824dd090..c2fec29b8e 100644 --- a/src/Umbraco.Web.Common/FileProviders/WebRootFileProviderFactory.cs +++ b/src/Umbraco.Web.Common/FileProviders/WebRootFileProviderFactory.cs @@ -18,7 +18,7 @@ public class WebRootFileProviderFactory : IManifestFileProviderFactory, IGridEdi } /// - /// Creates a new instance, pointing at . + /// Creates a new instance, pointing at . /// /// /// The newly created instance. diff --git a/src/Umbraco.Web.Common/Filters/ValidateUmbracoFormRouteStringAttribute.cs b/src/Umbraco.Web.Common/Filters/ValidateUmbracoFormRouteStringAttribute.cs index 791dfc0c12..cdf3d8c135 100644 --- a/src/Umbraco.Web.Common/Filters/ValidateUmbracoFormRouteStringAttribute.cs +++ b/src/Umbraco.Web.Common/Filters/ValidateUmbracoFormRouteStringAttribute.cs @@ -13,8 +13,8 @@ namespace Umbraco.Cms.Web.Common.Filters; /// Attribute used to check that the request contains a valid Umbraco form request string. /// /// - /// Applying this attribute/filter to a or SurfaceController Action will ensure that the Action can only be executed - /// when it is routed to from within Umbraco, typically when rendering a form with BeginUmbracoForm. It will mean that the natural MVC route for this Action +/// Applying this attribute/filter to a or SurfaceController Action will ensure that the Action can only be executed +/// when it is routed to from within Umbraco, typically when rendering a form with BeginUmbracoForm. It will mean that the natural MVC route for this Action /// will fail with a . /// public class ValidateUmbracoFormRouteStringAttribute : TypeFilterAttribute diff --git a/src/Umbraco.Web.Common/ModelsBuilder/DependencyInjection/UmbracoBuilderDependencyInjectionExtensions.cs b/src/Umbraco.Web.Common/ModelsBuilder/DependencyInjection/UmbracoBuilderDependencyInjectionExtensions.cs index 818e51aada..20cd35d0d1 100644 --- a/src/Umbraco.Web.Common/ModelsBuilder/DependencyInjection/UmbracoBuilderDependencyInjectionExtensions.cs +++ b/src/Umbraco.Web.Common/ModelsBuilder/DependencyInjection/UmbracoBuilderDependencyInjectionExtensions.cs @@ -114,8 +114,6 @@ public static class UmbracoBuilderDependencyInjectionExtensions builder.AddInMemoryModelsRazorEngine(); builder.AddNotificationHandler(); - builder.AddNotificationHandler(); - builder.AddNotificationHandler(); } if (builder.Config.GetRuntimeMode() != RuntimeMode.Production) @@ -128,12 +126,16 @@ public static class UmbracoBuilderDependencyInjectionExtensions builder.AddNotificationHandler(); builder.AddNotificationHandler(); builder.AddNotificationHandler(); + + builder.AddNotificationHandler(); + builder.AddNotificationHandler(); } builder.Services.TryAddSingleton(); // Register required services for ModelsBuilderDashboardController builder.Services.AddSingleton(); + // TODO: Remove in v13 - this is only here in case someone is already using this generator directly builder.Services.AddSingleton(); builder.Services.AddSingleton(); @@ -163,6 +165,7 @@ public static class UmbracoBuilderDependencyInjectionExtensions // This is what the community MB would replace, all of the above services are fine to be registered builder.Services.AddSingleton(factory => factory.CreateDefaultPublishedModelFactory()); + return builder; } } diff --git a/src/Umbraco.Web.Common/Security/IMemberSignInManager.cs b/src/Umbraco.Web.Common/Security/IMemberSignInManager.cs index 27d034930d..aed070333c 100644 --- a/src/Umbraco.Web.Common/Security/IMemberSignInManager.cs +++ b/src/Umbraco.Web.Common/Security/IMemberSignInManager.cs @@ -1,3 +1,4 @@ +using System.Security.Claims; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Identity; using Umbraco.Cms.Core.Security; @@ -13,7 +14,7 @@ public interface IMemberSignInManager Task SignOutAsync(); - AuthenticationProperties ConfigureExternalAuthenticationProperties(string provider, string redirectUrl, string? userId = null); + AuthenticationProperties ConfigureExternalAuthenticationProperties(string? provider, string? redirectUrl, string? userId = null); Task GetExternalLoginInfoAsync(string? expectedXsrf = null); @@ -24,4 +25,6 @@ public interface IMemberSignInManager Task GetTwoFactorAuthenticationUserAsync(); Task TwoFactorSignInAsync(string provider, string code, bool isPersistent, bool rememberClient); + + Task CreateUserPrincipalAsync(MemberIdentityUser user) => Task.FromResult(new ClaimsPrincipal()); } diff --git a/src/Umbraco.Web.Common/Security/PublicAccessChecker.cs b/src/Umbraco.Web.Common/Security/PublicAccessChecker.cs index 9777f56d7d..6bb78b7d81 100644 --- a/src/Umbraco.Web.Common/Security/PublicAccessChecker.cs +++ b/src/Umbraco.Web.Common/Security/PublicAccessChecker.cs @@ -1,3 +1,4 @@ +using System.Security.Claims; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Core.Security; @@ -19,16 +20,25 @@ public class PublicAccessChecker : IPublicAccessChecker _contentService = contentService; } + /// public async Task HasMemberAccessToContentAsync(int publishedContentId) + => await HasMemberAccessToContentAsync(publishedContentId, httpContext => httpContext.User); + + /// + public async Task HasMemberAccessToContentAsync(int publishedContentId, ClaimsPrincipal claimsPrincipal) + => await HasMemberAccessToContentAsync(publishedContentId, _ => claimsPrincipal); + + private async Task HasMemberAccessToContentAsync(int publishedContentId, Func getClaimsPrincipal) { HttpContext httpContext = _httpContextAccessor.GetRequiredHttpContext(); IMemberManager memberManager = httpContext.RequestServices.GetRequiredService(); - if (httpContext.User.Identity == null || !httpContext.User.Identity.IsAuthenticated) + ClaimsPrincipal claimsPrincipal = getClaimsPrincipal(httpContext); + if (claimsPrincipal.Identity is not { IsAuthenticated: true }) { return PublicAccessStatus.NotLoggedIn; } - MemberIdentityUser? currentMember = await memberManager.GetUserAsync(httpContext.User); + MemberIdentityUser? currentMember = await memberManager.GetUserAsync(claimsPrincipal); if (currentMember == null) { return PublicAccessStatus.NotLoggedIn; diff --git a/src/Umbraco.Web.UI.Client/package-lock.json b/src/Umbraco.Web.UI.Client/package-lock.json index 3a5f6175d2..1b74a78ced 100644 --- a/src/Umbraco.Web.UI.Client/package-lock.json +++ b/src/Umbraco.Web.UI.Client/package-lock.json @@ -6,10 +6,10 @@ "": { "name": "ui", "dependencies": { - "@microsoft/signalr": "7.0.7", - "@umbraco-ui/uui": "1.3.0", - "@umbraco-ui/uui-css": "1.3.0", - "ace-builds": "1.22.1", + "@microsoft/signalr": "7.0.12", + "@umbraco-ui/uui": "1.4.0", + "@umbraco-ui/uui-css": "1.4.0", + "ace-builds": "1.30.0", "angular": "1.8.3", "angular-animate": "1.8.3", "angular-aria": "1.8.3", @@ -31,7 +31,7 @@ "diff": "5.1.0", "flatpickr": "4.6.13", "font-awesome": "4.7.0", - "jquery": "3.7.0", + "jquery": "3.7.1", "jquery-ui-dist": "1.13.2", "jquery-ui-touch-punch": "0.2.3", "lazyload-js": "1.0.0", @@ -39,7 +39,7 @@ "ng-file-upload": "12.2.13", "nouislider": "15.7.1", "spectrum-colorpicker2": "2.0.10", - "tinymce": "6.5.1", + "tinymce": "6.7.0", "typeahead.js": "0.11.1", "underscore": "1.13.6", "wicg-inert": "3.1.2" @@ -47,16 +47,16 @@ "devDependencies": { "@babel/core": "7.21.8", "@babel/preset-env": "7.21.5", - "autoprefixer": "10.4.14", + "autoprefixer": "10.4.16", "cssnano": "6.0.1", - "eslint": "8.43.0", + "eslint": "8.51.0", "gulp": "4.0.2", "gulp-angular-embed-templates": "2.3.0", "gulp-babel": "8.0.0", "gulp-clean-css": "4.3.0", "gulp-cli": "2.3.0", "gulp-concat": "2.6.1", - "gulp-eslint-new": "1.8.1", + "gulp-eslint-new": "1.8.4", "gulp-imagemin": "7.1.0", "gulp-less": "5.0.0", "gulp-minify": "3.1.0", @@ -68,17 +68,17 @@ "gulp-watch": "5.0.1", "gulp-wrap": "0.15.0", "gulp-wrap-js": "0.4.1", - "jasmine-core": "5.0.1", - "jsdom": "21.1.2", + "jasmine-core": "5.1.1", + "jsdom": "22.1.0", "karma": "6.4.2", "karma-jasmine": "5.1.0", - "karma-jsdom-launcher": "14.0.0", + "karma-jsdom-launcher": "15.0.0", "karma-junit-reporter": "2.0.1", "karma-spec-reporter": "0.0.36", - "less": "4.1.3", + "less": "4.2.0", "lodash": "4.17.21", "merge-stream": "2.0.0", - "postcss": "8.4.24", + "postcss": "8.4.31", "run-sequence": "2.2.1" }, "engines": { @@ -86,6 +86,15 @@ "npm": ">=9.5" } }, + "node_modules/@aashutoshrathi/word-wrap": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", + "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/@ampproject/remapping": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", @@ -100,21 +109,22 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.5.tgz", - "integrity": "sha512-Xmwn266vad+6DAqEB2A6V/CcZVp62BbwVmcOJc2RPuwih1kw02TjQvWVWlcKGbBPd+8/0V5DEkOcizRGYsspYQ==", + "version": "7.22.13", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", + "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", "dev": true, "dependencies": { - "@babel/highlight": "^7.22.5" + "@babel/highlight": "^7.22.13", + "chalk": "^2.4.2" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/compat-data": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.22.5.tgz", - "integrity": "sha512-4Jc/YuIaYqKnDDz892kPIledykKg12Aw1PYX5i/TY28anJtacvM1Rrr8wbieB9GfEJwlzqT0hUEao0CxEebiDA==", + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.2.tgz", + "integrity": "sha512-0S9TQMmDHlqAZ2ITT95irXKfxN9bncq8ZCoJhun3nHL/lLUxd2NKBJYoNGWH7S0hz6fRQwWlAWn/ILM0C70KZQ==", "dev": true, "engines": { "node": ">=6.9.0" @@ -151,21 +161,21 @@ } }, "node_modules/@babel/core/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, "bin": { "semver": "bin/semver.js" } }, "node_modules/@babel/generator": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.5.tgz", - "integrity": "sha512-+lcUbnTRhd0jOewtFSedLyiPsD5tswKkbgcezOqqWFUVNEwoUTlpPOBmvhG7OXWLR4jMdv0czPGH5XbflnD1EA==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz", + "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==", "dev": true, "dependencies": { - "@babel/types": "^7.22.5", + "@babel/types": "^7.23.0", "@jridgewell/gen-mapping": "^0.3.2", "@jridgewell/trace-mapping": "^0.3.17", "jsesc": "^2.5.1" @@ -199,22 +209,19 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.5.tgz", - "integrity": "sha512-Ji+ywpHeuqxB8WDxraCiqR0xfhYjiDE/e6k7FuIaANnoOFxAHskHChz4vA1mJC9Lbm01s1PVAGhQY4FUKSkGZw==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.15.tgz", + "integrity": "sha512-y6EEzULok0Qvz8yyLkCvVX+02ic+By2UdOhylwUOvOn9dvYc9mKICJuuU1n1XBI02YWsNsnrY1kc6DVbjcXbtw==", "dev": true, "dependencies": { - "@babel/compat-data": "^7.22.5", - "@babel/helper-validator-option": "^7.22.5", - "browserslist": "^4.21.3", + "@babel/compat-data": "^7.22.9", + "@babel/helper-validator-option": "^7.22.15", + "browserslist": "^4.21.9", "lru-cache": "^5.1.1", - "semver": "^6.3.0" + "semver": "^6.3.1" }, "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" } }, "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { @@ -227,9 +234,9 @@ } }, "node_modules/@babel/helper-compilation-targets/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, "bin": { "semver": "bin/semver.js" @@ -242,20 +249,20 @@ "dev": true }, "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.22.5.tgz", - "integrity": "sha512-xkb58MyOYIslxu3gKmVXmjTtUPvBU4odYzbiIQbWwLKIHCsx6UGZGX6F1IznMFVnDdirseUZopzN+ZRt8Xb33Q==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.22.15.tgz", + "integrity": "sha512-jKkwA59IXcvSaiK2UN45kKwSC9o+KuoXsBDvHvU/7BecYIp8GQ2UwrVvFgJASUT+hBnwJx6MhvMCuMzwZZ7jlg==", "dev": true, "dependencies": { "@babel/helper-annotate-as-pure": "^7.22.5", "@babel/helper-environment-visitor": "^7.22.5", "@babel/helper-function-name": "^7.22.5", - "@babel/helper-member-expression-to-functions": "^7.22.5", + "@babel/helper-member-expression-to-functions": "^7.22.15", "@babel/helper-optimise-call-expression": "^7.22.5", - "@babel/helper-replace-supers": "^7.22.5", + "@babel/helper-replace-supers": "^7.22.9", "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.5", - "semver": "^6.3.0" + "@babel/helper-split-export-declaration": "^7.22.6", + "semver": "^6.3.1" }, "engines": { "node": ">=6.9.0" @@ -265,9 +272,9 @@ } }, "node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, "bin": { "semver": "bin/semver.js" @@ -317,31 +324,31 @@ } }, "node_modules/@babel/helper-define-polyfill-provider/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, "bin": { "semver": "bin/semver.js" } }, "node_modules/@babel/helper-environment-visitor": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.5.tgz", - "integrity": "sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-function-name": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz", - "integrity": "sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", "dev": true, "dependencies": { - "@babel/template": "^7.22.5", - "@babel/types": "^7.22.5" + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" }, "engines": { "node": ">=6.9.0" @@ -360,46 +367,46 @@ } }, "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.22.5.tgz", - "integrity": "sha512-aBiH1NKMG0H2cGZqspNvsaBe6wNGjbJjuLy29aU+eDZjSbbN53BaxlpB02xm9v34pLTZ1nIQPFYn2qMZoa5BQQ==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.23.0.tgz", + "integrity": "sha512-6gfrPwh7OuT6gZyJZvd6WbTfrqAo7vm4xCzAXOusKqq/vWdKXphTpj5klHKNmRUU6/QRGlBsyU9mAIPaWHlqJA==", "dev": true, "dependencies": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.23.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-imports": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.5.tgz", - "integrity": "sha512-8Dl6+HD/cKifutF5qGd/8ZJi84QeAKh+CEe1sBzz8UayBBGg1dAIJrdHOcOM5b2MpzWL2yuotJTtGjETq0qjXg==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz", + "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==", "dev": true, "dependencies": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.22.15" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.22.5.tgz", - "integrity": "sha512-+hGKDt/Ze8GFExiVHno/2dvG5IdstpzCq0y4Qc9OJ25D4q3pKfiIP/4Vp3/JvhDkLKsDK2api3q3fpIgiIF5bw==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.0.tgz", + "integrity": "sha512-WhDWw1tdrlT0gMgUJSlX0IQvoO1eN279zrAUbVB+KpV2c3Tylz8+GnKOLllCS6Z/iZQEyVYxhZVUdPTqs2YYPw==", "dev": true, "dependencies": { - "@babel/helper-environment-visitor": "^7.22.5", - "@babel/helper-module-imports": "^7.22.5", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-module-imports": "^7.22.15", "@babel/helper-simple-access": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.5", - "@babel/helper-validator-identifier": "^7.22.5", - "@babel/template": "^7.22.5", - "@babel/traverse": "^7.22.5", - "@babel/types": "^7.22.5" + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/helper-validator-identifier": "^7.22.20" }, "engines": { "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } }, "node_modules/@babel/helper-optimise-call-expression": { @@ -424,15 +431,14 @@ } }, "node_modules/@babel/helper-remap-async-to-generator": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.5.tgz", - "integrity": "sha512-cU0Sq1Rf4Z55fgz7haOakIyM7+x/uCFwXpLPaeRzfoUtAEAuUZjZvFPjL/rk5rW693dIgn2hng1W7xbT7lWT4g==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.20.tgz", + "integrity": "sha512-pBGyV4uBqOns+0UvhsTO8qgl8hO89PmiDYv+/COyp1aeMcmfrfruz+/nCMFiYyFF/Knn0yfrC85ZzNFjembFTw==", "dev": true, "dependencies": { "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-environment-visitor": "^7.22.5", - "@babel/helper-wrap-function": "^7.22.5", - "@babel/types": "^7.22.5" + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-wrap-function": "^7.22.20" }, "engines": { "node": ">=6.9.0" @@ -442,20 +448,20 @@ } }, "node_modules/@babel/helper-replace-supers": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.22.5.tgz", - "integrity": "sha512-aLdNM5I3kdI/V9xGNyKSF3X/gTyMUBohTZ+/3QdQKAA9vxIiy12E+8E2HoOP1/DjeqU+g6as35QHJNMDDYpuCg==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.22.20.tgz", + "integrity": "sha512-qsW0In3dbwQUbK8kejJ4R7IHVGwHJlV6lpG6UA7a9hSa2YEiAib+N1T2kr6PEeUT+Fl7najmSOS6SmAwCHK6Tw==", "dev": true, "dependencies": { - "@babel/helper-environment-visitor": "^7.22.5", - "@babel/helper-member-expression-to-functions": "^7.22.5", - "@babel/helper-optimise-call-expression": "^7.22.5", - "@babel/template": "^7.22.5", - "@babel/traverse": "^7.22.5", - "@babel/types": "^7.22.5" + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-member-expression-to-functions": "^7.22.15", + "@babel/helper-optimise-call-expression": "^7.22.5" }, "engines": { "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } }, "node_modules/@babel/helper-simple-access": { @@ -483,9 +489,9 @@ } }, "node_modules/@babel/helper-split-export-declaration": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.5.tgz", - "integrity": "sha512-thqK5QFghPKWLhAV321lxF95yCg2K3Ob5yw+M3VHWfdia0IkPXUtoLH8x/6Fh486QUvzhb8YOWHChTVen2/PoQ==", + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", "dev": true, "dependencies": { "@babel/types": "^7.22.5" @@ -504,60 +510,59 @@ } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz", - "integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-option": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.22.5.tgz", - "integrity": "sha512-R3oB6xlIVKUnxNUxbmgq7pKjxpru24zlimpE8WK47fACIlM0II/Hm1RS8IaOI7NgCr6LNS+jl5l75m20npAziw==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.22.15.tgz", + "integrity": "sha512-bMn7RmyFjY/mdECUbgn9eoSY4vqvacUnS9i9vGAGttgFWesO6B4CYWA7XlpbWgBt71iv/hfbPlynohStqnu5hA==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-wrap-function": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.22.5.tgz", - "integrity": "sha512-bYqLIBSEshYcYQyfks8ewYA8S30yaGSeRslcvKMvoUk6HHPySbxHq9YRi6ghhzEU+yhQv9bP/jXnygkStOcqZw==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.22.20.tgz", + "integrity": "sha512-pms/UwkOpnQe/PDAEdV/d7dVCoBbB+R4FvYoHGZz+4VPcg7RtYy2KP7S2lbuWM6FCSgob5wshfGESbC/hzNXZw==", "dev": true, "dependencies": { "@babel/helper-function-name": "^7.22.5", - "@babel/template": "^7.22.5", - "@babel/traverse": "^7.22.5", - "@babel/types": "^7.22.5" + "@babel/template": "^7.22.15", + "@babel/types": "^7.22.19" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helpers": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.22.5.tgz", - "integrity": "sha512-pSXRmfE1vzcUIDFQcSGA5Mr+GxBV9oiRKDuDxXvWQQBCh8HoIjs/2DlDB7H8smac1IVrB9/xdXj2N3Wol9Cr+Q==", + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.2.tgz", + "integrity": "sha512-lzchcp8SjTSVe/fPmLwtWVBFC7+Tbn8LGHDVfDp9JGxpAY5opSaEFgt8UQvrnECWOTdji2mOWMz1rOhkHscmGQ==", "dev": true, "dependencies": { - "@babel/template": "^7.22.5", - "@babel/traverse": "^7.22.5", - "@babel/types": "^7.22.5" + "@babel/template": "^7.22.15", + "@babel/traverse": "^7.23.2", + "@babel/types": "^7.23.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/highlight": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.5.tgz", - "integrity": "sha512-BSKlD1hgnedS5XRnGOljZawtag7H1yPfQp0tdNJCHoH6AZ+Pcm9VvkrK59/Yy593Ypg0zMxH2BxD1VPYUQ7UIw==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", + "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", "dev": true, "dependencies": { - "@babel/helper-validator-identifier": "^7.22.5", - "chalk": "^2.0.0", + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", "js-tokens": "^4.0.0" }, "engines": { @@ -565,9 +570,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.5.tgz", - "integrity": "sha512-DFZMC9LJUG9PLOclRC32G63UXwzqS2koQC8dkx+PLdmt1xSePYpbT/NbsrJy8Q/muXz7o/h/d4A7Fuyixm559Q==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz", + "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==", "dev": true, "bin": { "parser": "bin/babel-parser.js" @@ -577,9 +582,9 @@ } }, "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.22.5.tgz", - "integrity": "sha512-NP1M5Rf+u2Gw9qfSO4ihjcTGW5zXTi36ITLd4/EoAcEhIZ0yjMqmftDNl3QC19CX7olhrjpyU454g/2W7X0jvQ==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.22.15.tgz", + "integrity": "sha512-FB9iYlz7rURmRJyXRKEnalYPPdn87H5no108cyuQQyMwlpJ2SJtpIUBI27kdTin956pz+LPypkPVPUTlxOmrsg==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.22.5" @@ -592,14 +597,14 @@ } }, "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.22.5.tgz", - "integrity": "sha512-31Bb65aZaUwqCbWMnZPduIZxCBngHFlzyN6Dq6KAJjtx+lx6ohKHubc61OomYi7XwVD4Ol0XCVz4h+pYFR048g==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.22.15.tgz", + "integrity": "sha512-Hyph9LseGvAeeXzikV88bczhsrLrIZqDPxO+sSmAunMPaGrBGhfMWzCPYTtiW9t+HzSE2wtV8e5cc5P6r1xMDQ==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.22.5", "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", - "@babel/plugin-transform-optional-chaining": "^7.22.5" + "@babel/plugin-transform-optional-chaining": "^7.22.15" }, "engines": { "node": ">=6.9.0" @@ -612,6 +617,7 @@ "version": "7.20.7", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.20.7.tgz", "integrity": "sha512-xMbiLsn/8RK7Wq7VeVytytS2L6qE69bXPB10YCmMdDZbKF4okCqY74pI/jJQ/8U0b/F6NrT2+14b8/P9/3AMGA==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-async-generator-functions instead.", "dev": true, "dependencies": { "@babel/helper-environment-visitor": "^7.18.9", @@ -630,6 +636,7 @@ "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz", "integrity": "sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-class-properties instead.", "dev": true, "dependencies": { "@babel/helper-create-class-features-plugin": "^7.18.6", @@ -646,6 +653,7 @@ "version": "7.21.0", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.21.0.tgz", "integrity": "sha512-XP5G9MWNUskFuP30IfFSEFB0Z6HzLIUcjYM4bYOPHXl7eiJ9HFv8tWj6TXTN5QODiEhDZAeI4hLok2iHFFV4hw==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-class-static-block instead.", "dev": true, "dependencies": { "@babel/helper-create-class-features-plugin": "^7.21.0", @@ -663,6 +671,7 @@ "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.18.6.tgz", "integrity": "sha512-1auuwmK+Rz13SJj36R+jqFPMJWyKEDd7lLSdOj4oJK0UTgGueSAtkrCvz9ewmgyU/P941Rv2fQwZJN8s6QruXw==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-dynamic-import instead.", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.18.6", @@ -679,6 +688,7 @@ "version": "7.18.9", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.18.9.tgz", "integrity": "sha512-k1NtHyOMvlDDFeb9G5PhUXuGj8m/wiwojgQVEhJ/fsVsMCpLyOP4h0uGEjYJKrRI+EVPlb5Jk+Gt9P97lOGwtA==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-export-namespace-from instead.", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.18.9", @@ -695,6 +705,7 @@ "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.18.6.tgz", "integrity": "sha512-lr1peyn9kOdbYc0xr0OdHTZ5FMqS6Di+H0Fz2I/JwMzGmzJETNeOFq2pBySw6X/KFL5EWDjlJuMsUGRFb8fQgQ==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-json-strings instead.", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.18.6", @@ -711,6 +722,7 @@ "version": "7.20.7", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.20.7.tgz", "integrity": "sha512-y7C7cZgpMIjWlKE5T7eJwp+tnRYM89HmRvWM5EQuB5BoHEONjmQ8lSNmBUwOyy/GFRsohJED51YBF79hE1djug==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-logical-assignment-operators instead.", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.20.2", @@ -727,6 +739,7 @@ "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz", "integrity": "sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-nullish-coalescing-operator instead.", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.18.6", @@ -743,6 +756,7 @@ "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.18.6.tgz", "integrity": "sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-numeric-separator instead.", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.18.6", @@ -759,6 +773,7 @@ "version": "7.20.7", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.20.7.tgz", "integrity": "sha512-d2S98yCiLxDVmBmE8UjGcfPvNEUbA1U5q5WxaWFUGRzJSVAZqm5W6MbPct0jxnegUZ0niLeNX+IOzEs7wYg9Dg==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-object-rest-spread instead.", "dev": true, "dependencies": { "@babel/compat-data": "^7.20.5", @@ -778,6 +793,7 @@ "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.18.6.tgz", "integrity": "sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-optional-catch-binding instead.", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.18.6", @@ -794,6 +810,7 @@ "version": "7.21.0", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.21.0.tgz", "integrity": "sha512-p4zeefM72gpmEe2fkUr/OnOXpWEf8nAgk7ZYVqqfFiyIG7oFfVZcCrU64hWn5xp4tQ9LkV4bTIa5rD0KANpKNA==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-optional-chaining instead.", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.20.2", @@ -811,6 +828,7 @@ "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.18.6.tgz", "integrity": "sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-private-methods instead.", "dev": true, "dependencies": { "@babel/helper-create-class-features-plugin": "^7.18.6", @@ -827,6 +845,7 @@ "version": "7.21.11", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.11.tgz", "integrity": "sha512-0QZ8qP/3RLDVBwBFoWAwCtgcDZJVwA5LUJRZU8x2YFfKNuFq161wK3cuGrALu5yiPu+vzwTAg/sMWVNeWeNyaw==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-private-property-in-object instead.", "dev": true, "dependencies": { "@babel/helper-annotate-as-pure": "^7.18.6", @@ -845,6 +864,7 @@ "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.18.6.tgz", "integrity": "sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-unicode-property-regex instead.", "dev": true, "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.18.6", @@ -1109,9 +1129,9 @@ } }, "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.22.5.tgz", - "integrity": "sha512-EcACl1i5fSQ6bt+YGuU/XGCeZKStLmyVGytWkpyhCLeQVA0eu6Wtiw92V+I1T/hnezUv7j74dA/Ro69gWcU+hg==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.23.0.tgz", + "integrity": "sha512-cOsrbmIOXmf+5YbL99/S49Y3j46k/T16b9ml8bm9lP6N9US5iQ2yBK7gpui1pg0V/WMcXdkfKbTb7HXq9u+v4g==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.22.5" @@ -1124,19 +1144,19 @@ } }, "node_modules/@babel/plugin-transform-classes": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.22.5.tgz", - "integrity": "sha512-2edQhLfibpWpsVBx2n/GKOz6JdGQvLruZQfGr9l1qes2KQaWswjBzhQF7UDUZMNaMMQeYnQzxwOMPsbYF7wqPQ==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.22.15.tgz", + "integrity": "sha512-VbbC3PGjBdE0wAWDdHM9G8Gm977pnYI0XpqMd6LrKISj8/DJXEsWqgRuTYaNE9Bv0JGhTZUzHDlMk18IpOuoqw==", "dev": true, "dependencies": { "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-compilation-targets": "^7.22.5", + "@babel/helper-compilation-targets": "^7.22.15", "@babel/helper-environment-visitor": "^7.22.5", "@babel/helper-function-name": "^7.22.5", "@babel/helper-optimise-call-expression": "^7.22.5", "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-replace-supers": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.5", + "@babel/helper-replace-supers": "^7.22.9", + "@babel/helper-split-export-declaration": "^7.22.6", "globals": "^11.1.0" }, "engines": { @@ -1163,9 +1183,9 @@ } }, "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.22.5.tgz", - "integrity": "sha512-GfqcFuGW8vnEqTUBM7UtPd5A4q797LTvvwKxXTgRsFjoqaJiEg9deBG6kWeQYkVEL569NpnmpC0Pkr/8BLKGnQ==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.23.0.tgz", + "integrity": "sha512-vaMdgNXFkYrB+8lbgniSYWHsgqK5gjaMNcc84bMIOMRLH0L9AqYq3hwMdvnyqj1OPqea8UtjPEuS/DCenah1wg==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.22.5" @@ -1225,9 +1245,9 @@ } }, "node_modules/@babel/plugin-transform-for-of": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.22.5.tgz", - "integrity": "sha512-3kxQjX1dU9uudwSshyLeEipvrLjBCVthCgeTp6CzE/9JYrlAIaeekVxRpCWsDDfYTfRZRoCeZatCQvwo+wvK8A==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.22.15.tgz", + "integrity": "sha512-me6VGeHsx30+xh9fbDLLPi0J1HzmeIIyenoOQHuw2D4m2SAU3NrspX5XxJLBpqn5yrLzrlw2Iy3RA//Bx27iOA==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.22.5" @@ -1287,12 +1307,12 @@ } }, "node_modules/@babel/plugin-transform-modules-amd": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.22.5.tgz", - "integrity": "sha512-R+PTfLTcYEmb1+kK7FNkhQ1gP4KgjpSO6HfH9+f8/yfp2Nt3ggBjiVpRwmwTlfqZLafYKJACy36yDXlEmI9HjQ==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.23.0.tgz", + "integrity": "sha512-xWT5gefv2HGSm4QHtgc1sYPbseOyf+FFDo2JbpE25GWl5BqTGO9IMwTYJRoIdjsF85GE+VegHxSCUt5EvoYTAw==", "dev": true, "dependencies": { - "@babel/helper-module-transforms": "^7.22.5", + "@babel/helper-module-transforms": "^7.23.0", "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { @@ -1303,12 +1323,12 @@ } }, "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.22.5.tgz", - "integrity": "sha512-B4pzOXj+ONRmuaQTg05b3y/4DuFz3WcCNAXPLb2Q0GT0TrGKGxNKV4jwsXts+StaM0LQczZbOpj8o1DLPDJIiA==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.23.0.tgz", + "integrity": "sha512-32Xzss14/UVc7k9g775yMIvkVK8xwKE0DPdP5JTapr3+Z9w4tzeOuLNY6BXDQR6BdnzIlXnCGAzsk/ICHBLVWQ==", "dev": true, "dependencies": { - "@babel/helper-module-transforms": "^7.22.5", + "@babel/helper-module-transforms": "^7.23.0", "@babel/helper-plugin-utils": "^7.22.5", "@babel/helper-simple-access": "^7.22.5" }, @@ -1320,15 +1340,15 @@ } }, "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.22.5.tgz", - "integrity": "sha512-emtEpoaTMsOs6Tzz+nbmcePl6AKVtS1yC4YNAeMun9U8YCsgadPNxnOPQ8GhHFB2qdx+LZu9LgoC0Lthuu05DQ==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.23.0.tgz", + "integrity": "sha512-qBej6ctXZD2f+DhlOC9yO47yEYgUh5CZNz/aBoH4j/3NOlRfJXJbY7xDQCqQVf9KbrqGzIWER1f23doHGrIHFg==", "dev": true, "dependencies": { "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-module-transforms": "^7.22.5", + "@babel/helper-module-transforms": "^7.23.0", "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-validator-identifier": "^7.22.5" + "@babel/helper-validator-identifier": "^7.22.20" }, "engines": { "node": ">=6.9.0" @@ -1401,9 +1421,9 @@ } }, "node_modules/@babel/plugin-transform-optional-chaining": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.22.5.tgz", - "integrity": "sha512-AconbMKOMkyG+xCng2JogMCDcqW8wedQAqpVIL4cOSescZ7+iW8utC6YDZLMCSUIReEA733gzRSaOSXMAt/4WQ==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.23.0.tgz", + "integrity": "sha512-sBBGXbLJjxTzLBF5rFWaikMnOGOk/BmK6vVByIdEggZ7Vn6CvWXZyRkkLFK6WE0IF8jSliyOkUN6SScFgzCM0g==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.22.5", @@ -1418,9 +1438,9 @@ } }, "node_modules/@babel/plugin-transform-parameters": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.22.5.tgz", - "integrity": "sha512-AVkFUBurORBREOmHRKo06FjHYgjrabpdqRSwq6+C7R5iTCZOsM4QbcB27St0a4U6fffyAOqh3s/qEfybAhfivg==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.22.15.tgz", + "integrity": "sha512-hjk7qKIqhyzhhUvRT683TYQOFa/4cQKwQy7ALvTpODswN40MljzNDa0YldevS6tGbxwaEKVn502JmY0dP7qEtQ==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.22.5" @@ -1448,13 +1468,13 @@ } }, "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.22.5.tgz", - "integrity": "sha512-rR7KePOE7gfEtNTh9Qw+iO3Q/e4DEsoQ+hdvM6QUDH7JRJ5qxq5AA52ZzBWbI5i9lfNuvySgOGP8ZN7LAmaiPw==", + "version": "7.22.10", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.22.10.tgz", + "integrity": "sha512-F28b1mDt8KcT5bUyJc/U9nwzw6cV+UmTeRlXYIl2TNqMMJif0Jeey9/RQ3C4NOd2zp0/TRsDns9ttj2L523rsw==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.22.5", - "regenerator-transform": "^0.15.1" + "regenerator-transform": "^0.15.2" }, "engines": { "node": ">=6.9.0" @@ -1555,9 +1575,9 @@ } }, "node_modules/@babel/plugin-transform-unicode-escapes": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.22.5.tgz", - "integrity": "sha512-biEmVg1IYB/raUO5wT1tgfacCef15Fbzhkx493D3urBI++6hpJ+RFG4SrWMn0NEZLfvilqKf3QDrRVZHo08FYg==", + "version": "7.22.10", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.22.10.tgz", + "integrity": "sha512-lRfaRKGZCBqDlRU3UIFovdp9c9mEvlylmpod0/OatICsSfuQ9YFthRo1tpTkGsklEefZdqlEFdY4A2dwTb6ohg==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.22.5" @@ -1676,18 +1696,18 @@ } }, "node_modules/@babel/preset-env/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, "bin": { "semver": "bin/semver.js" } }, "node_modules/@babel/preset-modules": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.5.tgz", - "integrity": "sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA==", + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6.tgz", + "integrity": "sha512-ID2yj6K/4lKfhuU3+EX4UvNbIt7eACFbHmNUjzA+ep+B5971CknnA/9DEWKbRokfbbtblxxxXFJJrH47UEAMVg==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.0.0", @@ -1697,7 +1717,7 @@ "esutils": "^2.0.2" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" } }, "node_modules/@babel/regjsgen": { @@ -1707,45 +1727,45 @@ "dev": true }, "node_modules/@babel/runtime": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.5.tgz", - "integrity": "sha512-ecjvYlnAaZ/KVneE/OdKYBYfgXV3Ptu6zQWmgEF7vwKhQnvVS6bjMD2XYgj+SNvQ1GfK/pjgokfPkC/2CO8CuA==", + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.2.tgz", + "integrity": "sha512-mM8eg4yl5D6i3lu2QKPuPH4FArvJ8KhTofbE7jwMUv9KX5mBvwPAqnV3MlyBNqdp9RyRKP6Yck8TrfYrPvX3bg==", "dev": true, "dependencies": { - "regenerator-runtime": "^0.13.11" + "regenerator-runtime": "^0.14.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/template": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.5.tgz", - "integrity": "sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", + "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.22.5", - "@babel/parser": "^7.22.5", - "@babel/types": "^7.22.5" + "@babel/code-frame": "^7.22.13", + "@babel/parser": "^7.22.15", + "@babel/types": "^7.22.15" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.5.tgz", - "integrity": "sha512-7DuIjPgERaNo6r+PZwItpjCZEa5vyw4eJGufeLxrPdBXBoLcCJCIasvK6pK/9DVNrLZTLFhUGqaC6X/PA007TQ==", + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz", + "integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.22.5", - "@babel/generator": "^7.22.5", - "@babel/helper-environment-visitor": "^7.22.5", - "@babel/helper-function-name": "^7.22.5", + "@babel/code-frame": "^7.22.13", + "@babel/generator": "^7.23.0", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.5", - "@babel/parser": "^7.22.5", - "@babel/types": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.23.0", + "@babel/types": "^7.23.0", "debug": "^4.1.0", "globals": "^11.1.0" }, @@ -1754,13 +1774,13 @@ } }, "node_modules/@babel/types": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.5.tgz", - "integrity": "sha512-zo3MIHGOkPOfoRXitsgHLjEXmlDaD/5KU1Uzuc9GNiZPhSqVxVRtxuPaSBZDsYZ9qV88AjtMtWW7ww98loJ9KA==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", + "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", "dev": true, "dependencies": { "@babel/helper-string-parser": "^7.22.5", - "@babel/helper-validator-identifier": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20", "to-fast-properties": "^2.0.0" }, "engines": { @@ -1792,23 +1812,23 @@ } }, "node_modules/@eslint-community/regexpp": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.5.1.tgz", - "integrity": "sha512-Z5ba73P98O1KUYCCJTUeVpja9RcGoMdncZ6T49FCUl2lN38JtCJ+3WgIDBv0AuY4WChU5PmtJmOCTlN6FZTFKQ==", + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.9.1.tgz", + "integrity": "sha512-Y27x+MBLjXa+0JWDhykM3+JE+il3kHKAEqabfEWq3SDhZjLYb6/BHL/JKFnH3fe207JaXkyDo685Oc2Glt6ifA==", "dev": true, "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, "node_modules/@eslint/eslintrc": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.3.tgz", - "integrity": "sha512-+5gy6OQfk+xx3q0d6jGZZC3f3KzAkXc/IanVxd1is/VIIziRqqt3ongQz0FiTUXqTk0c7aDB3OaFuKnuSoJicQ==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.2.tgz", + "integrity": "sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==", "dev": true, "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", - "espree": "^9.5.2", + "espree": "^9.6.0", "globals": "^13.19.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", @@ -1830,9 +1850,9 @@ "dev": true }, "node_modules/@eslint/eslintrc/node_modules/globals": { - "version": "13.20.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", - "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", + "version": "13.23.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.23.0.tgz", + "integrity": "sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==", "dev": true, "dependencies": { "type-fest": "^0.20.2" @@ -1857,9 +1877,9 @@ } }, "node_modules/@eslint/js": { - "version": "8.43.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.43.0.tgz", - "integrity": "sha512-s2UHCoiXfxMvmfzqoN+vrQ84ahUSYde9qNO1MdxmoEhyHWsfmwOpFlwYV+ePJEVc7gFnATGUi376WowX1N7tFg==", + "version": "8.51.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.51.0.tgz", + "integrity": "sha512-HxjQ8Qn+4SI3/AFv6sOrDB+g6PpUTDwSJiQqOrnneEk8L71161srI9gjzzZvYVbzHiVg/BvcH95+cK/zfIt4pg==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -1960,10 +1980,22 @@ "node": ">=0.10.0" } }, + "node_modules/@gulpjs/to-absolute-glob": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@gulpjs/to-absolute-glob/-/to-absolute-glob-4.0.0.tgz", + "integrity": "sha512-kjotm7XJrJ6v+7knhPaRgaT6q8F8K2jiafwYdNHLzmV0uGLuZY43FK6smNSHUPrhq5kX2slCUy+RGG/xGqmIKA==", + "dev": true, + "dependencies": { + "is-negated-glob": "^1.0.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/@humanwhocodes/config-array": { - "version": "0.11.10", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.10.tgz", - "integrity": "sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==", + "version": "0.11.11", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.11.tgz", + "integrity": "sha512-N2brEuAadi0CcdeMXUkhbZB84eskAc8MEX1By6qEchoVywSgXPIjou4rYsl0V3Hj0ZnuGycGCjdNgockbzeWNA==", "dev": true, "dependencies": { "@humanwhocodes/object-schema": "^1.2.1", @@ -2048,22 +2080,22 @@ "dev": true }, "node_modules/@lit-labs/ssr-dom-shim": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.1.1.tgz", - "integrity": "sha512-kXOeFbfCm4fFf2A3WwVEeQj55tMZa8c8/f9AKHMobQMkzNUfUj+antR3fRPaZJawsa1aZiP/Da3ndpZrwEe4rQ==" + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.1.2.tgz", + "integrity": "sha512-jnOD+/+dSrfTWYfSXBXlo5l5f0q1UuJo3tkbMDCYA2lKUYq79jaxqtGEvnRoh049nt1vdo1+45RinipU6FGY2g==" }, "node_modules/@lit/reactive-element": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-1.6.2.tgz", - "integrity": "sha512-rDfl+QnCYjuIGf5xI2sVJWdYIi56CTCwWa+nidKYX6oIuBYwUbT/vX4qbUDlHiZKJ/3FRNQ/tWJui44p6/stSA==", + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-1.6.3.tgz", + "integrity": "sha512-QuTgnG52Poic7uM1AN5yJ09QMe0O28e10XzSvWDz02TJiiKee4stsiownEIadWm8nYzyDAyT+gKzUoZmiWQtsQ==", "dependencies": { "@lit-labs/ssr-dom-shim": "^1.0.0" } }, "node_modules/@microsoft/signalr": { - "version": "7.0.7", - "resolved": "https://registry.npmjs.org/@microsoft/signalr/-/signalr-7.0.7.tgz", - "integrity": "sha512-RMWZLTxnjWPSaS9PYZxXAttql2JDM/+IsSJk0nACFhpLjnSw8UWfvUxOv/QjZSqLxhuksXxzBJ/91xUP6Y7Nvg==", + "version": "7.0.12", + "resolved": "https://registry.npmjs.org/@microsoft/signalr/-/signalr-7.0.12.tgz", + "integrity": "sha512-k1Xu+a73PsWgHwHDm6ctHwHTBnlqCzq7L33cbxdWhj90AGDFpxDSzaGCkZDoJFNHveUetix65zIWiazMvmMg3w==", "dependencies": { "abort-controller": "^3.0.0", "eventsource": "^2.0.2", @@ -2159,9 +2191,9 @@ "dev": true }, "node_modules/@types/eslint": { - "version": "8.40.2", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.40.2.tgz", - "integrity": "sha512-PRVjQ4Eh9z9pmmtaq8nTjZjQwKFk7YIHIud3lRoKRBgUQjgjRmoGxxGEPXQkF+lH7QkHJRNr5F4aBgYCW0lqpQ==", + "version": "8.44.5", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.44.5.tgz", + "integrity": "sha512-Ol2eio8LtD/tGM4Ga7Jb83NuFwEv3NqvssSlifXL9xuFpSyQZw0ecmm2Kux6iU0KxQmp95hlPmGCzGJ0TCFeRA==", "dev": true, "dependencies": { "@types/estree": "*", @@ -2169,9 +2201,9 @@ } }, "node_modules/@types/estree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.1.tgz", - "integrity": "sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.3.tgz", + "integrity": "sha512-CS2rOaoQ/eAgAfcTfq6amKG7bsN+EMcgGY4FAFQdvSj2y1ixvOZTUA9mOtCai7E1SYu283XNw7urKK30nP3wkQ==", "dev": true }, "node_modules/@types/glob": { @@ -2185,9 +2217,9 @@ } }, "node_modules/@types/json-schema": { - "version": "7.0.12", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz", - "integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==", + "version": "7.0.14", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.14.tgz", + "integrity": "sha512-U3PUjAudAdJBeC2pgN8uTIKgxrb4nlDF3SF0++EldXQvQBGkpFZMSnwQiIoDU77tv45VgNkl/L4ouD+rEomujw==", "dev": true }, "node_modules/@types/minimatch": { @@ -2210,785 +2242,785 @@ "optional": true }, "node_modules/@types/trusted-types": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.3.tgz", - "integrity": "sha512-NfQ4gyz38SL8sDNrSixxU2Os1a5xcdFxipAFxYEuLUlvU2uDwS4NUpsImcf1//SlWItCVMMLiylsxbmNMToV/g==" + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.4.tgz", + "integrity": "sha512-IDaobHimLQhjwsQ/NMwRVfa/yL7L/wriQPMhw1ZJall0KX6E1oxk29XMDeilW5qTIg5aoiqf5Udy8U/51aNoQQ==" }, "node_modules/@umbraco-ui/uui": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui/-/uui-1.3.0.tgz", - "integrity": "sha512-Zx30c08U0muc2T1ZAPYvKTsa8eNd19b96tIzOhvwHzsjNy56kdYKExPDKkvQ49Blzv9/SxKt4eVghZRNrBdUBg==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui/-/uui-1.4.0.tgz", + "integrity": "sha512-VG+C37WIS5Uv7ERDs/jQHT9mIncD9UrEsEQlgFnf2XZWc/TcBlV1Tvvt3xSYzZz9kIjwoymEG6lc5t6wJMqSfw==", "dependencies": { - "@umbraco-ui/uui-action-bar": "1.3.0", - "@umbraco-ui/uui-avatar": "1.3.0", - "@umbraco-ui/uui-avatar-group": "1.3.0", - "@umbraco-ui/uui-badge": "1.3.0", - "@umbraco-ui/uui-base": "1.3.0", - "@umbraco-ui/uui-boolean-input": "1.3.0", - "@umbraco-ui/uui-box": "1.3.0", - "@umbraco-ui/uui-breadcrumbs": "1.3.0", - "@umbraco-ui/uui-button": "1.3.0", - "@umbraco-ui/uui-button-group": "1.3.0", - "@umbraco-ui/uui-button-inline-create": "1.3.0", - "@umbraco-ui/uui-card": "1.3.0", - "@umbraco-ui/uui-card-content-node": "1.3.0", - "@umbraco-ui/uui-card-media": "1.3.0", - "@umbraco-ui/uui-card-user": "1.3.0", - "@umbraco-ui/uui-caret": "1.3.0", - "@umbraco-ui/uui-checkbox": "1.3.0", - "@umbraco-ui/uui-color-area": "1.3.0", - "@umbraco-ui/uui-color-picker": "1.3.0", - "@umbraco-ui/uui-color-slider": "1.3.0", - "@umbraco-ui/uui-color-swatch": "1.3.0", - "@umbraco-ui/uui-color-swatches": "1.3.0", - "@umbraco-ui/uui-combobox": "1.3.0", - "@umbraco-ui/uui-combobox-list": "1.3.0", - "@umbraco-ui/uui-css": "1.3.0", - "@umbraco-ui/uui-dialog": "1.3.0", - "@umbraco-ui/uui-dialog-layout": "1.3.0", - "@umbraco-ui/uui-file-dropzone": "1.3.0", - "@umbraco-ui/uui-file-preview": "1.3.0", - "@umbraco-ui/uui-form": "1.3.0", - "@umbraco-ui/uui-form-layout-item": "1.3.0", - "@umbraco-ui/uui-form-validation-message": "1.3.0", - "@umbraco-ui/uui-icon": "1.3.0", - "@umbraco-ui/uui-icon-registry": "1.3.0", - "@umbraco-ui/uui-icon-registry-essential": "1.3.0", - "@umbraco-ui/uui-input": "1.3.0", - "@umbraco-ui/uui-input-file": "1.3.0", - "@umbraco-ui/uui-input-lock": "1.3.0", - "@umbraco-ui/uui-input-password": "1.3.0", - "@umbraco-ui/uui-keyboard-shortcut": "1.3.0", - "@umbraco-ui/uui-label": "1.3.0", - "@umbraco-ui/uui-loader": "1.3.0", - "@umbraco-ui/uui-loader-bar": "1.3.0", - "@umbraco-ui/uui-loader-circle": "1.3.0", - "@umbraco-ui/uui-menu-item": "1.3.0", - "@umbraco-ui/uui-modal": "1.3.0", - "@umbraco-ui/uui-pagination": "1.3.0", - "@umbraco-ui/uui-popover": "1.3.0", - "@umbraco-ui/uui-progress-bar": "1.3.0", - "@umbraco-ui/uui-radio": "1.3.0", - "@umbraco-ui/uui-range-slider": "1.3.0", - "@umbraco-ui/uui-ref": "1.3.0", - "@umbraco-ui/uui-ref-list": "1.3.0", - "@umbraco-ui/uui-ref-node": "1.3.0", - "@umbraco-ui/uui-ref-node-data-type": "1.3.0", - "@umbraco-ui/uui-ref-node-document-type": "1.3.0", - "@umbraco-ui/uui-ref-node-form": "1.3.0", - "@umbraco-ui/uui-ref-node-member": "1.3.0", - "@umbraco-ui/uui-ref-node-package": "1.3.0", - "@umbraco-ui/uui-ref-node-user": "1.3.0", - "@umbraco-ui/uui-scroll-container": "1.3.0", - "@umbraco-ui/uui-select": "1.3.0", - "@umbraco-ui/uui-slider": "1.3.0", - "@umbraco-ui/uui-symbol-expand": "1.3.0", - "@umbraco-ui/uui-symbol-file": "1.3.0", - "@umbraco-ui/uui-symbol-file-dropzone": "1.3.0", - "@umbraco-ui/uui-symbol-file-thumbnail": "1.3.0", - "@umbraco-ui/uui-symbol-folder": "1.3.0", - "@umbraco-ui/uui-symbol-lock": "1.3.0", - "@umbraco-ui/uui-symbol-more": "1.3.0", - "@umbraco-ui/uui-symbol-sort": "1.3.0", - "@umbraco-ui/uui-table": "1.3.0", - "@umbraco-ui/uui-tabs": "1.3.0", - "@umbraco-ui/uui-tag": "1.3.0", - "@umbraco-ui/uui-textarea": "1.3.0", - "@umbraco-ui/uui-toast-notification": "1.3.0", - "@umbraco-ui/uui-toast-notification-container": "1.3.0", - "@umbraco-ui/uui-toast-notification-layout": "1.3.0", - "@umbraco-ui/uui-toggle": "1.3.0" + "@umbraco-ui/uui-action-bar": "1.4.0", + "@umbraco-ui/uui-avatar": "1.4.0", + "@umbraco-ui/uui-avatar-group": "1.4.0", + "@umbraco-ui/uui-badge": "1.4.0", + "@umbraco-ui/uui-base": "1.4.0", + "@umbraco-ui/uui-boolean-input": "1.4.0", + "@umbraco-ui/uui-box": "1.4.0", + "@umbraco-ui/uui-breadcrumbs": "1.4.0", + "@umbraco-ui/uui-button": "1.4.0", + "@umbraco-ui/uui-button-group": "1.4.0", + "@umbraco-ui/uui-button-inline-create": "1.4.0", + "@umbraco-ui/uui-card": "1.4.0", + "@umbraco-ui/uui-card-content-node": "1.4.0", + "@umbraco-ui/uui-card-media": "1.4.0", + "@umbraco-ui/uui-card-user": "1.4.0", + "@umbraco-ui/uui-caret": "1.4.0", + "@umbraco-ui/uui-checkbox": "1.4.0", + "@umbraco-ui/uui-color-area": "1.4.0", + "@umbraco-ui/uui-color-picker": "1.4.0", + "@umbraco-ui/uui-color-slider": "1.4.0", + "@umbraco-ui/uui-color-swatch": "1.4.0", + "@umbraco-ui/uui-color-swatches": "1.4.0", + "@umbraco-ui/uui-combobox": "1.4.0", + "@umbraco-ui/uui-combobox-list": "1.4.0", + "@umbraco-ui/uui-css": "1.4.0", + "@umbraco-ui/uui-dialog": "1.4.0", + "@umbraco-ui/uui-dialog-layout": "1.4.0", + "@umbraco-ui/uui-file-dropzone": "1.4.0", + "@umbraco-ui/uui-file-preview": "1.4.0", + "@umbraco-ui/uui-form": "1.4.0", + "@umbraco-ui/uui-form-layout-item": "1.4.0", + "@umbraco-ui/uui-form-validation-message": "1.4.0", + "@umbraco-ui/uui-icon": "1.4.0", + "@umbraco-ui/uui-icon-registry": "1.4.0", + "@umbraco-ui/uui-icon-registry-essential": "1.4.0", + "@umbraco-ui/uui-input": "1.4.0", + "@umbraco-ui/uui-input-file": "1.4.0", + "@umbraco-ui/uui-input-lock": "1.4.0", + "@umbraco-ui/uui-input-password": "1.4.0", + "@umbraco-ui/uui-keyboard-shortcut": "1.4.0", + "@umbraco-ui/uui-label": "1.4.0", + "@umbraco-ui/uui-loader": "1.4.0", + "@umbraco-ui/uui-loader-bar": "1.4.0", + "@umbraco-ui/uui-loader-circle": "1.4.0", + "@umbraco-ui/uui-menu-item": "1.4.0", + "@umbraco-ui/uui-modal": "1.4.0", + "@umbraco-ui/uui-pagination": "1.4.0", + "@umbraco-ui/uui-popover": "1.4.0", + "@umbraco-ui/uui-progress-bar": "1.4.0", + "@umbraco-ui/uui-radio": "1.4.0", + "@umbraco-ui/uui-range-slider": "1.4.0", + "@umbraco-ui/uui-ref": "1.4.0", + "@umbraco-ui/uui-ref-list": "1.4.0", + "@umbraco-ui/uui-ref-node": "1.4.0", + "@umbraco-ui/uui-ref-node-data-type": "1.4.0", + "@umbraco-ui/uui-ref-node-document-type": "1.4.0", + "@umbraco-ui/uui-ref-node-form": "1.4.0", + "@umbraco-ui/uui-ref-node-member": "1.4.0", + "@umbraco-ui/uui-ref-node-package": "1.4.0", + "@umbraco-ui/uui-ref-node-user": "1.4.0", + "@umbraco-ui/uui-scroll-container": "1.4.0", + "@umbraco-ui/uui-select": "1.4.0", + "@umbraco-ui/uui-slider": "1.4.0", + "@umbraco-ui/uui-symbol-expand": "1.4.0", + "@umbraco-ui/uui-symbol-file": "1.4.0", + "@umbraco-ui/uui-symbol-file-dropzone": "1.4.0", + "@umbraco-ui/uui-symbol-file-thumbnail": "1.4.0", + "@umbraco-ui/uui-symbol-folder": "1.4.0", + "@umbraco-ui/uui-symbol-lock": "1.4.0", + "@umbraco-ui/uui-symbol-more": "1.4.0", + "@umbraco-ui/uui-symbol-sort": "1.4.0", + "@umbraco-ui/uui-table": "1.4.0", + "@umbraco-ui/uui-tabs": "1.4.0", + "@umbraco-ui/uui-tag": "1.4.0", + "@umbraco-ui/uui-textarea": "1.4.0", + "@umbraco-ui/uui-toast-notification": "1.4.0", + "@umbraco-ui/uui-toast-notification-container": "1.4.0", + "@umbraco-ui/uui-toast-notification-layout": "1.4.0", + "@umbraco-ui/uui-toggle": "1.4.0" } }, "node_modules/@umbraco-ui/uui-action-bar": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-action-bar/-/uui-action-bar-1.3.0.tgz", - "integrity": "sha512-r7iJpaRPfXcaKFkd0V706DSscv8qE7wkz7cjM1ARQoTF6n9Ihgo3sBRNSQY8KK+qMrVZiRy7DAyEbFga1xXfPA==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-action-bar/-/uui-action-bar-1.4.0.tgz", + "integrity": "sha512-FMTSWXZOhWEziGL3OFvRGczAdRu2Ic82XLh4kCpCbRlKJHouqymOfo9FT3NbHEION37JUl9bv1nKiNA0m4s2bg==", "dependencies": { - "@umbraco-ui/uui-base": "1.3.0", - "@umbraco-ui/uui-button-group": "1.3.0" + "@umbraco-ui/uui-base": "1.4.0", + "@umbraco-ui/uui-button-group": "1.4.0" } }, "node_modules/@umbraco-ui/uui-avatar": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-avatar/-/uui-avatar-1.3.0.tgz", - "integrity": "sha512-hj1rTzbWx2g+YQGBjwhoDcO0uVAON2K+Y17wgvH/Zf3iTmX2MGlns0iUtlyhOnyIH9cX2qA7TawuVXJY1m6GrQ==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-avatar/-/uui-avatar-1.4.0.tgz", + "integrity": "sha512-sUvQKsaWXP+5xQO5p2YAqQyUITiyzIzK6cVRlGRUoEla3QlhCd7YHrRnrIJTNxwmfPygDtxGa9Zx8GNkW8N91w==", "dependencies": { - "@umbraco-ui/uui-base": "1.3.0" + "@umbraco-ui/uui-base": "1.4.0" } }, "node_modules/@umbraco-ui/uui-avatar-group": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-avatar-group/-/uui-avatar-group-1.3.0.tgz", - "integrity": "sha512-RtMdnNdY5brjme+dV9y84vCgBcKgVDRkDPhLnGb555kkac8hAh53HlOEhc50RwQTV00D91PP68elU8X0DRB5BQ==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-avatar-group/-/uui-avatar-group-1.4.0.tgz", + "integrity": "sha512-xpWMumABRNqVH3sdLBH43gBk8RSNjknTvqfuvfMgdrVUqAYE3cIjeadUDf9OfmzMWVoQn7PXyLSX7l/JRUhZJQ==", "dependencies": { - "@umbraco-ui/uui-avatar": "1.3.0", - "@umbraco-ui/uui-base": "1.3.0" + "@umbraco-ui/uui-avatar": "1.4.0", + "@umbraco-ui/uui-base": "1.4.0" } }, "node_modules/@umbraco-ui/uui-badge": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-badge/-/uui-badge-1.3.0.tgz", - "integrity": "sha512-AIAmoI1elKuAowm/Cw3lG3LYM6f31z6rUgu61NMGP9seLv8kVn9Ed4joqng9NFTWXHZgb7QWfplX5V/8cI8/GA==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-badge/-/uui-badge-1.4.0.tgz", + "integrity": "sha512-6qUhcoGL43FWFS/Q6yozieaigQfKp2zqIrUGkdDpC3LqvUBshzuCFuDQEE+nobW/0oUkGV9MaMfa7hBI88eQTQ==", "dependencies": { - "@umbraco-ui/uui-base": "1.3.0" + "@umbraco-ui/uui-base": "1.4.0" } }, "node_modules/@umbraco-ui/uui-base": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-base/-/uui-base-1.3.0.tgz", - "integrity": "sha512-2puL+mqSuYXVcy2bsmlyHtMS14OMlZL3nBQ0GzlwrQS/rncKabOiv6zsPBobv+UqQUnIiy+Ms4cfylbWODzVIw==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-base/-/uui-base-1.4.0.tgz", + "integrity": "sha512-RcNY2WfE2vTyAiDVyItBdo/o5owgMF16V+IFqa4xHeFlu1i08fp9/Qmyk+5Mb4LRJatt/V21zaOM0QlloyuNUg==", "dependencies": { "lit": "^2.3.1" } }, "node_modules/@umbraco-ui/uui-boolean-input": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-boolean-input/-/uui-boolean-input-1.3.0.tgz", - "integrity": "sha512-xQpr/Aq1kNGSWNvEaUbfUC1Gj4JZ+T5LrVdsjNBZZpZMyFbs2WHEZnBlpZkv3tSAgyZaIlWRat7ZMRWWL7BpDg==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-boolean-input/-/uui-boolean-input-1.4.0.tgz", + "integrity": "sha512-yIhvUpT5KBE+nmROtYdrkyTg7k5OQd2f5YpSKK2RrAA1Ex7J7ZZpGIO4B7w6wNuZLLPA657YxRADwrPKU91nNw==", "dependencies": { - "@umbraco-ui/uui-base": "1.3.0" + "@umbraco-ui/uui-base": "1.4.0" } }, "node_modules/@umbraco-ui/uui-box": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-box/-/uui-box-1.3.0.tgz", - "integrity": "sha512-VYBFilLZLC2KB5tYMg/CjPm6+fxFtJ/8Wi7u1FdUXMLuzmoleeNkHUXWYQe7BBIICYs9t8f0ewQG0N0G56M7mg==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-box/-/uui-box-1.4.0.tgz", + "integrity": "sha512-dQ8IeX86rAEmaz/ulJGDTGvmP0bMgm6LkRhGumignIRaVDLJdK5AIcPauVoq2n39IuczmoFjAEm6MFTAeQqZaQ==", "dependencies": { - "@umbraco-ui/uui-base": "1.3.0", - "@umbraco-ui/uui-css": "1.3.0" + "@umbraco-ui/uui-base": "1.4.0", + "@umbraco-ui/uui-css": "1.4.0" } }, "node_modules/@umbraco-ui/uui-breadcrumbs": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-breadcrumbs/-/uui-breadcrumbs-1.3.0.tgz", - "integrity": "sha512-gxQXJSasmop7ZSTFbygZ65lIn84rr9bolnUW5CyO/f0dciNYpSfZqUfK8y2QvQFrGvfgYf3BUVGbL8reucQLuQ==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-breadcrumbs/-/uui-breadcrumbs-1.4.0.tgz", + "integrity": "sha512-NfV8uVq093JceBC/Dog30iLi9z6ZwzwyS90At3qnCdIRn/ydxPghUA0xhS0Hf83GDQRgs9Ni7XbZv1P/SFdgrw==", "dependencies": { - "@umbraco-ui/uui-base": "1.3.0" + "@umbraco-ui/uui-base": "1.4.0" } }, "node_modules/@umbraco-ui/uui-button": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-button/-/uui-button-1.3.0.tgz", - "integrity": "sha512-+SvGpevoYof0gFU5ed5oQSiH9JIq41ltZU6Hx3RAhG8Q74IplEhKCiRAMKaQYuketSz7ckqVQlZLVPiKQjKLvg==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-button/-/uui-button-1.4.0.tgz", + "integrity": "sha512-8a6lZ/PLWg8iDuOv4YDhKvczWv844C3OfhPngLlmaK6UdkaiPlkxEoK41zZaVUV70B0ZhKk/odQYBp5nEUeeDA==", "dependencies": { - "@umbraco-ui/uui-base": "1.3.0", - "@umbraco-ui/uui-icon-registry-essential": "1.3.0" + "@umbraco-ui/uui-base": "1.4.0", + "@umbraco-ui/uui-icon-registry-essential": "1.4.0" } }, "node_modules/@umbraco-ui/uui-button-group": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-button-group/-/uui-button-group-1.3.0.tgz", - "integrity": "sha512-8yb5gkwY8nmhG260R+dmlv4l3BWIOWfuBtwdke6p1+2/305sLKMkDOSgMpw0Sm8M32BzIiEAGwMJgSirnPtahg==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-button-group/-/uui-button-group-1.4.0.tgz", + "integrity": "sha512-Cwb1tFQbmo8XBpcTRwM5yolrselxBiDue0z+WyGWjKVuhNK/Cxlt1X2iT+MBlsgI1xW+I611+7d4n9V57wPXlQ==", "dependencies": { - "@umbraco-ui/uui-base": "1.3.0" + "@umbraco-ui/uui-base": "1.4.0" } }, "node_modules/@umbraco-ui/uui-button-inline-create": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-button-inline-create/-/uui-button-inline-create-1.3.0.tgz", - "integrity": "sha512-QspinKWiMSG6umA0zzSEU6WQn3vjy/yWsasZBGqNV+Fi2/UOCiEoUhLtb2GUIASpe634Le3txgkEagypP5zsXA==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-button-inline-create/-/uui-button-inline-create-1.4.0.tgz", + "integrity": "sha512-pngszZKSk4uIaW0L06aBjBImKykxarNp7JTx6YJqi+rF+GXTS31/gRuckWN4pN0/BgUTJMd0Q11zVWfB0uwjvA==", "dependencies": { - "@umbraco-ui/uui-base": "1.3.0" + "@umbraco-ui/uui-base": "1.4.0" } }, "node_modules/@umbraco-ui/uui-card": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-card/-/uui-card-1.3.0.tgz", - "integrity": "sha512-SntgALMe99V0moXI/7PmrO4RV9rZaI5xSkymqsPwmwSKxz15hENY7eVW45ByRUMpd6xWv6cblhDWJhwu2CFyQA==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-card/-/uui-card-1.4.0.tgz", + "integrity": "sha512-eS5QdKzNqQQ+en3ZpPq88YGSWD1mSr4Nk9okpZ06fQmEZlYMMliR0A3WKFBQHhnleZafaEgHq3VwpVL1SQrluw==", "dependencies": { - "@umbraco-ui/uui-base": "1.3.0" + "@umbraco-ui/uui-base": "1.4.0" } }, "node_modules/@umbraco-ui/uui-card-content-node": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-card-content-node/-/uui-card-content-node-1.3.0.tgz", - "integrity": "sha512-NqP7B2gfxWeL8eHieV3IS3fMm6jyZuhZ7QeZbYYAkTOCPbE2MBJtkLX1Z5sypTmaL7fdzd3zqAM1dF+Gu108GA==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-card-content-node/-/uui-card-content-node-1.4.0.tgz", + "integrity": "sha512-8xbaSytLMsA7pXMKI4gttgiXjRgoQFh/pc3HzaQf3hKaWfeCPUxUaponXfZXmXjqMAi+eoyyxS1qeUt+Zlt0Rw==", "dependencies": { - "@umbraco-ui/uui-base": "1.3.0", - "@umbraco-ui/uui-card": "1.3.0", - "@umbraco-ui/uui-icon": "1.3.0" + "@umbraco-ui/uui-base": "1.4.0", + "@umbraco-ui/uui-card": "1.4.0", + "@umbraco-ui/uui-icon": "1.4.0" } }, "node_modules/@umbraco-ui/uui-card-media": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-card-media/-/uui-card-media-1.3.0.tgz", - "integrity": "sha512-F1+l7rLjpe5hiqmHIAqQRWi/T7lIQL4l65+DzS9UEeEJR1YDI/EZ3pITC0Bi6oTsrKpViIh9bJxHeIyw0G2qcQ==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-card-media/-/uui-card-media-1.4.0.tgz", + "integrity": "sha512-rQT4m0KFYMelEszFExFMYYNIBHHcYlDd0alqiKitEUBlpu2UXCHK7mXyQlU+sFWLJ262zSONMmwSaXsqhMLVug==", "dependencies": { - "@umbraco-ui/uui-base": "1.3.0", - "@umbraco-ui/uui-card": "1.3.0", - "@umbraco-ui/uui-symbol-file": "1.3.0", - "@umbraco-ui/uui-symbol-folder": "1.3.0" + "@umbraco-ui/uui-base": "1.4.0", + "@umbraco-ui/uui-card": "1.4.0", + "@umbraco-ui/uui-symbol-file": "1.4.0", + "@umbraco-ui/uui-symbol-folder": "1.4.0" } }, "node_modules/@umbraco-ui/uui-card-user": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-card-user/-/uui-card-user-1.3.0.tgz", - "integrity": "sha512-A9Lj3mqcv9p72RTwCffFBJUWLaLb/KwJYNecs0Z8XQhubO0dbgHqWhQxasvQb/xBHl49DSINbO80YWE6ViEm4Q==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-card-user/-/uui-card-user-1.4.0.tgz", + "integrity": "sha512-t7C7F1sFrxAizNZJG7JDu+Wk0vizm7lN8UZCNggPiua6AkVVDpH8YN013Tk/reKxfTp9PkYh9aVUeAyyhWYa4g==", "dependencies": { - "@umbraco-ui/uui-avatar": "1.3.0", - "@umbraco-ui/uui-base": "1.3.0", - "@umbraco-ui/uui-card": "1.3.0" + "@umbraco-ui/uui-avatar": "1.4.0", + "@umbraco-ui/uui-base": "1.4.0", + "@umbraco-ui/uui-card": "1.4.0" } }, "node_modules/@umbraco-ui/uui-caret": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-caret/-/uui-caret-1.3.0.tgz", - "integrity": "sha512-asEdTV7B83A6XwmOBznhQFp4ASAZw8GNcXXW6Z/ESNDnGY0rgVV0o8TaVK46k3egUQhWeChCayg7eymzXmaETQ==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-caret/-/uui-caret-1.4.0.tgz", + "integrity": "sha512-RtWgCSvFelya+E0INy95XDiLNYDH3Tv7AdMvUTUKf/5PKYp/yR5MYo70P9EvUkCVMvIFVf/VVGd9mDwvLr2k+A==", "dependencies": { - "@umbraco-ui/uui-base": "1.3.0" + "@umbraco-ui/uui-base": "1.4.0" } }, "node_modules/@umbraco-ui/uui-checkbox": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-checkbox/-/uui-checkbox-1.3.0.tgz", - "integrity": "sha512-s/2Am7d5E9YyvkHPlcfbd5zPUSDnJ1EgyDKlJKmTPGHYP1ebMcvwnz00a2EsGBAq8eZM+I6ffISZ5lPSPgNHMg==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-checkbox/-/uui-checkbox-1.4.0.tgz", + "integrity": "sha512-VCcYycChEPmaOo5q2QF1xsxxYQ5XToGh/z+46GmFyc5TDFP2OyOWqVm6+4gVpljcvf4aS9IRqcoONa/Bv2LQqQ==", "dependencies": { - "@umbraco-ui/uui-base": "1.3.0", - "@umbraco-ui/uui-boolean-input": "1.3.0", - "@umbraco-ui/uui-icon-registry-essential": "1.3.0" + "@umbraco-ui/uui-base": "1.4.0", + "@umbraco-ui/uui-boolean-input": "1.4.0", + "@umbraco-ui/uui-icon-registry-essential": "1.4.0" } }, "node_modules/@umbraco-ui/uui-color-area": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-color-area/-/uui-color-area-1.3.0.tgz", - "integrity": "sha512-JcM0WzRk5l21kb9F236ZWtiHy4QD7M8TdTLS2qy+C29BBNYtURE4rMe2HOXv6ebj4mxOWpYEB2vAme6K06CM7w==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-color-area/-/uui-color-area-1.4.0.tgz", + "integrity": "sha512-csIswxLN9YDhmL6veZ9iR8SjQrDi8wscPPJB0i7w4TQDI8TwlvB0mAdb86FM0eoobXLPFeMDFkYGQijWpv69Gw==", "dependencies": { - "@umbraco-ui/uui-base": "1.3.0", + "@umbraco-ui/uui-base": "1.4.0", "colord": "^2.9.3" } }, "node_modules/@umbraco-ui/uui-color-picker": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-color-picker/-/uui-color-picker-1.3.0.tgz", - "integrity": "sha512-RQ0UN29fYdy3zKBdoAJpNpfWGp0aXdG0OGVtZ7g3LKQy4UW6Fqih9do2V3AAd01ne1gq+ZiZrbu6d7aIgLuDow==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-color-picker/-/uui-color-picker-1.4.0.tgz", + "integrity": "sha512-zxOpmhEGEfQtLp/RYSPNBi8S2K+KjiuVyWhvmoqgO1gb/uNU5Om2xW1Q7pz/jiKe1qwWHO3whGl8LHM6el/C2w==", "dependencies": { - "@umbraco-ui/uui-base": "1.3.0", + "@umbraco-ui/uui-base": "1.4.0", "colord": "^2.9.3" } }, "node_modules/@umbraco-ui/uui-color-slider": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-color-slider/-/uui-color-slider-1.3.0.tgz", - "integrity": "sha512-8vUUJvx8nt75+C2fREpmXUUsQplLPt4k3VlpGZDg2RqkqriJL6KJyO9aUb7kHNPS6j6nvJD6ggsbSouB/oxFiw==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-color-slider/-/uui-color-slider-1.4.0.tgz", + "integrity": "sha512-XEgi6shSGCnB4LhQgalcWfsHXyC2oLGw0ZCANr9l/4LpjaoZ0Uq4H/CL8UFfwiLXbJWdzZwqQqJcP928QmUFYQ==", "dependencies": { - "@umbraco-ui/uui-base": "1.3.0" + "@umbraco-ui/uui-base": "1.4.0" } }, "node_modules/@umbraco-ui/uui-color-swatch": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-color-swatch/-/uui-color-swatch-1.3.0.tgz", - "integrity": "sha512-ycIs//+8sru/h0cvxhWusdObkyIsZ+Ohhj7FlsXhUILqFas2mYHoMXlaQWujB9NSVvjgi6aMBHQj1L1BsIa4VQ==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-color-swatch/-/uui-color-swatch-1.4.0.tgz", + "integrity": "sha512-/k1SgzfdA1sCueqDaGYXJyb+bZjMdffHgM4Qk5LMSjX3JDL+c6yKvoc/w2Bvky+9N1NUp+tEMbJKD7bzQalQlg==", "dependencies": { - "@umbraco-ui/uui-base": "1.3.0", - "@umbraco-ui/uui-icon-registry-essential": "1.3.0", + "@umbraco-ui/uui-base": "1.4.0", + "@umbraco-ui/uui-icon-registry-essential": "1.4.0", "colord": "^2.9.3" } }, "node_modules/@umbraco-ui/uui-color-swatches": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-color-swatches/-/uui-color-swatches-1.3.0.tgz", - "integrity": "sha512-uT2P4CFJ550owMKPupLniQX2xtk9Iv0UiVjLH4fmKQv98swGWSJhcUZO6AlV5aU5q8+qBaFvbwxfOCubXYrRdg==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-color-swatches/-/uui-color-swatches-1.4.0.tgz", + "integrity": "sha512-U6+0fu9OULPqRW0TuwVpj1PLectXM7ha2dc1Cw+rEzOtqBEbDmJTs4bh7EosMmxksmZQdXFhVkxu1yBHhXUJtQ==", "dependencies": { - "@umbraco-ui/uui-base": "1.3.0", - "@umbraco-ui/uui-color-swatch": "1.3.0" + "@umbraco-ui/uui-base": "1.4.0", + "@umbraco-ui/uui-color-swatch": "1.4.0" } }, "node_modules/@umbraco-ui/uui-combobox": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-combobox/-/uui-combobox-1.3.0.tgz", - "integrity": "sha512-qnG3gdx/CQ62VDX7AfxD1SA2QdwnXcryxWmIckw90Wp4kvFhdRzQa2cBieQU0Nyg5WAx7clBltVlSUkth/x9NA==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-combobox/-/uui-combobox-1.4.0.tgz", + "integrity": "sha512-epBlmRtVlUKeToA+DbYJYEWzTvKQahm2RnUMzFk9BvISP1xE9X5q7MtZLPRoiTjA9wf4SYrxIgHlYBGUOmy9lQ==", "dependencies": { - "@umbraco-ui/uui-base": "1.3.0", - "@umbraco-ui/uui-button": "1.3.0", - "@umbraco-ui/uui-combobox-list": "1.3.0", - "@umbraco-ui/uui-icon": "1.3.0", - "@umbraco-ui/uui-scroll-container": "1.3.0" + "@umbraco-ui/uui-base": "1.4.0", + "@umbraco-ui/uui-button": "1.4.0", + "@umbraco-ui/uui-combobox-list": "1.4.0", + "@umbraco-ui/uui-icon": "1.4.0", + "@umbraco-ui/uui-scroll-container": "1.4.0" } }, "node_modules/@umbraco-ui/uui-combobox-list": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-combobox-list/-/uui-combobox-list-1.3.0.tgz", - "integrity": "sha512-+RWexG+9XqExbxPWuwEAccdMGj/MkKmC3v/W8EybxO9LhVxzY51sJp36NouzCyGW6IfLkBPDRbFpk5tbLx9w3Q==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-combobox-list/-/uui-combobox-list-1.4.0.tgz", + "integrity": "sha512-T6fOqHcOSB/NxfUmjZHlNWUU1ct9eVghXdQpA4tcPE83HSfHhWS5F1nbE9Cr/LO/al2Fe8iFfub9ed9OOsNqdA==", "dependencies": { - "@umbraco-ui/uui-base": "1.3.0" + "@umbraco-ui/uui-base": "1.4.0" } }, "node_modules/@umbraco-ui/uui-css": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-css/-/uui-css-1.3.0.tgz", - "integrity": "sha512-btFsjkYo7q+1DHl5BXYdOZ3EKShSmOlcasfR5OEsoEb4houDthRfMCMMhwIppmLDjwarrHwP3vBlXi/feanC7g==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-css/-/uui-css-1.4.0.tgz", + "integrity": "sha512-HBCFPuXJijeZbjnjdqmg3oqOGB3RmpQKT/s/Uy0TSJfaQGfz0e73o2eRghYHWF2rdqHw6brKFrZTZHBVvCE/xA==", "dependencies": { "lit": "^2.2.2" } }, "node_modules/@umbraco-ui/uui-dialog": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-dialog/-/uui-dialog-1.3.0.tgz", - "integrity": "sha512-tHniS27WRj3uxDAZYvixOnQ5PBGi2ZZsGRi4zygPeO+kfLMNNlnuC4qmN+ppoUqz1pj89UtOUEHpQxqd6VckUA==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-dialog/-/uui-dialog-1.4.0.tgz", + "integrity": "sha512-FCrz17nKh2zybsDeN0AIxBQJjSFhK1q8OdZGSzaegPKx6R/xmZBPx6KPZeQnmjdGzQJHwh4xILKHXGazZbIZXA==", "dependencies": { - "@umbraco-ui/uui-base": "1.3.0", - "@umbraco-ui/uui-css": "1.3.0" + "@umbraco-ui/uui-base": "1.4.0", + "@umbraco-ui/uui-css": "1.4.0" } }, "node_modules/@umbraco-ui/uui-dialog-layout": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-dialog-layout/-/uui-dialog-layout-1.3.0.tgz", - "integrity": "sha512-gWGVtg5GimIUcMFSsvsrb9rSo1Fg9d5IkeG8oJBp5DGMgh2Hwqwvz2MF6qHg0CCgVyMq/77EoZchwuSsje1zCA==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-dialog-layout/-/uui-dialog-layout-1.4.0.tgz", + "integrity": "sha512-67/yVhysc+wMsyVEQXSP2E21YlzoQfir/CQjxCRlfKGe8FdCck/m3HSnzyb1rvPfbXrxGUMCUmcTqDBoazBfAw==", "dependencies": { - "@umbraco-ui/uui-base": "1.3.0" + "@umbraco-ui/uui-base": "1.4.0" } }, "node_modules/@umbraco-ui/uui-file-dropzone": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-file-dropzone/-/uui-file-dropzone-1.3.0.tgz", - "integrity": "sha512-037GMO6+CVNo6Y5UDM/PUP8U2rvbjpyFbIu/gpICG1YENjC6T/yJV0UgD6cXpDXuEEz5x8E4wIw7kFpzjCrJUw==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-file-dropzone/-/uui-file-dropzone-1.4.0.tgz", + "integrity": "sha512-pbNcTS7x7fvSyCrvR+yA7HzjWLtJXLHcLZvkJ4yNoAxS1d4/5ppyi/Fyz0QakBgLWzPuBv1mKj2o6RvBy29QWA==", "dependencies": { - "@umbraco-ui/uui-base": "1.3.0", - "@umbraco-ui/uui-symbol-file-dropzone": "1.3.0" + "@umbraco-ui/uui-base": "1.4.0", + "@umbraco-ui/uui-symbol-file-dropzone": "1.4.0" } }, "node_modules/@umbraco-ui/uui-file-preview": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-file-preview/-/uui-file-preview-1.3.0.tgz", - "integrity": "sha512-iV5HVY3PDcrILC/gANYJyCdoL2Jowb1yYbJZHBP1P7TTqUaGeYuYs3ipgHQ8YY0Yheo9MHknFSA/2kJaQsjOlw==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-file-preview/-/uui-file-preview-1.4.0.tgz", + "integrity": "sha512-UYi4Omww0/COjheTuAUdvZHqEAITT65Vsi5NSDHaUH3AM9BSVlj0FR3wOpwF7OwbOXjIeIonMEC8xMf1JtjusQ==", "dependencies": { - "@umbraco-ui/uui-base": "1.3.0", - "@umbraco-ui/uui-symbol-file": "1.3.0", - "@umbraco-ui/uui-symbol-file-thumbnail": "1.3.0", - "@umbraco-ui/uui-symbol-folder": "1.3.0" + "@umbraco-ui/uui-base": "1.4.0", + "@umbraco-ui/uui-symbol-file": "1.4.0", + "@umbraco-ui/uui-symbol-file-thumbnail": "1.4.0", + "@umbraco-ui/uui-symbol-folder": "1.4.0" } }, "node_modules/@umbraco-ui/uui-form": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-form/-/uui-form-1.3.0.tgz", - "integrity": "sha512-7raFihkOQslDzcnZefEoJ0JdPoKS5GWvHRGWl3Ib+HjCorj+TBFrtR/lwZ09iapCizj38C5Xd3t7hBJuWYBTXw==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-form/-/uui-form-1.4.0.tgz", + "integrity": "sha512-jjukKI+eoKmvw9Jc8n0ryle6gAA1ogQM3GLgId509qS9qiFGxMetMJ0KQjcRkrisRM/oQjz7huf9tF1es/prOQ==", "dependencies": { - "@umbraco-ui/uui-base": "1.3.0" + "@umbraco-ui/uui-base": "1.4.0" } }, "node_modules/@umbraco-ui/uui-form-layout-item": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-form-layout-item/-/uui-form-layout-item-1.3.0.tgz", - "integrity": "sha512-f7i1AXIwAYgiPkwsF5ugfaZkJko7X8COTUBGCrzFVV7eb0OmYC5BPKkoJ6zlbhX6lpgkuZvfHO+jqFoegXVqVQ==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-form-layout-item/-/uui-form-layout-item-1.4.0.tgz", + "integrity": "sha512-aHBfwq7Y0YAWVHpiXZ1lnwSXyLbsGdk7lPkJ6hqVaBJ77VA/N2oDGMUjsRcCd1vKtD8AA3Nc2kT2e++NlUIPDg==", "dependencies": { - "@umbraco-ui/uui-base": "1.3.0", - "@umbraco-ui/uui-form-validation-message": "1.3.0" + "@umbraco-ui/uui-base": "1.4.0", + "@umbraco-ui/uui-form-validation-message": "1.4.0" } }, "node_modules/@umbraco-ui/uui-form-validation-message": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-form-validation-message/-/uui-form-validation-message-1.3.0.tgz", - "integrity": "sha512-kHqwt8cf/p9gidAG9HTnSvur1ODycqUs4V2RRj8K78UAMN4Wb+VosmPhobtPNq38GbgthqGHEv85giWfFZ1omA==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-form-validation-message/-/uui-form-validation-message-1.4.0.tgz", + "integrity": "sha512-AZXcvusVb48H5YrPIj71iMMUOXn2pZtensi3fUj55sVY1RNFa+QuJW/vC/79qDBLw/vQJu3NcZGbi4q4NBKh9A==", "dependencies": { - "@umbraco-ui/uui-base": "1.3.0" + "@umbraco-ui/uui-base": "1.4.0" } }, "node_modules/@umbraco-ui/uui-icon": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-icon/-/uui-icon-1.3.0.tgz", - "integrity": "sha512-esxCauAnddOh5ws/GyKYX2Z1fUGluP8jXHOlRuYTBFLXQtSMFhl0YNieOhT/6B/RieyWF6AUzuKAxfL3YxUHEg==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-icon/-/uui-icon-1.4.0.tgz", + "integrity": "sha512-aLzVYbubk+VSI4iKHJSKFxlHMe9CGq5JbaUfuy9a9U/D7VfUUrroM+tDMPFP4qEvSkjthyCzdPBxodJ+QQOZew==", "dependencies": { - "@umbraco-ui/uui-base": "1.3.0" + "@umbraco-ui/uui-base": "1.4.0" } }, "node_modules/@umbraco-ui/uui-icon-registry": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-icon-registry/-/uui-icon-registry-1.3.0.tgz", - "integrity": "sha512-8YtCKCeg1FFPBMtV6TJu2ZdUjE5uUOdYxIjDhue0VKuia7JH7Rr/HlWGkU6/EduyWzVa+osyFWyIg8R/jj1Alw==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-icon-registry/-/uui-icon-registry-1.4.0.tgz", + "integrity": "sha512-76XXyxq96XIp4qIT58UgY4vp4+agD2YvfpCd+Dhs/rdu5iQq56PmYoxJ7qr7JYTSf8xxZ//0/PiuamwWkPmSEw==", "dependencies": { - "@umbraco-ui/uui-base": "1.3.0", - "@umbraco-ui/uui-icon": "1.3.0" + "@umbraco-ui/uui-base": "1.4.0", + "@umbraco-ui/uui-icon": "1.4.0" } }, "node_modules/@umbraco-ui/uui-icon-registry-essential": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-icon-registry-essential/-/uui-icon-registry-essential-1.3.0.tgz", - "integrity": "sha512-S/SO70kLf2sGKRbHcR1CVuIZm5L4Lzhv/sOD/tZjOSbSllNFlge1Aqg1ykBUxrSwdngtDhhvUkWucdfXdaSBkg==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-icon-registry-essential/-/uui-icon-registry-essential-1.4.0.tgz", + "integrity": "sha512-o1woHz7YFjyOBIQHsdoCxE3vpXrJ/Sj0QNcGexdlFqUsvv/LhHAJ9a88cmTve1Y8nYDWW2pyyKZbyX1nDokByg==", "dependencies": { - "@umbraco-ui/uui-base": "1.3.0", - "@umbraco-ui/uui-icon-registry": "1.3.0" + "@umbraco-ui/uui-base": "1.4.0", + "@umbraco-ui/uui-icon-registry": "1.4.0" } }, "node_modules/@umbraco-ui/uui-input": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-input/-/uui-input-1.3.0.tgz", - "integrity": "sha512-ebowNUrRePh0/tc9Wm5s5+VL8PL6a9Q6o8c4TsnjLCPE0RiVDFI0xsepZx0R+kzRJdOw+FRDCUFbpx++0QMEAQ==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-input/-/uui-input-1.4.0.tgz", + "integrity": "sha512-mtlONZWmLV5OOYt2APhjl9cukTktrWNl1w4yF889F/wO2ZiGasBWwL9amtW4RIby/5nxns9yGgzXXG1/6GaqYw==", "dependencies": { - "@umbraco-ui/uui-base": "1.3.0" + "@umbraco-ui/uui-base": "1.4.0" } }, "node_modules/@umbraco-ui/uui-input-file": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-input-file/-/uui-input-file-1.3.0.tgz", - "integrity": "sha512-JzG9K8xN1RbYPdlbvVW93Z/zWKUFXIgolG7euu7vJXY2MvNxjlpq4UKTmL/O4d7cb+JKj7MefXLjkqpJccMFgg==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-input-file/-/uui-input-file-1.4.0.tgz", + "integrity": "sha512-qdRce6NA6VDgFR71hUhuasX28N4qmCtWscWwoU+2E/rxfYWd2MIFOSsBqnIW6R4wagw+LnC7YXV6oy4vZiCKuQ==", "dependencies": { - "@umbraco-ui/uui-action-bar": "1.3.0", - "@umbraco-ui/uui-base": "1.3.0", - "@umbraco-ui/uui-button": "1.3.0", - "@umbraco-ui/uui-file-dropzone": "1.3.0", - "@umbraco-ui/uui-icon": "1.3.0", - "@umbraco-ui/uui-icon-registry-essential": "1.3.0" + "@umbraco-ui/uui-action-bar": "1.4.0", + "@umbraco-ui/uui-base": "1.4.0", + "@umbraco-ui/uui-button": "1.4.0", + "@umbraco-ui/uui-file-dropzone": "1.4.0", + "@umbraco-ui/uui-icon": "1.4.0", + "@umbraco-ui/uui-icon-registry-essential": "1.4.0" } }, "node_modules/@umbraco-ui/uui-input-lock": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-input-lock/-/uui-input-lock-1.3.0.tgz", - "integrity": "sha512-Sz7BYv6Q4bV7QuLIZKpNsHXWdYCxr+Xhgg74HIbwEvFwVHyUOBnd5CPgv8my/IEXkMNBhCblU47cfT7L0UEytA==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-input-lock/-/uui-input-lock-1.4.0.tgz", + "integrity": "sha512-LK9jgCmSJFENRA+Hj7qnwhuhuYmMgWYPc44LMYdowqTKlkffr67mY2VqaK+92WbjmH8PKStJr0wf0L8tuEczWQ==", "dependencies": { - "@umbraco-ui/uui-base": "1.3.0", - "@umbraco-ui/uui-button": "1.3.0", - "@umbraco-ui/uui-icon": "1.3.0", - "@umbraco-ui/uui-input": "1.3.0" + "@umbraco-ui/uui-base": "1.4.0", + "@umbraco-ui/uui-button": "1.4.0", + "@umbraco-ui/uui-icon": "1.4.0", + "@umbraco-ui/uui-input": "1.4.0" } }, "node_modules/@umbraco-ui/uui-input-password": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-input-password/-/uui-input-password-1.3.0.tgz", - "integrity": "sha512-9MXaJ5wnXacmSaAvbDldIDkWJ0YhnOqeG4UqxPKO6yn1B6L6IvrH5jVaXcu3U7eYsRzE/M3veKEnmH0MfgLNLQ==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-input-password/-/uui-input-password-1.4.0.tgz", + "integrity": "sha512-slxRycyh8okgl6vH89O/y9lWPkfrga6s3Myijz4RXnprWfVtntIkB5pZoM17yT9bjSfo15UKd4E4GdOS9YpcaQ==", "dependencies": { - "@umbraco-ui/uui-base": "1.3.0", - "@umbraco-ui/uui-icon-registry-essential": "1.3.0", - "@umbraco-ui/uui-input": "1.3.0" + "@umbraco-ui/uui-base": "1.4.0", + "@umbraco-ui/uui-icon-registry-essential": "1.4.0", + "@umbraco-ui/uui-input": "1.4.0" } }, "node_modules/@umbraco-ui/uui-keyboard-shortcut": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-keyboard-shortcut/-/uui-keyboard-shortcut-1.3.0.tgz", - "integrity": "sha512-cF7PoKSiX+UZqvWUYKg/0f7SJnBW3mYYMRLVr+9FgCLhN1bl8W4U68B6YTOyMEBUSLSBh4laSudt1FRztZGCDA==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-keyboard-shortcut/-/uui-keyboard-shortcut-1.4.0.tgz", + "integrity": "sha512-3hTFxrilMW7hGwfFtsNUmJdF0e4wk5pM8oGMuwwkKxsuxMdGzdpmht0PnB3G0EPQAsA60Xypiuvm0EgFnX91zg==", "dependencies": { - "@umbraco-ui/uui-base": "1.3.0" + "@umbraco-ui/uui-base": "1.4.0" } }, "node_modules/@umbraco-ui/uui-label": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-label/-/uui-label-1.3.0.tgz", - "integrity": "sha512-enjog0wvQQG+pgaJWQyYrP9HSGrIrFWFfb9nJzRMDGAAZKp+j6ph+NfYfAgI525CZySLs32VgZsN3DZUOeEdKQ==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-label/-/uui-label-1.4.0.tgz", + "integrity": "sha512-XTKH92Z0Apu15qI3MvJew1z3oAyOVBgByIipxVmWPb52Nlvj/Haa8QUlfksJWp4E4c2IhhYTPVXeft8CpS2q1w==", "dependencies": { - "@umbraco-ui/uui-base": "1.3.0" + "@umbraco-ui/uui-base": "1.4.0" } }, "node_modules/@umbraco-ui/uui-loader": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-loader/-/uui-loader-1.3.0.tgz", - "integrity": "sha512-l5l3fACQ3pI3F+iWUTLhQtDuySoM9UYo6Wp3jAYI8G4Bk2ACDrmW4ueL+SnxamgL4mdpr6stJx+9RtJUGjS+Dg==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-loader/-/uui-loader-1.4.0.tgz", + "integrity": "sha512-5KgUdzusuJeMwgIwtScuqgMnJ9NW+/G0/Osj3B20UBPwcwVm1z4s3cWlt3kKJmPA/W4fzbdTxRt2MRdSEp3+cw==", "dependencies": { - "@umbraco-ui/uui-base": "1.3.0" + "@umbraco-ui/uui-base": "1.4.0" } }, "node_modules/@umbraco-ui/uui-loader-bar": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-loader-bar/-/uui-loader-bar-1.3.0.tgz", - "integrity": "sha512-+2TgZUKQB8+cP4suvqsVWjTLiKAp6CpDHJS9WanoMwNh6gdzY5cG3DeUB6toUb/ieX2meKSdqZNIDkwdLpamlw==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-loader-bar/-/uui-loader-bar-1.4.0.tgz", + "integrity": "sha512-n+sxqJxp1aKy7lF8rbB9a72OzcdhTuHif5bR2XD2NwMmEZ7jl6xd+Em+sHo35ePqqmualpwetM2DlO50/uTDgg==", "dependencies": { - "@umbraco-ui/uui-base": "1.3.0" + "@umbraco-ui/uui-base": "1.4.0" } }, "node_modules/@umbraco-ui/uui-loader-circle": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-loader-circle/-/uui-loader-circle-1.3.0.tgz", - "integrity": "sha512-FReFiC7Dk3H3WpSyMNSEaSNPjD2tistAeAYkX6/b4sqg24iP96ryb9lg9jMelSNIqjSdHk9a4TMEmbCxAo8V/g==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-loader-circle/-/uui-loader-circle-1.4.0.tgz", + "integrity": "sha512-mFQB6psm9W4U/g9KEPPoUNFeEju2k/oJ+J5I1g0fz20HpfvDKIoebqErcCd0wngZfk4FZm1ditpN3t2eFGBR4A==", "dependencies": { - "@umbraco-ui/uui-base": "1.3.0" + "@umbraco-ui/uui-base": "1.4.0" } }, "node_modules/@umbraco-ui/uui-menu-item": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-menu-item/-/uui-menu-item-1.3.0.tgz", - "integrity": "sha512-jrvMnD/8peTbxzSvYXIeuLmODBYD3pFWn4cEm2VN/yFAA1w/0rQztBgZKOMMQZ0jnlP/jtREWkAfPV7MZ9bPWA==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-menu-item/-/uui-menu-item-1.4.0.tgz", + "integrity": "sha512-GqrjrUlzQzbctDzzg1X0fVRO4Yxll/H5oqnXZBuDZBpqu++AlXknqMuAjur0cFkeiV0Kn7N9w+uZl1NYWW9OJA==", "dependencies": { - "@umbraco-ui/uui-base": "1.3.0", - "@umbraco-ui/uui-loader-bar": "1.3.0", - "@umbraco-ui/uui-symbol-expand": "1.3.0" + "@umbraco-ui/uui-base": "1.4.0", + "@umbraco-ui/uui-loader-bar": "1.4.0", + "@umbraco-ui/uui-symbol-expand": "1.4.0" } }, "node_modules/@umbraco-ui/uui-modal": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-modal/-/uui-modal-1.3.0.tgz", - "integrity": "sha512-FyqnJ2igRjNsnXI8odGirTENMqlwz/5+zO9R5Dsv1XG/sazirGo0o+JGnY4Za4MYNwSpZq5OfKjvXDVkd7jIZQ==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-modal/-/uui-modal-1.4.0.tgz", + "integrity": "sha512-v+jiYIGCLTL4NY+Td5UIgoK52pxGVWxWEe+xxNJLYSUtiRsp+7dw9UwmNqLdflR3ngfyBVY+rfEXSfbcfjiQdA==", "dependencies": { - "@umbraco-ui/uui-base": "1.3.0" + "@umbraco-ui/uui-base": "1.4.0" } }, "node_modules/@umbraco-ui/uui-pagination": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-pagination/-/uui-pagination-1.3.0.tgz", - "integrity": "sha512-zqlYVFjsFQX8lZwOAa7AbjtX1r8kbjj2czC7mth4ZomNI02DERXqBFM445h+9ALiM3pgrZS3qgnD1BlNzQMjtQ==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-pagination/-/uui-pagination-1.4.0.tgz", + "integrity": "sha512-f38AuUTyZ5/JNWZFU02EzAaQ81R3sa38jClSjyDScQ9Vh+8Uwj16sRPnbnveFWU/c5URVMFpG5OGXA/RXI3WEg==", "dependencies": { - "@umbraco-ui/uui-base": "1.3.0", - "@umbraco-ui/uui-button": "1.3.0", - "@umbraco-ui/uui-button-group": "1.3.0" + "@umbraco-ui/uui-base": "1.4.0", + "@umbraco-ui/uui-button": "1.4.0", + "@umbraco-ui/uui-button-group": "1.4.0" } }, "node_modules/@umbraco-ui/uui-popover": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-popover/-/uui-popover-1.3.0.tgz", - "integrity": "sha512-mNuFMHGtRhtPMDDDVTOGw1yJJB7ZD6ws6jMAMkEWMMVNXT34mcZZTAd7V+9IdWaBdfZkBkT+5KbVr5nQsRfNzw==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-popover/-/uui-popover-1.4.0.tgz", + "integrity": "sha512-jbYHUGoN1S81VU4TbUh0HKipGcCnqiwINtQNDGf2W61Rgy++wBR3MfWqCaXd1K10GL8+wgkly6RsJKKUzqrDNw==", "dependencies": { - "@umbraco-ui/uui-base": "1.3.0" + "@umbraco-ui/uui-base": "1.4.0" } }, "node_modules/@umbraco-ui/uui-progress-bar": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-progress-bar/-/uui-progress-bar-1.3.0.tgz", - "integrity": "sha512-ZvGZc/RiBqPi3zG6UiBth6eFw1hL8u8qz6OwG8Mvmooj7D5Oc9+sRjeZLQMW2bUl1lKb5j1qLeJ4lRyJvPH+1g==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-progress-bar/-/uui-progress-bar-1.4.0.tgz", + "integrity": "sha512-lJdoxiJMaDl7Qsaa6TkeuiudWV7Zer1LjWS9yO0aAZ4xWkkVxLf89qIlaTukdOat+Sr8ZtI2mjmRih5IjMdalg==", "dependencies": { - "@umbraco-ui/uui-base": "1.3.0" + "@umbraco-ui/uui-base": "1.4.0" } }, "node_modules/@umbraco-ui/uui-radio": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-radio/-/uui-radio-1.3.0.tgz", - "integrity": "sha512-ip6nABwf8demHpt6WCYML1Ug1XSzYUlqk8Qq8ZthlogYunqasCSp6A2VD8RO8RYO5Qyr0hkUxI5oTTDNFuk5qg==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-radio/-/uui-radio-1.4.0.tgz", + "integrity": "sha512-pIJjmzWRIKPDxzwmB4CbBJNmMhlB97NOcgMoiIruiacVGEfZTWqXYXAkNtMragYGVQ0oz+ySYxEgl4iVvg2tdw==", "dependencies": { - "@umbraco-ui/uui-base": "1.3.0" + "@umbraco-ui/uui-base": "1.4.0" } }, "node_modules/@umbraco-ui/uui-range-slider": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-range-slider/-/uui-range-slider-1.3.0.tgz", - "integrity": "sha512-ZnIaby8phrkpwFVS5nhuh7gu045mRDPAihDgRQE7WhOcL1r1G+xYTvd32FVTh0QruTQOa7lUp9BjMNzd+DVUnA==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-range-slider/-/uui-range-slider-1.4.0.tgz", + "integrity": "sha512-3rGrXEAOfztQHvD8aJlGuBfe0tXkpZgWtzq888D+8X68RMvPHs89X32FVqT8e34kK1/vfm8I7BwbDSXL6FTzbg==", "dependencies": { - "@umbraco-ui/uui-base": "1.3.0" + "@umbraco-ui/uui-base": "1.4.0" } }, "node_modules/@umbraco-ui/uui-ref": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref/-/uui-ref-1.3.0.tgz", - "integrity": "sha512-mt27HH4NL+r9cL4wYO0NDobMyUczGOqNc88oumSblAs+3ex8HHz5SX8GYi05zdndbJATxHJmh9Q5op3bE4SKPw==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref/-/uui-ref-1.4.0.tgz", + "integrity": "sha512-zESd9N+72zON+kLCv95zzQtfmFY10zJU9DzzLR0GdZouujtyysU5qIwJG+dTy5ewm1jzGq5DHAyJtwO6IQSx7Q==", "dependencies": { - "@umbraco-ui/uui-base": "1.3.0" + "@umbraco-ui/uui-base": "1.4.0" } }, "node_modules/@umbraco-ui/uui-ref-list": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-list/-/uui-ref-list-1.3.0.tgz", - "integrity": "sha512-KProeP3ielc1aX1WYqM0etuilvVgfjFnj5d1AufRLeFcnojJZWqwl0QX7E5yPmCTBChsU/SQA4ab3lROCJH57Q==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-list/-/uui-ref-list-1.4.0.tgz", + "integrity": "sha512-r8X0dSUsbvbyvK+2Yy7jsaCE4Q+PV7CDGQAO1eArYywCuJWjdVO18zt26Abvl1Z+v5qAWnbPiJHvF0h6mYTGMg==", "dependencies": { - "@umbraco-ui/uui-base": "1.3.0" + "@umbraco-ui/uui-base": "1.4.0" } }, "node_modules/@umbraco-ui/uui-ref-node": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node/-/uui-ref-node-1.3.0.tgz", - "integrity": "sha512-NANRP3BKLOzxRFl0ZxD4XAkCRf7KaQVN/hytyQdlmhKaTjnXXllgd8Cy7795VwK6PAOBqzDwWh4KGC6MpoZpSQ==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node/-/uui-ref-node-1.4.0.tgz", + "integrity": "sha512-Jc8ews6mC9au4gUvzjRYfTeQWgFkrSICcsxd1oPz1qxVsyXWk66b3tWjAwkyjWwI13EOp4YoGK9QsPXbQKeTvg==", "dependencies": { - "@umbraco-ui/uui-base": "1.3.0", - "@umbraco-ui/uui-icon": "1.3.0", - "@umbraco-ui/uui-ref": "1.3.0" + "@umbraco-ui/uui-base": "1.4.0", + "@umbraco-ui/uui-icon": "1.4.0", + "@umbraco-ui/uui-ref": "1.4.0" } }, "node_modules/@umbraco-ui/uui-ref-node-data-type": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-data-type/-/uui-ref-node-data-type-1.3.0.tgz", - "integrity": "sha512-v2GkM8FPorlHcBXWTTfiMsDPPvEfJYP/yG7v+7WPcQ8zV3inUIAiYKCBD7a/z9UIlndDMpCt29UeCSo58qA7fQ==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-data-type/-/uui-ref-node-data-type-1.4.0.tgz", + "integrity": "sha512-tcuRnbYJxV8X3/ezP1gQ/DY2Vy9f+TDB/HFKtsNp+n891zShRbcEQ1As/fOoXGtM2JVAJ7VUYboyMhJ195hBVw==", "dependencies": { - "@umbraco-ui/uui-base": "1.3.0", - "@umbraco-ui/uui-ref-node": "1.3.0" + "@umbraco-ui/uui-base": "1.4.0", + "@umbraco-ui/uui-ref-node": "1.4.0" } }, "node_modules/@umbraco-ui/uui-ref-node-document-type": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-document-type/-/uui-ref-node-document-type-1.3.0.tgz", - "integrity": "sha512-U+uGFDFyLQGFLe9EcrF+6yshp5+6QhmGPbcS6kAjt52MKN3iRMV4H9mJTltvbHCjF/jUIfu/MRaQLDBWVXD4HA==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-document-type/-/uui-ref-node-document-type-1.4.0.tgz", + "integrity": "sha512-pWESJsEm+Dect5kUws+sse0Xj8Z9+ZZkR1ZaeTHDL3kPMLxD6wMfMwWJtMeAIh7OvqJY0B/ldLonTof/ysebdA==", "dependencies": { - "@umbraco-ui/uui-base": "1.3.0", - "@umbraco-ui/uui-ref-node": "1.3.0" + "@umbraco-ui/uui-base": "1.4.0", + "@umbraco-ui/uui-ref-node": "1.4.0" } }, "node_modules/@umbraco-ui/uui-ref-node-form": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-form/-/uui-ref-node-form-1.3.0.tgz", - "integrity": "sha512-3ECrFJKAzn+I67sPwAX8YYcJadGq5azeg1QLi76FscrXvCXCcAoO36dpObvjwW5I1FDZGs42XkoUNYl5Tg7xpw==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-form/-/uui-ref-node-form-1.4.0.tgz", + "integrity": "sha512-Xd17jQycvjq5TGfxkTZr+Kb/OU/lsUPkh4ft8/V4W/p0xv4sTio6txPw0bjDDcjJ/75zuHOLyTYicmcchcjXbA==", "dependencies": { - "@umbraco-ui/uui-base": "1.3.0", - "@umbraco-ui/uui-ref-node": "1.3.0" + "@umbraco-ui/uui-base": "1.4.0", + "@umbraco-ui/uui-ref-node": "1.4.0" } }, "node_modules/@umbraco-ui/uui-ref-node-member": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-member/-/uui-ref-node-member-1.3.0.tgz", - "integrity": "sha512-gqriNy9K0fF7xaV9BgOX7ooNuD/1o9gyJGhaa4JNua2w60HSAoaHKewEndRrZvplYhnMSfk5tdxQhdH9u5XJVw==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-member/-/uui-ref-node-member-1.4.0.tgz", + "integrity": "sha512-Lhpsh1CAwQRKOaR4tPkXBBZN3fjuEJMENlVHDB2UmmSJvFozl2byEWX4dEHwvPQpe0cbU8lE0By8iNDaEbl7Qw==", "dependencies": { - "@umbraco-ui/uui-base": "1.3.0", - "@umbraco-ui/uui-ref-node": "1.3.0" + "@umbraco-ui/uui-base": "1.4.0", + "@umbraco-ui/uui-ref-node": "1.4.0" } }, "node_modules/@umbraco-ui/uui-ref-node-package": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-package/-/uui-ref-node-package-1.3.0.tgz", - "integrity": "sha512-yRTDubukJ9JqOm8g90NldxFlvzqGU+mEpQWRfZ0pNlggo+xLn7kj+1NNazVHl3J5M2D81xlaVnwTdAj8TGwA+w==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-package/-/uui-ref-node-package-1.4.0.tgz", + "integrity": "sha512-FQgAZ8NOjBVUWLyDg93pg6bqgONcM275qbqM3Htd+JMmmYcoYii/oTXlBqhGq7+9eDhcb8tGko2RN/tH9p8KSQ==", "dependencies": { - "@umbraco-ui/uui-base": "1.3.0", - "@umbraco-ui/uui-ref-node": "1.3.0" + "@umbraco-ui/uui-base": "1.4.0", + "@umbraco-ui/uui-ref-node": "1.4.0" } }, "node_modules/@umbraco-ui/uui-ref-node-user": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-user/-/uui-ref-node-user-1.3.0.tgz", - "integrity": "sha512-GhOZdOtDsqIvAjIFhy4jJ2RVt6yGvVBA50tM6BrszIsC28fNGYnDKVmmC3FHS/Va9omVqnF+jeKRpBFxOt06Rg==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-user/-/uui-ref-node-user-1.4.0.tgz", + "integrity": "sha512-dmp44LDXJbnupP8dnUpAMSPCU2+udhMSE9uQDx1hfmX08Q49Phw6R4Az9h1ESh5uSxSm6UEb/Y7JEblods7C3w==", "dependencies": { - "@umbraco-ui/uui-base": "1.3.0", - "@umbraco-ui/uui-ref-node": "1.3.0" + "@umbraco-ui/uui-base": "1.4.0", + "@umbraco-ui/uui-ref-node": "1.4.0" } }, "node_modules/@umbraco-ui/uui-scroll-container": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-scroll-container/-/uui-scroll-container-1.3.0.tgz", - "integrity": "sha512-HQ0U45D5opkrN4NTAJfP0vlkMfXTWXfR2Gj2NW4P7CPzV1JyQUB5opDhNmvmQwY7kd+tMjKY+gHGmTM1nKa7uQ==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-scroll-container/-/uui-scroll-container-1.4.0.tgz", + "integrity": "sha512-/Rfqjtw+9LCCjvxl/MEmAjVfn4+aE8elfZ77EoItbF79R8WVmoJsIJUezjFp/Hvtp51PsgVgu/Da94dxTR4QBA==", "dependencies": { - "@umbraco-ui/uui-base": "1.3.0" + "@umbraco-ui/uui-base": "1.4.0" } }, "node_modules/@umbraco-ui/uui-select": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-select/-/uui-select-1.3.0.tgz", - "integrity": "sha512-hO2zBFS9ahSSqSoO1K2vup2124NKIV4vYuM4TpGl9kt2t0wzBVK153amuib7YhfTGpuQwEYRUzWc6TTbJnTkzw==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-select/-/uui-select-1.4.0.tgz", + "integrity": "sha512-bvdVIGot2vWiuoQmQL9dCriY8KnmpqLyn0q6FCvx7xGAl9nFBn1MfZFbs4INxriIGWjq17YFvUXklTWuhMLGTA==", "dependencies": { - "@umbraco-ui/uui-base": "1.3.0" + "@umbraco-ui/uui-base": "1.4.0" } }, "node_modules/@umbraco-ui/uui-slider": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-slider/-/uui-slider-1.3.0.tgz", - "integrity": "sha512-+FoUxEzl3S+R1nox4k3OOGV2Z7uT0d/YCEqjrof+gTZl+1DNxgGIrv2wvujp5kGnYwVMdpyeOnsg82Hngk4zvw==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-slider/-/uui-slider-1.4.0.tgz", + "integrity": "sha512-eDTIcXhYAiMSpPwI5e5gnMMpr0zOpx8te8pxF6K2YrGo8mCO2CI1zXZTzuv7e4ImL4HLmLoph8kbk+/wlrEtLw==", "dependencies": { - "@umbraco-ui/uui-base": "1.3.0" + "@umbraco-ui/uui-base": "1.4.0" } }, "node_modules/@umbraco-ui/uui-symbol-expand": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-expand/-/uui-symbol-expand-1.3.0.tgz", - "integrity": "sha512-4n/qV+0uys+zFw1tyzjeN8wNMiafdxw9iAqx/bW9oYy52x0SdzuhvqJSFyQMxopAqnfLuBQnCR9rqzW+F2UIVA==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-expand/-/uui-symbol-expand-1.4.0.tgz", + "integrity": "sha512-vSWRYiUwTjERuWtbiAW7IB49s57bqjN2XrSmCrOtyS9i4t5jIjsZ11If97WD+gQI/tt+khQZ85oPWNcj6C3eVQ==", "dependencies": { - "@umbraco-ui/uui-base": "1.3.0" + "@umbraco-ui/uui-base": "1.4.0" } }, "node_modules/@umbraco-ui/uui-symbol-file": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-file/-/uui-symbol-file-1.3.0.tgz", - "integrity": "sha512-00W6Dq/Kko8bWRU7StxDWZ0NtY4T9qkAdU3MY1WXb4gqEKSXeHZjG+I2Mm1zfcnz69W4Jo5APKU96Oo2xup0XA==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-file/-/uui-symbol-file-1.4.0.tgz", + "integrity": "sha512-wGmdw47jXjIcjpThf/TZ+6EZh+aQwqBA/1SMlgTtNBbUZDSy77NZ0pOWw8SaXzKqRrDqgFqIZukb7MILio3fwQ==", "dependencies": { - "@umbraco-ui/uui-base": "1.3.0" + "@umbraco-ui/uui-base": "1.4.0" } }, "node_modules/@umbraco-ui/uui-symbol-file-dropzone": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-file-dropzone/-/uui-symbol-file-dropzone-1.3.0.tgz", - "integrity": "sha512-g640sHcxzx4XEIkfToKvNyYqzfnwHXmbySm1HDrf7oJBoBvcTYk7zkDcOn6meWRzG08QfrlyOBmgINYv9f9Mtw==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-file-dropzone/-/uui-symbol-file-dropzone-1.4.0.tgz", + "integrity": "sha512-GftR6cK+9kbY43fV9a3+ICJX0rn8iT6SEe9vt85Uu4JMi2GCOT3TnKnIxgXRP3u9SyHhMNMiWmSgRfLpgJ2v/w==", "dependencies": { - "@umbraco-ui/uui-base": "1.3.0" + "@umbraco-ui/uui-base": "1.4.0" } }, "node_modules/@umbraco-ui/uui-symbol-file-thumbnail": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-file-thumbnail/-/uui-symbol-file-thumbnail-1.3.0.tgz", - "integrity": "sha512-5R1gUqr6+oT1w7mSYOZHszGp0MUbDYSbi3aJOrntB+eu9qX6h4ON5bHFDcQTb4+ysJhuqBprJL3hmhkd9KiD4g==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-file-thumbnail/-/uui-symbol-file-thumbnail-1.4.0.tgz", + "integrity": "sha512-Xlu7NQ88AiQI9kfKOQKi1kH0zMkop7GqtGyuIXbnt7rM3EZfioTdltW1NvqgKzc2QpZPqMY1s449hravObHUUg==", "dependencies": { - "@umbraco-ui/uui-base": "1.3.0" + "@umbraco-ui/uui-base": "1.4.0" } }, "node_modules/@umbraco-ui/uui-symbol-folder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-folder/-/uui-symbol-folder-1.3.0.tgz", - "integrity": "sha512-6ixquiF47WAxMx0hdmsr8NDwgrJaiqjxqhGl1P5rIGI+XVsuf0IY/tZKttd+ZqQC87ktzx9lX2kLip0DicU0lg==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-folder/-/uui-symbol-folder-1.4.0.tgz", + "integrity": "sha512-/cpV6Br3bOZkOh6YNr5PbIA/+NKKjyj1PkJwITSGm5/TnW2a4J5nzJTVn5ez7IjId176loRDZM2w05bemRavmA==", "dependencies": { - "@umbraco-ui/uui-base": "1.3.0" + "@umbraco-ui/uui-base": "1.4.0" } }, "node_modules/@umbraco-ui/uui-symbol-lock": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-lock/-/uui-symbol-lock-1.3.0.tgz", - "integrity": "sha512-JyoOT7HYH4j2NrubKKjqkBtJZMsFuNe4P1k9YXkDYoh8bb9uJ6Zvf/9j4bTC9DlqztI2aqGmGWzduA7XY2RGwA==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-lock/-/uui-symbol-lock-1.4.0.tgz", + "integrity": "sha512-BUPxOwhjjl4GVixbbGkKOPi9FI+C1fr1cy5NT2uLNY64z5r3jFzbnHMySKGzvpfig8wD+1hsuSPGP3lypzknOQ==", "dependencies": { - "@umbraco-ui/uui-base": "1.3.0" + "@umbraco-ui/uui-base": "1.4.0" } }, "node_modules/@umbraco-ui/uui-symbol-more": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-more/-/uui-symbol-more-1.3.0.tgz", - "integrity": "sha512-LuNr72QaOCA18jm7yEMigC+nEDWnRc27AaRv3NiP1QdRXmzq5CaYFnxU0b/+9A00xZNFsysnDruHNzyWgEzdFw==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-more/-/uui-symbol-more-1.4.0.tgz", + "integrity": "sha512-fx125CCeBY+sspQpWITYt79AKYZ11NFaa72Zquz8cxH+hQA1z32jOUDL+m6oF3jTYwQkKQlCoff3VnOaJ91VyA==", "dependencies": { - "@umbraco-ui/uui-base": "1.3.0" + "@umbraco-ui/uui-base": "1.4.0" } }, "node_modules/@umbraco-ui/uui-symbol-sort": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-sort/-/uui-symbol-sort-1.3.0.tgz", - "integrity": "sha512-4iiyqjSr0ZO8VY4zqCJLQMXSlF8J18D0+jzMZFKMZk5QGw0dAGdF1Zy8vEoygXvIiiytbg4Sik+ehhgsF5o5vQ==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-sort/-/uui-symbol-sort-1.4.0.tgz", + "integrity": "sha512-pVeT7qrKhRK8NUX3IDodSK0GNAKOKyWyzRhrxKrDT7wRuMManKmAK6WAVYpLaRqO+PRF8+NljfoCOEtJAHlGUg==", "dependencies": { - "@umbraco-ui/uui-base": "1.3.0" + "@umbraco-ui/uui-base": "1.4.0" } }, "node_modules/@umbraco-ui/uui-table": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-table/-/uui-table-1.3.0.tgz", - "integrity": "sha512-po0znA7Zv6xF78bm3A6/CW+aclVXcjFVahGxVnYE0F7RlshaKnw5TpQByRTw4U2M/HMi1RLKrutBOf/j/pm+EA==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-table/-/uui-table-1.4.0.tgz", + "integrity": "sha512-wpEqTmUQrAWjloeHZQqzAt5HR+j5ihMJusHpqZmY4076LcvnmpZHPhtmwpIzosZNqRq2N1rbrPIyEotlzSg9Fw==", "dependencies": { - "@umbraco-ui/uui-base": "1.3.0" + "@umbraco-ui/uui-base": "1.4.0" } }, "node_modules/@umbraco-ui/uui-tabs": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-tabs/-/uui-tabs-1.3.0.tgz", - "integrity": "sha512-zNzm6ktAp3QykOT+4/9dzl4IZGSC2dH0yV9p8DRFFeEppv/J26S29WPLK9EHwa5/fZPLpKiGQ1sqQB/z1Oa7wQ==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-tabs/-/uui-tabs-1.4.0.tgz", + "integrity": "sha512-RWoLJHwMb9MbKqMyuyz3DaSc9ZGCa/NBtgBDpKpn/8oolbmNYBnr9e4sabHARtqfsEWFWKWP3kUw9iTQZNa0oA==", "dependencies": { - "@umbraco-ui/uui-base": "1.3.0" + "@umbraco-ui/uui-base": "1.4.0" } }, "node_modules/@umbraco-ui/uui-tag": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-tag/-/uui-tag-1.3.0.tgz", - "integrity": "sha512-R4yU8LoWDV8Qkvy0mwo1yjBcOrh0RvDfTa5CSJmTgGrUhWLPNR5ZpDiqu+5Ntr0nkHhT4LF76EkQhvGWleQglg==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-tag/-/uui-tag-1.4.0.tgz", + "integrity": "sha512-9R+WJrJav780ZoA+dbZb7bHYazxrHxADnLdNOHoLvNyggLyxIT/SRsSxrP3x9zFRwbcRLZ8MRxQ3I32YiWacKw==", "dependencies": { - "@umbraco-ui/uui-base": "1.3.0" + "@umbraco-ui/uui-base": "1.4.0" } }, "node_modules/@umbraco-ui/uui-textarea": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-textarea/-/uui-textarea-1.3.0.tgz", - "integrity": "sha512-eUjPkWu99Y7uygu2pZdXH+S86qWIN8RuBz5Aaz+OQPvMQKD0CE6zjx/Zv1qMf60TbPhdcRHGV+2chpvlCjDKEA==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-textarea/-/uui-textarea-1.4.0.tgz", + "integrity": "sha512-nd6kWBmAvWaNLmXbEhfLRnWMfAp8rkll7XtHec9W32EQJwcHlYrS3wga6Xu32d3rKb3zUg+VXHh3EKKQH8M4uQ==", "dependencies": { - "@umbraco-ui/uui-base": "1.3.0" + "@umbraco-ui/uui-base": "1.4.0" } }, "node_modules/@umbraco-ui/uui-toast-notification": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-toast-notification/-/uui-toast-notification-1.3.0.tgz", - "integrity": "sha512-47bKZti+gSwOjzoZR16GoQu0W//esDJiKkELZ8JIhxD5gqn8QsxrqJlQ525T4qSg9hBIUCTM3/HKsvPbyjZoow==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-toast-notification/-/uui-toast-notification-1.4.0.tgz", + "integrity": "sha512-ioiTTxqaOV/2ggnK9/IrnJPf1KRaKEIXd6qrXkMaYH1orCmv3BIdQMnl3TxFOM1YMlnbVZrfxBe2++iqV6TxHA==", "dependencies": { - "@umbraco-ui/uui-base": "1.3.0", - "@umbraco-ui/uui-button": "1.3.0", - "@umbraco-ui/uui-css": "1.3.0", - "@umbraco-ui/uui-icon": "1.3.0", - "@umbraco-ui/uui-icon-registry-essential": "1.3.0" + "@umbraco-ui/uui-base": "1.4.0", + "@umbraco-ui/uui-button": "1.4.0", + "@umbraco-ui/uui-css": "1.4.0", + "@umbraco-ui/uui-icon": "1.4.0", + "@umbraco-ui/uui-icon-registry-essential": "1.4.0" } }, "node_modules/@umbraco-ui/uui-toast-notification-container": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-toast-notification-container/-/uui-toast-notification-container-1.3.0.tgz", - "integrity": "sha512-Zao5QFrL9JjC9msqNsUjeLdWZAWqn6fDYQfhRsZPDEmvsZ4yQN+QnoJVyufFnzOgOfqUNHB6VHTqbS2MPg6ojQ==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-toast-notification-container/-/uui-toast-notification-container-1.4.0.tgz", + "integrity": "sha512-VIftKhOoQ0EdtM9pvDUM2IcvR8S9Fveh/QwMHgGLVlsgUogBNkCPGJKLfh9hzE5RS2v9FdPIkk72qP2A4fpspQ==", "dependencies": { - "@umbraco-ui/uui-base": "1.3.0", - "@umbraco-ui/uui-toast-notification": "1.3.0" + "@umbraco-ui/uui-base": "1.4.0", + "@umbraco-ui/uui-toast-notification": "1.4.0" } }, "node_modules/@umbraco-ui/uui-toast-notification-layout": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-toast-notification-layout/-/uui-toast-notification-layout-1.3.0.tgz", - "integrity": "sha512-Ho8Ad6VIxxN0QC9k980TjY2X+sItfiD6mCIOwle3bCAadFMnLOa/ESseA1l+zr4/liY0PPQoMBnQtKR9L0XK3A==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-toast-notification-layout/-/uui-toast-notification-layout-1.4.0.tgz", + "integrity": "sha512-Secrk5+GlZYzOrg1MQ28+rLGW5krXYxYSAhSe5uDKOqTFLjuag7/qiraQDG3xBtf9ZfAAJ3qUy9n50adshoDbA==", "dependencies": { - "@umbraco-ui/uui-base": "1.3.0", - "@umbraco-ui/uui-css": "1.3.0" + "@umbraco-ui/uui-base": "1.4.0", + "@umbraco-ui/uui-css": "1.4.0" } }, "node_modules/@umbraco-ui/uui-toggle": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-toggle/-/uui-toggle-1.3.0.tgz", - "integrity": "sha512-6NnAP6L8jSnE2CyPx1wgIMrZm0eCRNOAcYWKsF3iyhG4TLkZqbGq7pGEhxqHT0uNigHdv5Wseu+UC1L0GLM+pQ==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-toggle/-/uui-toggle-1.4.0.tgz", + "integrity": "sha512-APIOxs96fcn6HvD/SksN7rhEk6IAta7XU6s0T2Fa+RPIeOBS0NbbvFUX6hW3qjpiD5DdsjOpO2jn/R1fH3nqnQ==", "dependencies": { - "@umbraco-ui/uui-base": "1.3.0", - "@umbraco-ui/uui-boolean-input": "1.3.0" + "@umbraco-ui/uui-base": "1.4.0", + "@umbraco-ui/uui-boolean-input": "1.4.0" } }, "node_modules/abab": { @@ -3022,9 +3054,9 @@ } }, "node_modules/ace-builds": { - "version": "1.22.1", - "resolved": "https://registry.npmjs.org/ace-builds/-/ace-builds-1.22.1.tgz", - "integrity": "sha512-o5RGTPBIiRxguWNors3pT6KuLqj0a2NvNLoqir7/2LLiFm34PJV3BMq4sl9kjPayo4+lmd99m6zAq+XPdhyHQA==" + "version": "1.30.0", + "resolved": "https://registry.npmjs.org/ace-builds/-/ace-builds-1.30.0.tgz", + "integrity": "sha512-ZC+G1ozrrVCVL/KPkeU9R7TEwYeNJUYRrjnEvNhF8r2+WR2tkcCjmduL8M6D3abIdf/16ccEXHtpoRBhAnTyCw==" }, "node_modules/acorn": { "version": "8.9.0", @@ -3038,16 +3070,6 @@ "node": ">=0.4.0" } }, - "node_modules/acorn-globals": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-7.0.1.tgz", - "integrity": "sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==", - "dev": true, - "dependencies": { - "acorn": "^8.1.0", - "acorn-walk": "^8.0.2" - } - }, "node_modules/acorn-jsx": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", @@ -3057,15 +3079,6 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, - "node_modules/acorn-walk": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", - "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/agent-base": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", @@ -3626,9 +3639,9 @@ } }, "node_modules/autoprefixer": { - "version": "10.4.14", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.14.tgz", - "integrity": "sha512-FQzyfOsTlwVzjHxKEqRIAdJx9niO6VCBCoEwax/VLSoQF29ggECcPuBqUMZ+u8jCZOPSy8b8/8KnuFbp0SaFZQ==", + "version": "10.4.16", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.16.tgz", + "integrity": "sha512-7vd3UC6xKp0HLfua5IjZlcXvGAGy7cBAXTg2lyQ/8WpNhd6SiZ8Be+xm3FyBSYJx5GKcpRCzBh7RH4/0dnY+uQ==", "dev": true, "funding": [ { @@ -3638,12 +3651,16 @@ { "type": "tidelift", "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" } ], "dependencies": { - "browserslist": "^4.21.5", - "caniuse-lite": "^1.0.30001464", - "fraction.js": "^4.2.0", + "browserslist": "^4.21.10", + "caniuse-lite": "^1.0.30001538", + "fraction.js": "^4.3.6", "normalize-range": "^0.1.2", "picocolors": "^1.0.0", "postcss-value-parser": "^4.2.0" @@ -3673,9 +3690,9 @@ } }, "node_modules/babel-plugin-polyfill-corejs2/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, "bin": { "semver": "bin/semver.js" @@ -3780,8 +3797,7 @@ "type": "consulting", "url": "https://feross.org/support" } - ], - "optional": true + ] }, "node_modules/base64id": { "version": "2.0.0", @@ -4263,9 +4279,9 @@ } }, "node_modules/browserslist": { - "version": "4.21.9", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.9.tgz", - "integrity": "sha512-M0MFoZzbUrRU4KNfCrDLnvyE7gub+peetoTid3TBIqtunaDJyXlwhakT+/VkvSXcfIzFfK/nkCs4nmyTmxdNSg==", + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.1.tgz", + "integrity": "sha512-FEVc202+2iuClEhZhrWy6ZiAcRLvNMyYcxZ8raemul1DYVOVdFsbqckWLdsixQZCpJlwe77Z3UTalE7jsjnKfQ==", "dev": true, "funding": [ { @@ -4282,10 +4298,10 @@ } ], "dependencies": { - "caniuse-lite": "^1.0.30001503", - "electron-to-chromium": "^1.4.431", - "node-releases": "^2.0.12", - "update-browserslist-db": "^1.0.11" + "caniuse-lite": "^1.0.30001541", + "electron-to-chromium": "^1.4.535", + "node-releases": "^2.0.13", + "update-browserslist-db": "^1.0.13" }, "bin": { "browserslist": "cli.js" @@ -4567,9 +4583,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001504", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001504.tgz", - "integrity": "sha512-5uo7eoOp2mKbWyfMXnGO9rJWOGU8duvzEiYITW+wivukL7yHH4gX9yuRaobu6El4jPxo6jKZfG+N6fB621GD/Q==", + "version": "1.0.30001550", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001550.tgz", + "integrity": "sha512-p82WjBYIypO0ukTsd/FG3Xxs+4tFeaY9pfT4amQL8KWtYH7H9nYwReGAbMTJ0hsmRO8IfDtsS6p3ZWj8+1c2RQ==", "dev": true, "funding": [ { @@ -5216,12 +5232,12 @@ } }, "node_modules/core-js-compat": { - "version": "3.31.0", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.31.0.tgz", - "integrity": "sha512-hM7YCu1cU6Opx7MXNu0NuumM0ezNeAeRKadixyiQELWY3vT3De9S4J5ZBMraWV2vZnrE1Cirl0GtFtDtMUXzPw==", + "version": "3.33.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.33.0.tgz", + "integrity": "sha512-0w4LcLXsVEuNkIqwjjf9rjCoPhK8uqA4tMRh4Ge26vfLtUutshn+aRJU21I9LCJlh2QQHfisNToLjw1XEJLTWw==", "dev": true, "dependencies": { - "browserslist": "^4.21.5" + "browserslist": "^4.22.1" }, "funding": { "type": "opencollective", @@ -6263,9 +6279,9 @@ "dev": true }, "node_modules/electron-to-chromium": { - "version": "1.4.433", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.433.tgz", - "integrity": "sha512-MGO1k0w1RgrfdbLVwmXcDhHHuxCn2qRgR7dYsJvWFKDttvYPx6FNzCGG0c/fBBvzK2LDh3UV7Tt9awnHnvAAUQ==", + "version": "1.4.557", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.557.tgz", + "integrity": "sha512-6x0zsxyMXpnMJnHrondrD3SuAeKcwij9S+83j2qHAQPXbGTDDfgImzzwgGlzrIcXbHQ42tkG4qA6U860cImNhw==", "dev": true }, "node_modules/emits": { @@ -6577,27 +6593,27 @@ } }, "node_modules/eslint": { - "version": "8.43.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.43.0.tgz", - "integrity": "sha512-aaCpf2JqqKesMFGgmRPessmVKjcGXqdlAYLLC3THM8t5nBRZRQ+st5WM/hoJXkdioEXLLbXgclUpM0TXo5HX5Q==", + "version": "8.51.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.51.0.tgz", + "integrity": "sha512-2WuxRZBrlwnXi+/vFSJyjMqrNjtJqiasMzehF0shoLaW7DzS3/9Yvrmq5JiT66+pNjiX4UBnLDiKHcWAr/OInA==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.4.0", - "@eslint/eslintrc": "^2.0.3", - "@eslint/js": "8.43.0", - "@humanwhocodes/config-array": "^0.11.10", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.2", + "@eslint/js": "8.51.0", + "@humanwhocodes/config-array": "^0.11.11", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", - "ajv": "^6.10.0", + "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", "debug": "^4.3.2", "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.2.0", - "eslint-visitor-keys": "^3.4.1", - "espree": "^9.5.2", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", @@ -6607,7 +6623,6 @@ "globals": "^13.19.0", "graphemer": "^1.4.0", "ignore": "^5.2.0", - "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "is-path-inside": "^3.0.3", @@ -6617,9 +6632,8 @@ "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", - "optionator": "^0.9.1", + "optionator": "^0.9.3", "strip-ansi": "^6.0.1", - "strip-json-comments": "^3.1.0", "text-table": "^0.2.0" }, "bin": { @@ -6633,9 +6647,9 @@ } }, "node_modules/eslint-scope": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.0.tgz", - "integrity": "sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw==", + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", "dev": true, "dependencies": { "esrecurse": "^4.3.0", @@ -6658,9 +6672,9 @@ } }, "node_modules/eslint-visitor-keys": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz", - "integrity": "sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==", + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -6822,17 +6836,17 @@ } }, "node_modules/eslint/node_modules/optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", "dev": true, "dependencies": { + "@aashutoshrathi/word-wrap": "^1.2.3", "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" + "type-check": "^0.4.0" }, "engines": { "node": ">= 0.8.0" @@ -6926,12 +6940,12 @@ } }, "node_modules/espree": { - "version": "9.5.2", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.2.tgz", - "integrity": "sha512-7OASN1Wma5fum5SrNhFMAMJxOUAbhyfQ8dQ//PJaJbNw0URTPWqIghHWt1MmAANKhHZIYOHruW4Kw4ruUWOdGw==", + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", "dev": true, "dependencies": { - "acorn": "^8.8.0", + "acorn": "^8.9.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^3.4.1" }, @@ -7462,6 +7476,12 @@ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "dev": true }, + "node_modules/fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", + "dev": true + }, "node_modules/fast-glob": { "version": "3.2.7", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.7.tgz", @@ -7930,16 +7950,16 @@ } }, "node_modules/fraction.js": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz", - "integrity": "sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==", + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", + "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", "dev": true, "engines": { "node": "*" }, "funding": { "type": "patreon", - "url": "https://www.patreon.com/infusion" + "url": "https://github.com/sponsors/rawify" } }, "node_modules/fragment-cache": { @@ -8527,9 +8547,9 @@ } }, "node_modules/graceful-fs": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz", - "integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==", + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "dev": true }, "node_modules/graphemer": { @@ -8661,24 +8681,43 @@ } }, "node_modules/gulp-eslint-new": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/gulp-eslint-new/-/gulp-eslint-new-1.8.1.tgz", - "integrity": "sha512-JKApDbNp81PPQ+ok0t3hVJPOz+ZcXwqmiaxtfI/lCBEwk973RtDmLYWjjY82brS5bXr6j4dQumrtDb/4beA0tw==", + "version": "1.8.4", + "resolved": "https://registry.npmjs.org/gulp-eslint-new/-/gulp-eslint-new-1.8.4.tgz", + "integrity": "sha512-T/eY9OWHJ0LGYvHUagPpC8tOMgLpniYKkgtwn3XA83e3IgeZV0NjlcWbhxkBHwO/LX/lbK01jp/Xqs6REyy+Vg==", "dev": true, "dependencies": { - "@types/eslint": "^8.40.0", + "@types/eslint": "^8.44.3", "@types/node": ">=12", "eslint": "8", "fancy-log": "^2.0.0", "plugin-error": "^2.0.1", - "semver": "^7.5.1", + "semver": "^7.5.4", "ternary-stream": "^3.0.0", - "vinyl-fs": "^3.0.3" + "vinyl-fs": "^4.0.0" }, "engines": { "node": "^12.20 || ^14.13 || >=16" } }, + "node_modules/gulp-eslint-new/node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/gulp-eslint-new/node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, "node_modules/gulp-eslint-new/node_modules/fancy-log": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/fancy-log/-/fancy-log-2.0.0.tgz", @@ -8691,6 +8730,71 @@ "node": ">=10.13.0" } }, + "node_modules/gulp-eslint-new/node_modules/fs-mkdirp-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fs-mkdirp-stream/-/fs-mkdirp-stream-2.0.1.tgz", + "integrity": "sha512-UTOY+59K6IA94tec8Wjqm0FSh5OVudGNB0NL/P6fB3HiE3bYOY3VYBGijsnOHNkQSwC1FKkU77pmq7xp9CskLw==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.8", + "streamx": "^2.12.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/gulp-eslint-new/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/gulp-eslint-new/node_modules/glob-stream": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/glob-stream/-/glob-stream-8.0.0.tgz", + "integrity": "sha512-CdIUuwOkYNv9ZadR3jJvap8CMooKziQZ/QCSPhEb7zqfsEI5YnPmvca7IvbaVE3z58ZdUYD2JsU6AUWjL8WZJA==", + "dev": true, + "dependencies": { + "@gulpjs/to-absolute-glob": "^4.0.0", + "anymatch": "^3.1.3", + "fastq": "^1.13.0", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "is-negated-glob": "^1.0.0", + "normalize-path": "^3.0.0", + "streamx": "^2.12.5" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/gulp-eslint-new/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-eslint-new/node_modules/lead": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/lead/-/lead-4.0.0.tgz", + "integrity": "sha512-DpMa59o5uGUWWjruMp71e6knmwKU3jRBBn1kjuLWN9EeIOxNeSAwvHf03WIl8g/ZMR2oSQC9ej3yeLBwdDc/pg==", + "dev": true, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/gulp-eslint-new/node_modules/lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -8703,6 +8807,18 @@ "node": ">=10" } }, + "node_modules/gulp-eslint-new/node_modules/now-and-later": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/now-and-later/-/now-and-later-3.0.0.tgz", + "integrity": "sha512-pGO4pzSdaxhWTGkfSfHx3hVzJVslFPwBp2Myq9MYN/ChfJZF87ochMAXnvz6/58RJSf5ik2q9tXprBBrk2cpcg==", + "dev": true, + "dependencies": { + "once": "^1.4.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, "node_modules/gulp-eslint-new/node_modules/plugin-error": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-2.0.1.tgz", @@ -8715,10 +8831,31 @@ "node": ">=10.13.0" } }, + "node_modules/gulp-eslint-new/node_modules/replace-ext": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-2.0.0.tgz", + "integrity": "sha512-UszKE5KVK6JvyD92nzMn9cDapSk6w/CaFZ96CnmDMUqH9oowfxF/ZjRITD25H4DnOQClLA4/j7jLGXXLVKxAug==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/gulp-eslint-new/node_modules/resolve-options": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/resolve-options/-/resolve-options-2.0.0.tgz", + "integrity": "sha512-/FopbmmFOQCfsCx77BRFdKOniglTiHumLgwvd6IDPihy1GKkadZbgQJBcTb2lMzSR1pndzd96b1nZrreZ7+9/A==", + "dev": true, + "dependencies": { + "value-or-function": "^4.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, "node_modules/gulp-eslint-new/node_modules/semver": { - "version": "7.5.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.2.tgz", - "integrity": "sha512-SoftuTROv/cRjCze/scjGyiDtcUyxw1rgYQSZY7XTmtR5hX+dm76iDbTH8TkLPHCQmlbQVSSbNZCPM2hb0knnQ==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -8730,6 +8867,85 @@ "node": ">=10" } }, + "node_modules/gulp-eslint-new/node_modules/to-through": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/to-through/-/to-through-3.0.0.tgz", + "integrity": "sha512-y8MN937s/HVhEoBU1SxfHC+wxCHkV1a9gW8eAdTadYh/bGyesZIVcbjI+mSpFbSVwQici/XjBjuUyri1dnXwBw==", + "dev": true, + "dependencies": { + "streamx": "^2.12.5" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/gulp-eslint-new/node_modules/value-or-function": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/value-or-function/-/value-or-function-4.0.0.tgz", + "integrity": "sha512-aeVK81SIuT6aMJfNo9Vte8Dw0/FZINGBV8BfCraGtqVxIeLAEhJyoWs8SmvRVmXfGss2PmmOwZCuBPbZR+IYWg==", + "dev": true, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/gulp-eslint-new/node_modules/vinyl": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-3.0.0.tgz", + "integrity": "sha512-rC2VRfAVVCGEgjnxHUnpIVh3AGuk62rP3tqVrn+yab0YH7UULisC085+NYH+mnqf3Wx4SpSi1RQMwudL89N03g==", + "dev": true, + "dependencies": { + "clone": "^2.1.2", + "clone-stats": "^1.0.0", + "remove-trailing-separator": "^1.1.0", + "replace-ext": "^2.0.0", + "teex": "^1.0.1" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/gulp-eslint-new/node_modules/vinyl-fs": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/vinyl-fs/-/vinyl-fs-4.0.0.tgz", + "integrity": "sha512-7GbgBnYfaquMk3Qu9g22x000vbYkOex32930rBnc3qByw6HfMEAoELjCjoJv4HuEQxHAurT+nvMHm6MnJllFLw==", + "dev": true, + "dependencies": { + "fs-mkdirp-stream": "^2.0.1", + "glob-stream": "^8.0.0", + "graceful-fs": "^4.2.11", + "iconv-lite": "^0.6.3", + "is-valid-glob": "^1.0.0", + "lead": "^4.0.0", + "normalize-path": "3.0.0", + "resolve-options": "^2.0.0", + "stream-composer": "^1.0.2", + "streamx": "^2.14.0", + "to-through": "^3.0.0", + "value-or-function": "^4.0.0", + "vinyl": "^3.0.0", + "vinyl-sourcemap": "^2.0.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/gulp-eslint-new/node_modules/vinyl-sourcemap": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/vinyl-sourcemap/-/vinyl-sourcemap-2.0.0.tgz", + "integrity": "sha512-BAEvWxbBUXvlNoFQVFVHpybBbjW1r03WhohJzJDSfgrrK5xVYIDTan6xN14DlyImShgDRv2gl9qhM6irVMsV0Q==", + "dev": true, + "dependencies": { + "convert-source-map": "^2.0.0", + "graceful-fs": "^4.2.10", + "now-and-later": "^3.0.0", + "streamx": "^2.12.5", + "vinyl": "^3.0.0", + "vinyl-contents": "^2.0.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/gulp-eslint-new/node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", @@ -9791,8 +10007,7 @@ "type": "consulting", "url": "https://feross.org/support" } - ], - "optional": true + ] }, "node_modules/ignore": { "version": "5.2.0", @@ -10700,15 +10915,15 @@ } }, "node_modules/jasmine-core": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-5.0.1.tgz", - "integrity": "sha512-D4bRej8CplwNtNGyTPD++cafJlZUphzZNV+MSAnbD3er4D0NjL4x9V+mu/SI+5129utnCBen23JwEuBZA9vlpQ==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-5.1.1.tgz", + "integrity": "sha512-UrzO3fL7nnxlQXlvTynNAenL+21oUQRlzqQFsA2U11ryb4+NLOCOePZ70PTojEaUKhiFugh7dG0Q+I58xlPdWg==", "dev": true }, "node_modules/jquery": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.0.tgz", - "integrity": "sha512-umpJ0/k8X0MvD1ds0P9SfowREz2LenHsQaxSohMZ5OMNEU2r0tf8pdeEFTHMFxWVxKNyU9rTtK3CWzUCTKJUeQ==" + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz", + "integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==" }, "node_modules/jquery-ui-dist": { "version": "1.13.2", @@ -10756,19 +10971,16 @@ } }, "node_modules/jsdom": { - "version": "21.1.2", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-21.1.2.tgz", - "integrity": "sha512-sCpFmK2jv+1sjff4u7fzft+pUh2KSUbUrEHYHyfSIbGTIcmnjyp83qg6qLwdJ/I3LpTXx33ACxeRL7Lsyc6lGQ==", + "version": "22.1.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-22.1.0.tgz", + "integrity": "sha512-/9AVW7xNbsBv6GfWho4TTNjEo9fe6Zhf9O7s0Fhhr3u+awPwAJMKwAMXnkk5vBxflqLW9hTHX/0cs+P3gW+cQw==", "dev": true, "dependencies": { "abab": "^2.0.6", - "acorn": "^8.8.2", - "acorn-globals": "^7.0.0", "cssstyle": "^3.0.0", "data-urls": "^4.0.0", "decimal.js": "^10.4.3", "domexception": "^4.0.0", - "escodegen": "^2.0.0", "form-data": "^4.0.0", "html-encoding-sniffer": "^3.0.0", "http-proxy-agent": "^5.0.0", @@ -10789,7 +11001,7 @@ "xml-name-validator": "^4.0.0" }, "engines": { - "node": ">=14" + "node": ">=16" }, "peerDependencies": { "canvas": "^2.5.0" @@ -10800,60 +11012,6 @@ } } }, - "node_modules/jsdom/node_modules/escodegen": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.0.0.tgz", - "integrity": "sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw==", - "dev": true, - "dependencies": { - "esprima": "^4.0.1", - "estraverse": "^5.2.0", - "esutils": "^2.0.2", - "optionator": "^0.8.1" - }, - "bin": { - "escodegen": "bin/escodegen.js", - "esgenerate": "bin/esgenerate.js" - }, - "engines": { - "node": ">=6.0" - }, - "optionalDependencies": { - "source-map": "~0.6.1" - } - }, - "node_modules/jsdom/node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true, - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/jsdom/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/jsdom/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/jsdom/node_modules/ws": { "version": "8.13.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", @@ -11005,12 +11163,12 @@ "dev": true }, "node_modules/karma-jsdom-launcher": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/karma-jsdom-launcher/-/karma-jsdom-launcher-14.0.0.tgz", - "integrity": "sha512-GrZpJK9cdBo0C6YXZh0Sbq8D5NvdoTvRE9R/nhJzl0Tyfg7GI6iCU73ww264eqAZzy1mjCk/vj8sVA6ECacLlQ==", + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/karma-jsdom-launcher/-/karma-jsdom-launcher-15.0.0.tgz", + "integrity": "sha512-F5KkE7qNS93cLk6BZtGI1V+OAHoruGIhZm+zCViT9J4oqJ2tn9hScIk+KXQL6Zmq6exddhpFKNw+hVIQYUocpg==", "dev": true, "peerDependencies": { - "jsdom": ">=14 <=21", + "jsdom": ">=15 <=22", "karma": ">=2 <=6" } }, @@ -11405,9 +11563,9 @@ } }, "node_modules/less": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/less/-/less-4.1.3.tgz", - "integrity": "sha512-w16Xk/Ta9Hhyei0Gpz9m7VS8F28nieJaL/VyShID7cYvP6IL5oHeL6p4TXSDJqZE/lNv0oJ2pGVjJsRkfwm5FA==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/less/-/less-4.2.0.tgz", + "integrity": "sha512-P3b3HJDBtSzsXUl0im2L7gTO5Ubg8mEN6G8qoTS77iXxXX4Hvu4Qj540PZDvQ8V6DmX6iXo98k7Md0Cm1PrLaA==", "dev": true, "dependencies": { "copy-anything": "^2.0.1", @@ -11527,29 +11685,29 @@ } }, "node_modules/lit": { - "version": "2.7.5", - "resolved": "https://registry.npmjs.org/lit/-/lit-2.7.5.tgz", - "integrity": "sha512-i/cH7Ye6nBDUASMnfwcictBnsTN91+aBjXoTHF2xARghXScKxpD4F4WYI+VLXg9lqbMinDfvoI7VnZXjyHgdfQ==", + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/lit/-/lit-2.8.0.tgz", + "integrity": "sha512-4Sc3OFX9QHOJaHbmTMk28SYgVxLN3ePDjg7hofEft2zWlehFL3LiAuapWc4U/kYwMYJSh2hTCPZ6/LIC7ii0MA==", "dependencies": { "@lit/reactive-element": "^1.6.0", "lit-element": "^3.3.0", - "lit-html": "^2.7.0" + "lit-html": "^2.8.0" } }, "node_modules/lit-element": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/lit-element/-/lit-element-3.3.2.tgz", - "integrity": "sha512-xXAeVWKGr4/njq0rGC9dethMnYCq5hpKYrgQZYTzawt9YQhMiXfD+T1RgrdY3NamOxwq2aXlb0vOI6e29CKgVQ==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/lit-element/-/lit-element-3.3.3.tgz", + "integrity": "sha512-XbeRxmTHubXENkV4h8RIPyr8lXc+Ff28rkcQzw3G6up2xg5E8Zu1IgOWIwBLEQsu3cOVFqdYwiVi0hv0SlpqUA==", "dependencies": { "@lit-labs/ssr-dom-shim": "^1.1.0", "@lit/reactive-element": "^1.3.0", - "lit-html": "^2.7.0" + "lit-html": "^2.8.0" } }, "node_modules/lit-html": { - "version": "2.7.4", - "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-2.7.4.tgz", - "integrity": "sha512-/Jw+FBpeEN+z8X6PJva5n7+0MzCVAH2yypN99qHYYkq8bI+j7I39GH+68Z/MZD6rGKDK9RpzBw7CocfmHfq6+g==", + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-2.8.0.tgz", + "integrity": "sha512-o9t+MQM3P4y7M7yNzqAyjp7z+mQGa4NS4CxiyLqFPyFWyc4O+nodLrkrxSaCTrla6M5YOLaT3RpbbqjszB5g3Q==", "dependencies": { "@types/trusted-types": "^2.0.2" } @@ -12613,9 +12771,9 @@ "dev": true }, "node_modules/node-releases": { - "version": "2.0.12", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.12.tgz", - "integrity": "sha512-QzsYKWhXTWx8h1kIvqfnC++o0pEmpRQA/aenALsL2F4pqNVr7YzcdMlDij5WBnwftRbJCNJL/O7zdKaxKPHqgQ==", + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", + "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==", "dev": true }, "node_modules/node.extend": { @@ -13552,9 +13710,9 @@ } }, "node_modules/postcss": { - "version": "8.4.24", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.24.tgz", - "integrity": "sha512-M0RzbcI0sO/XJNucsGjvWU9ERWxb/ytp1w6dKtxTKgixdtQDq4rmx/g8W1hnaheq9jgwL/oyEdH5Bc4WwJKMqg==", + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", "dev": true, "funding": [ { @@ -14395,6 +14553,12 @@ } ] }, + "node_modules/queue-tick": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz", + "integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==", + "dev": true + }, "node_modules/randomatic": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-3.1.1.tgz", @@ -14575,15 +14739,15 @@ } }, "node_modules/regenerator-runtime": { - "version": "0.13.11", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", - "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz", + "integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==", "dev": true }, "node_modules/regenerator-transform": { - "version": "0.15.1", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.1.tgz", - "integrity": "sha512-knzmNAcuyxV+gQCufkYcvOqX/qIIfHLv0u5x79kRxuGojfYVky1f15TzZEu2Avte8QGepvUNTnLskf8E6X6Vyg==", + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.2.tgz", + "integrity": "sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==", "dev": true, "dependencies": { "@babel/runtime": "^7.8.4" @@ -15833,6 +15997,15 @@ "node": ">= 0.6" } }, + "node_modules/stream-composer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/stream-composer/-/stream-composer-1.0.2.tgz", + "integrity": "sha512-bnBselmwfX5K10AH6L4c8+S5lgZMWI7ZYrz2rvYjCPB2DIMC4Ig8OpxGpNJSxRZ58oti7y1IcNvjBAz9vW5m4w==", + "dev": true, + "dependencies": { + "streamx": "^2.13.2" + } + }, "node_modules/stream-exhaust": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/stream-exhaust/-/stream-exhaust-1.0.2.tgz", @@ -15859,6 +16032,16 @@ "node": ">=8.0" } }, + "node_modules/streamx": { + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.15.1.tgz", + "integrity": "sha512-fQMzy2O/Q47rgwErk/eGeLu/roaFWV0jVsogDmrszM9uIw8L5OA+t+V93MgYlufNptfjmYR1tOMWhei/Eh7TQA==", + "dev": true, + "dependencies": { + "fast-fifo": "^1.1.0", + "queue-tick": "^1.0.1" + } + }, "node_modules/strict-uri-encode": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz", @@ -16145,6 +16328,15 @@ "node": ">= 0.8.0" } }, + "node_modules/teex": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/teex/-/teex-1.0.1.tgz", + "integrity": "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==", + "dev": true, + "dependencies": { + "streamx": "^2.12.5" + } + }, "node_modules/temp-dir": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-1.0.0.tgz", @@ -16326,9 +16518,9 @@ "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==" }, "node_modules/tinymce": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/tinymce/-/tinymce-6.5.1.tgz", - "integrity": "sha512-J67fxJiX3tjvVqer1dg1+cOxMeE2P55ESGhaakvqGPbAUU45HnCMLSioaOsxV1KfcXustw9WJo0rtn1SNQlVKQ==" + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/tinymce/-/tinymce-6.7.0.tgz", + "integrity": "sha512-Wf2RSobIXQ7XNw3/v4z1lPGiH3Pjsmc/6/7fG28aIS6uVWj/7IhvOPuwfJJDeOx0o0D3nSnoLHgR2KU8JAdE+w==" }, "node_modules/to-absolute-glob": { "version": "2.0.2", @@ -16847,9 +17039,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz", - "integrity": "sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==", + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", + "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", "dev": true, "funding": [ { @@ -17040,6 +17232,93 @@ "bufferstreams": "1.0.1" } }, + "node_modules/vinyl-contents": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/vinyl-contents/-/vinyl-contents-2.0.0.tgz", + "integrity": "sha512-cHq6NnGyi2pZ7xwdHSW1v4Jfnho4TEGtxZHw01cmnc8+i7jgR6bRnED/LbrKan/Q7CvVLbnvA5OepnhbpjBZ5Q==", + "dev": true, + "dependencies": { + "bl": "^5.0.0", + "vinyl": "^3.0.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/vinyl-contents/node_modules/bl": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-5.1.0.tgz", + "integrity": "sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ==", + "dev": true, + "dependencies": { + "buffer": "^6.0.3", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/vinyl-contents/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/vinyl-contents/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/vinyl-contents/node_modules/replace-ext": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-2.0.0.tgz", + "integrity": "sha512-UszKE5KVK6JvyD92nzMn9cDapSk6w/CaFZ96CnmDMUqH9oowfxF/ZjRITD25H4DnOQClLA4/j7jLGXXLVKxAug==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/vinyl-contents/node_modules/vinyl": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-3.0.0.tgz", + "integrity": "sha512-rC2VRfAVVCGEgjnxHUnpIVh3AGuk62rP3tqVrn+yab0YH7UULisC085+NYH+mnqf3Wx4SpSi1RQMwudL89N03g==", + "dev": true, + "dependencies": { + "clone": "^2.1.2", + "clone-stats": "^1.0.0", + "remove-trailing-separator": "^1.1.0", + "replace-ext": "^2.0.0", + "teex": "^1.0.1" + }, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/vinyl-file": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/vinyl-file/-/vinyl-file-2.0.0.tgz", diff --git a/src/Umbraco.Web.UI.Client/package.json b/src/Umbraco.Web.UI.Client/package.json index 3135379786..fc15db2004 100644 --- a/src/Umbraco.Web.UI.Client/package.json +++ b/src/Umbraco.Web.UI.Client/package.json @@ -18,10 +18,10 @@ "npm": ">=9.5" }, "dependencies": { - "@microsoft/signalr": "7.0.7", - "@umbraco-ui/uui": "1.3.0", - "@umbraco-ui/uui-css": "1.3.0", - "ace-builds": "1.22.1", + "@microsoft/signalr": "7.0.12", + "@umbraco-ui/uui": "1.4.0", + "@umbraco-ui/uui-css": "1.4.0", + "ace-builds": "1.30.0", "angular": "1.8.3", "angular-animate": "1.8.3", "angular-aria": "1.8.3", @@ -43,7 +43,7 @@ "diff": "5.1.0", "flatpickr": "4.6.13", "font-awesome": "4.7.0", - "jquery": "3.7.0", + "jquery": "3.7.1", "jquery-ui-dist": "1.13.2", "jquery-ui-touch-punch": "0.2.3", "lazyload-js": "1.0.0", @@ -51,7 +51,7 @@ "ng-file-upload": "12.2.13", "nouislider": "15.7.1", "spectrum-colorpicker2": "2.0.10", - "tinymce": "6.5.1", + "tinymce": "6.7.0", "typeahead.js": "0.11.1", "underscore": "1.13.6", "wicg-inert": "3.1.2" @@ -59,16 +59,16 @@ "devDependencies": { "@babel/core": "7.21.8", "@babel/preset-env": "7.21.5", - "autoprefixer": "10.4.14", + "autoprefixer": "10.4.16", "cssnano": "6.0.1", - "eslint": "8.43.0", + "eslint": "8.51.0", "gulp": "4.0.2", "gulp-angular-embed-templates": "2.3.0", "gulp-babel": "8.0.0", "gulp-clean-css": "4.3.0", "gulp-cli": "2.3.0", "gulp-concat": "2.6.1", - "gulp-eslint-new": "1.8.1", + "gulp-eslint-new": "1.8.4", "gulp-imagemin": "7.1.0", "gulp-less": "5.0.0", "gulp-minify": "3.1.0", @@ -80,17 +80,17 @@ "gulp-watch": "5.0.1", "gulp-wrap": "0.15.0", "gulp-wrap-js": "0.4.1", - "jasmine-core": "5.0.1", - "jsdom": "21.1.2", + "jasmine-core": "5.1.1", + "jsdom": "22.1.0", "karma": "6.4.2", "karma-jasmine": "5.1.0", - "karma-jsdom-launcher": "14.0.0", + "karma-jsdom-launcher": "15.0.0", "karma-junit-reporter": "2.0.1", "karma-spec-reporter": "0.0.36", - "less": "4.1.3", + "less": "4.2.0", "lodash": "4.17.21", "merge-stream": "2.0.0", - "postcss": "8.4.24", + "postcss": "8.4.31", "run-sequence": "2.2.1" } } diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js index 3a7c99ff59..3099bc561c 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js @@ -346,7 +346,7 @@ labelKey: "buttons_saveAndPreview" }; - const activeVariant = content.variants?.find((variant) => variant.compositeId === compositeId); + const activeVariant = content.variants?.find((variant) => content.documentType?.variations === "Nothing" || variant.compositeId === compositeId); $scope.previewSubButtons = activeVariant?.additionalPreviewUrls?.map((additionalPreviewUrl) => { return { diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/media/umbmedianodeinfo.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/media/umbmedianodeinfo.directive.js index 2a65c67a8d..30bb2b6f3f 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/media/umbmedianodeinfo.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/media/umbmedianodeinfo.directive.js @@ -1,13 +1,74 @@ (function () { 'use strict'; - function MediaNodeInfoDirective($timeout, $location, $q, eventsService, userService, dateHelper, editorService, mediaHelper, mediaResource) { + function MediaNodeInfoDirective($timeout, logResource, $location, $q, eventsService, userService, dateHelper, editorService, mediaHelper, mediaResource) { function link(scope, element, attrs, ctrl) { var evts = []; + var isInfoTab = false; + var auditTrailLoaded = false; scope.allowChangeMediaType = false; + scope.historyLabelKey = "general_history"; + scope.auditTrailOptions = { + id: scope.node.id + }; + + scope.auditTrailPageChange = function (pageNumber) { + scope.auditTrailOptions.pageNumber = pageNumber; + loadAuditTrail(true); + }; + + function loadAuditTrail(forceReload) { + + //don't load this if it's already done + if (auditTrailLoaded && !forceReload) { + return; + } + + scope.loadingAuditTrail = true; + + logResource.getPagedEntityLog(scope.auditTrailOptions) + .then(data => { + + // get current backoffice user and format dates + userService.getCurrentUser().then(currentUser => { + data.items.forEach(item => { + item.timestampFormatted = dateHelper.getLocalDate(item.timestamp, currentUser.locale, 'LLL'); + }); + }); + + scope.auditTrail = data.items; + scope.auditTrailOptions.pageNumber = data.pageNumber; + scope.auditTrailOptions.pageSize = data.pageSize; + scope.auditTrailOptions.totalItems = data.totalItems; + scope.auditTrailOptions.totalPages = data.totalPages; + + setAuditTrailLogTypeColor(scope.auditTrail); + + scope.loadingAuditTrail = false; + + auditTrailLoaded = true; + }); + + } + + function setAuditTrailLogTypeColor(auditTrail) { + auditTrail.forEach(item => { + + switch (item.logType) { + case "Save": + item.logTypeColor = "success"; + break; + case "Delete": + item.logTypeColor = "danger"; + break; + default: + item.logTypeColor = "gray"; + } + }); + } function onInit() { @@ -31,6 +92,12 @@ // set media file extension initially setMediaExtension(); + + var activeApp = scope.node.apps.find(a => a.active); + if (activeApp.alias === "umbInfo") { + loadAuditTrail(); + isInfoTab = true; + } } function formatDatesToLocal() { @@ -92,6 +159,8 @@ //Update the media file format setMediaExtension(); + + loadAuditTrail(true); }); //ensure to unregister from all events! @@ -101,6 +170,18 @@ } }); + evts.push(eventsService.on("app.tabChange", function (event, args) { + $timeout(function () { + if (args.alias === "umbInfo") { + isInfoTab = true; + loadAuditTrail(); + formatDatesToLocal(); + } else { + isInfoTab = false; + } + }); + })); + onInit(); } diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/property/umbproperty.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/property/umbproperty.directive.js index 5a30f81d4b..11efb4b811 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/property/umbproperty.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/property/umbproperty.directive.js @@ -64,10 +64,13 @@ function onInit() { vm.controlLabelTitle = null; + vm.controlAriaLabel = null; if (Umbraco.Sys.ServerVariables.isDebuggingEnabled) { userService.getCurrentUser().then(function (u) { if (u.allowedSections.indexOf("settings") !== -1 ? true : false) { vm.controlLabelTitle = vm.property.alias; + // capitalize first letter of the alias for screen readers + vm.controlAriaLabel = vm.property.alias.charAt(0).toUpperCase() + vm.property.alias.slice(1); } }); } diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtree.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtree.directive.js index a39475f4da..54119f8df7 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtree.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtree.directive.js @@ -3,406 +3,415 @@ * @name umbraco.directives.directive:umbTree * @restrict E **/ -function umbTreeDirective($q, treeService, notificationsService) { +function umbTreeDirective($q, treeService, notificationsService, localizationService) { - return { - restrict: 'E', - replace: true, - terminal: false, - templateUrl: 'views/components/tree/umb-tree.html', - scope: { - section: '@', - treealias: '@', - hideoptions: '@', - hideheader: '@', - cachekey: '@', - isdialog: '@', - onlyInitialized: '@', - //Custom query string arguments to pass in to the tree as a string, example: "startnodeid=123&something=value" - customtreeparams: '@', - enablecheckboxes: '@', - enablelistviewsearch: '@', - enablelistviewexpand: '@', - api: '=?', - onInit: '&?' + return { + restrict: 'E', + replace: true, + terminal: false, + templateUrl: 'views/components/tree/umb-tree.html', + scope: { + section: '@', + treealias: '@', + hideoptions: '@', + hideheader: '@', + cachekey: '@', + isdialog: '@', + onlyInitialized: '@', + //Custom query string arguments to pass in to the tree as a string, example: "startnodeid=123&something=value" + customtreeparams: '@', + enablecheckboxes: '@', + enablelistviewsearch: '@', + enablelistviewexpand: '@', + api: '=?', + onInit: '&?' + }, + controller: function ($scope, $element) { + + var vm = this; + + var registeredCallbacks = { + treeNodeExpanded: [], + treeNodeSelect: [], + treeLoaded: [], + treeSynced: [], + treeOptionsClick: [], + treeNodeAltSelect: [] + }; + + //this is the API exposed by this directive, for either hosting controllers or for other directives + vm.callbacks = { + treeNodeExpanded: function (f) { + registeredCallbacks.treeNodeExpanded.push(f); }, - controller: function ($scope, $element) { + treeNodeSelect: function (f) { + registeredCallbacks.treeNodeSelect.push(f); + }, + treeLoaded: function (f) { + registeredCallbacks.treeLoaded.push(f); + }, + treeSynced: function (f) { + registeredCallbacks.treeSynced.push(f); + }, + treeOptionsClick: function (f) { + registeredCallbacks.treeOptionsClick.push(f); + }, + treeNodeAltSelect: function (f) { + registeredCallbacks.treeNodeAltSelect.push(f); + } + }; + vm.emitEvent = emitEvent; + vm.load = load; + vm.reloadNode = reloadNode; + vm.syncTree = syncTree; + vm.loadChildren = loadChildren; + vm.hasTree = hasTree; - var vm = this; + $scope.labels = { + openContextNode: "Open context node for" + }; - var registeredCallbacks = { - treeNodeExpanded: [], - treeNodeSelect: [], - treeLoaded: [], - treeSynced: [], - treeOptionsClick: [], - treeNodeAltSelect: [] - }; + //wire up the exposed api object for hosting controllers + if ($scope.api) { + $scope.api.callbacks = vm.callbacks; + $scope.api.load = vm.load; + $scope.api.reloadNode = vm.reloadNode; + $scope.api.syncTree = vm.syncTree; + $scope.api.hasTree = vm.hasTree; + } - //this is the API exposed by this directive, for either hosting controllers or for other directives - vm.callbacks = { - treeNodeExpanded: function (f) { - registeredCallbacks.treeNodeExpanded.push(f); - }, - treeNodeSelect: function (f) { - registeredCallbacks.treeNodeSelect.push(f); - }, - treeLoaded: function (f) { - registeredCallbacks.treeLoaded.push(f); - }, - treeSynced: function (f) { - registeredCallbacks.treeSynced.push(f); - }, - treeOptionsClick: function (f) { - registeredCallbacks.treeOptionsClick.push(f); - }, - treeNodeAltSelect: function (f) { - registeredCallbacks.treeNodeAltSelect.push(f); - } - }; - vm.emitEvent = emitEvent; - vm.load = load; - vm.reloadNode = reloadNode; - vm.syncTree = syncTree; - vm.loadChildren = loadChildren; - vm.hasTree = hasTree; + //flag to track the last loaded section when the tree 'un-loads'. We use this to determine if we should + // re-load the tree again. For example, if we hover over 'content' the content tree is shown. Then we hover + // outside of the tree and the tree 'un-loads'. When we re-hover over 'content', we don't want to re-load the + // entire tree again since we already still have it in memory. Of course if the section is different we will + // reload it. This saves a lot on processing if someone is navigating in and out of the same section many times + // since it saves on data retreival and DOM processing. + // TODO: This isn't used!? + var lastSection = ""; - //wire up the exposed api object for hosting controllers - if ($scope.api) { - $scope.api.callbacks = vm.callbacks; - $scope.api.load = vm.load; - $scope.api.reloadNode = vm.reloadNode; - $scope.api.syncTree = vm.syncTree; - $scope.api.hasTree = vm.hasTree; - } - - //flag to track the last loaded section when the tree 'un-loads'. We use this to determine if we should - // re-load the tree again. For example, if we hover over 'content' the content tree is shown. Then we hover - // outside of the tree and the tree 'un-loads'. When we re-hover over 'content', we don't want to re-load the - // entire tree again since we already still have it in memory. Of course if the section is different we will - // reload it. This saves a lot on processing if someone is navigating in and out of the same section many times - // since it saves on data retreival and DOM processing. - // TODO: This isn't used!? - var lastSection = ""; - - /** Helper function to emit tree events */ - function emitEvent(eventName, args) { - if (registeredCallbacks[eventName] && Utilities.isArray(registeredCallbacks[eventName])) { - // call it - registeredCallbacks[eventName].forEach(c => c(args)); - } - } + /** Helper function to emit tree events */ + function emitEvent(eventName, args) { + if (registeredCallbacks[eventName] && Utilities.isArray(registeredCallbacks[eventName])) { + // call it + registeredCallbacks[eventName].forEach(c => c(args)); + } + } - /** - * Re-loads the tree with the updated parameters - * @param {any} args either a string representing the 'section' or an object containing: 'section', 'treeAlias', 'customTreeParams', 'cacheKey' - */ - function load(args) { - if (Utilities.isString(args)) { - $scope.section = args; - } - else if (args) { - if (args.section) { - $scope.section = args.section; - } - if (args.customTreeParams) { - $scope.customtreeparams = args.customTreeParams; - } - if (args.treeAlias) { - $scope.treealias = args.treeAlias; - } - if (args.cacheKey) { - $scope.cachekey = args.cacheKey; - } - } + /** + * Re-loads the tree with the updated parameters + * @param {any} args either a string representing the 'section' or an object containing: 'section', 'treeAlias', 'customTreeParams', 'cacheKey' + */ + function load(args) { + if (Utilities.isString(args)) { + $scope.section = args; + } + else if (args) { + if (args.section) { + $scope.section = args.section; + } + if (args.customTreeParams) { + $scope.customtreeparams = args.customTreeParams; + } + if (args.treeAlias) { + $scope.treealias = args.treeAlias; + } + if (args.cacheKey) { + $scope.cachekey = args.cacheKey; + } + } - return loadTree(); - } + return loadTree(); + } - function reloadNode(node) { + function reloadNode(node) { - if (!node) { - node = $scope.currentNode; - } + if (!node) { + node = $scope.currentNode; + } - if (node) { - return $scope.loadChildren(node, true); - } + if (node) { + return $scope.loadChildren(node, true); + } - return $q.reject(); - } + return $q.reject(); + } - /** - * Used to do the tree syncing - * @param {any} args - * @returns a promise with an object containing 'node' and 'activate' - */ - function syncTree(args) { - if (!args) { - throw "args cannot be null"; - } - if (!args.path) { - throw "args.path cannot be null"; - } + /** + * Used to do the tree syncing + * @param {any} args + * @returns a promise with an object containing 'node' and 'activate' + */ + function syncTree(args) { + if (!args) { + throw "args cannot be null"; + } + if (!args.path) { + throw "args.path cannot be null"; + } - if (Utilities.isString(args.path)) { - args.path = args.path.replace('"', '').split(','); - } + if (Utilities.isString(args.path)) { + args.path = args.path.replace('"', '').split(','); + } - //Filter the path for root node ids (we don't want to pass in -1 or 'init') + //Filter the path for root node ids (we don't want to pass in -1 or 'init') - args.path = _.filter(args.path, function (item) { return (item !== "init" && item !== "-1"); }); + args.path = _.filter(args.path, function (item) { return (item !== "init" && item !== "-1"); }); - var treeNode = loadActiveTree(args.tree); + var treeNode = loadActiveTree(args.tree); - return treeService.syncTree({ - node: treeNode, - path: args.path, - forceReload: args.forceReload - }).then(function (data) { + return treeService.syncTree({ + node: treeNode, + path: args.path, + forceReload: args.forceReload + }).then(function (data) { - if (args.activate === undefined || args.activate === true) { - $scope.currentNode = data; - } + if (args.activate === undefined || args.activate === true) { + $scope.currentNode = data; + } - emitEvent("treeSynced", { node: data, activate: args.activate }); + emitEvent("treeSynced", { node: data, activate: args.activate }); - return $q.when({ node: data, activate: args.activate }); - }, function (data) { - return $q.reject(data); - }, function (data) { - //on notification - if (data.type === "treeNodeExpanded") { - //raise the event - emitEvent("treeNodeExpanded", { tree: $scope.tree, node: data.node, children: data.children }); - } - }); + return $q.when({ node: data, activate: args.activate }); + }, function (data) { + return $q.reject(data); + }, function (data) { + //on notification + if (data.type === "treeNodeExpanded") { + //raise the event + emitEvent("treeNodeExpanded", { tree: $scope.tree, node: data.node, children: data.children }); + } + }); - } + } - /** This will check the section tree loaded and return all actual root nodes based on a tree type (non group nodes, non section groups) */ - function getTreeRootNodes() { - var roots; - if ($scope.tree.root.containsGroups) { - //all children in this case are group nodes, so we want the children of these children - roots = _.reduce( - //get the array of array of children - _.map($scope.tree.root.children, function (n) { - return n.children - }), function (m, p) { - //combine the arrays to one array - return m.concat(p) - }); - } - else { - roots = [$scope.tree.root].concat($scope.tree.root.children); - } - - return _.filter(roots, function (node) { - return node && node.metaData && node.metaData.treeAlias; - }); - } - - //given a tree alias, this will search the current section tree for the specified tree alias and set the current active tree to it's root node - function hasTree(treeAlias) { - - if (!$scope.tree) { - throw "Err in umbtree.directive.loadActiveTree, $scope.tree is null"; - } - - if (!treeAlias) { - return false; - } - - var treeRoots = getTreeRootNodes(); - var foundTree = _.find(treeRoots, function (node) { - return node.metaData.treeAlias.toUpperCase() === treeAlias.toUpperCase(); - }); - - return foundTree !== undefined; - } - - //given a tree alias, this will search the current section tree for the specified tree alias and set the current active tree to it's root node - function loadActiveTree(treeAlias) { - - if (!$scope.tree) { - throw "Err in umbtree.directive.loadActiveTree, $scope.tree is null"; - } - - //if its not specified, it should have been specified before - if (!treeAlias) { - if (!$scope.activeTree) { - throw "Err in umbtree.directive.loadActiveTree, $scope.activeTree is null"; - } - return $scope.activeTree; - } - - var treeRoots = getTreeRootNodes(); - $scope.activeTree = _.find(treeRoots, function (node) { - return node.metaData.treeAlias.toUpperCase() === treeAlias.toUpperCase(); - }); - - if (!$scope.activeTree) { - throw "Could not find the tree " + treeAlias; - } - - emitEvent("activeTreeLoaded", { tree: $scope.activeTree }); - - return $scope.activeTree; - } - - /** Method to load in the tree data */ - function loadTree() { - if ($scope.section) { - - //default args - var args = { section: $scope.section, tree: $scope.treealias, cacheKey: $scope.cachekey, isDialog: $scope.isdialog ? $scope.isdialog : false }; - - //add the extra query string params if specified - if ($scope.customtreeparams) { - args["queryString"] = $scope.customtreeparams; - } - - return treeService.getTree(args) - .then(function (data) { - //Only use the tree data, if we are still on the correct section - if(data.alias !== $scope.section){ - return $q.reject(); - } - - //set the data once we have it - $scope.tree = data; - - //set the root as the current active tree - $scope.activeTree = $scope.tree.root; - - emitEvent("treeLoaded", { tree: $scope.tree }); - emitEvent("treeNodeExpanded", { tree: $scope.tree, node: $scope.tree.root, children: $scope.tree.root.children }); - - return $q.when(data); - }, function (reason) { - notificationsService.error("Tree Error", reason); - return $q.reject(reason); - }); - } - else { - return $q.reject(); - } - } - - function loadChildren(node, forceReload) { - //emit treeNodeExpanding event, if a callback object is set on the tree - emitEvent("treeNodeExpanding", { tree: $scope.tree, node: node }); - - //standardising - if (!node.children) { - node.children = []; - } - - if (forceReload || (node.hasChildren && node.children.length === 0)) { - //get the children from the tree service - return treeService.loadNodeChildren({ node: node, section: $scope.section, isDialog: $scope.isdialog }) - .then(function (data) { - //emit expanded event - emitEvent("treeNodeExpanded", { tree: $scope.tree, node: node, children: data }); - - return $q.when(data); - }); - } - else { - emitEvent("treeNodeExpanded", { tree: $scope.tree, node: node, children: node.children }); - node.expanded = true; - - return $q.when(node.children); - } - } - - /** Returns the css classses assigned to the node (div element) */ - $scope.getNodeCssClass = function (node) { - if (!node) { - return ''; - } - - // TODO: This is called constantly because as a method in a template it's re-evaluated pretty much all the time - // it would be better if we could cache the processing. The problem is that some of these things are dynamic. - - var css = []; - if (node.cssClasses) { - node.cssClasses.forEach(c => css.push(c)); - } - - return css.join(" "); - }; - - $scope.selectEnabledNodeClass = node => - node && node.selected ? 'icon sprTree icon-check green temporary' : '-hidden'; - - /* helper to force reloading children of a tree node */ - $scope.loadChildren = (node, forceReload) => loadChildren(node, forceReload); - - /** - Method called when the options button next to the root node is called. - The tree doesnt know about this, so it raises an event to tell the parent controller - about it. - */ - $scope.options = function (n, ev) { - emitEvent("treeOptionsClick", { element: $element, node: n, event: ev }); - }; - - /** - Method called when an item is clicked in the tree, this passes the - DOM element, the tree node object and the original click - and emits it as a treeNodeSelect element if there is a callback object - defined on the tree - */ - $scope.select = function (n, ev) { - - if (n.metaData && n.metaData.noAccess === true) { - ev.preventDefault(); - return; - } - - //on tree select we need to remove the current node - - // whoever handles this will need to make sure the correct node is selected - //reset current node selection - $scope.currentNode = null; - - emitEvent("treeNodeSelect", { element: $element, node: n, event: ev }); - }; - - $scope.altSelect = function (n, ev) { - emitEvent("treeNodeAltSelect", { element: $element, tree: $scope.tree, node: n, event: ev }); - }; - - //call the onInit method, if the result is a promise then load the tree after that resolves (if it's not a promise this will just resolve automatically). - //NOTE: The promise cannot be rejected, else the tree won't be loaded and we'll get exceptions if some API calls syncTree or similar. - $q.when($scope.onInit(), function (args) { - - //the promise resolution can pass in parameters - if (args) { - if (args.section) { - $scope.section = args.section; - } - if (args.cacheKey) { - $scope.cachekey = args.cacheKey; - } - if (args.customTreeParams) { - $scope.customtreeparams = args.customTreeParams; - } - } - - //load the tree - loadTree().then(function () { - //because angular doesn't return a promise for the resolve method, we need to resort to some hackery, else - //like normal JS promises we could do resolve(...).then() - if (args && args.onLoaded && Utilities.isFunction(args.onLoaded)) { - args.onLoaded(); - } - }); + /** This will check the section tree loaded and return all actual root nodes based on a tree type (non group nodes, non section groups) */ + function getTreeRootNodes() { + var roots; + if ($scope.tree.root.containsGroups) { + //all children in this case are group nodes, so we want the children of these children + roots = _.reduce( + //get the array of array of children + _.map($scope.tree.root.children, function (n) { + return n.children + }), function (m, p) { + //combine the arrays to one array + return m.concat(p) }); } - }; + else { + roots = [$scope.tree.root].concat($scope.tree.root.children); + } + + return _.filter(roots, function (node) { + return node && node.metaData && node.metaData.treeAlias; + }); + } + + //given a tree alias, this will search the current section tree for the specified tree alias and set the current active tree to it's root node + function hasTree(treeAlias) { + + if (!$scope.tree) { + throw "Err in umbtree.directive.loadActiveTree, $scope.tree is null"; + } + + if (!treeAlias) { + return false; + } + + var treeRoots = getTreeRootNodes(); + var foundTree = _.find(treeRoots, function (node) { + return node.metaData.treeAlias.toUpperCase() === treeAlias.toUpperCase(); + }); + + return foundTree !== undefined; + } + + //given a tree alias, this will search the current section tree for the specified tree alias and set the current active tree to it's root node + function loadActiveTree(treeAlias) { + + if (!$scope.tree) { + throw "Err in umbtree.directive.loadActiveTree, $scope.tree is null"; + } + + //if its not specified, it should have been specified before + if (!treeAlias) { + if (!$scope.activeTree) { + throw "Err in umbtree.directive.loadActiveTree, $scope.activeTree is null"; + } + return $scope.activeTree; + } + + var treeRoots = getTreeRootNodes(); + $scope.activeTree = _.find(treeRoots, function (node) { + return node.metaData.treeAlias.toUpperCase() === treeAlias.toUpperCase(); + }); + + if (!$scope.activeTree) { + throw "Could not find the tree " + treeAlias; + } + + emitEvent("activeTreeLoaded", { tree: $scope.activeTree }); + + return $scope.activeTree; + } + + /** Method to load in the tree data */ + function loadTree() { + if ($scope.section) { + + //default args + var args = { section: $scope.section, tree: $scope.treealias, cacheKey: $scope.cachekey, isDialog: $scope.isdialog ? $scope.isdialog : false }; + + //add the extra query string params if specified + if ($scope.customtreeparams) { + args["queryString"] = $scope.customtreeparams; + } + + return treeService.getTree(args) + .then(function (data) { + //Only use the tree data, if we are still on the correct section + if (data.alias !== $scope.section) { + return $q.reject(); + } + + //set the data once we have it + $scope.tree = data; + + //set the root as the current active tree + $scope.activeTree = $scope.tree.root; + + emitEvent("treeLoaded", { tree: $scope.tree }); + emitEvent("treeNodeExpanded", { tree: $scope.tree, node: $scope.tree.root, children: $scope.tree.root.children }); + + return $q.when(data); + }, function (reason) { + notificationsService.error("Tree Error", reason); + return $q.reject(reason); + }); + } + else { + return $q.reject(); + } + } + + function loadChildren(node, forceReload) { + //emit treeNodeExpanding event, if a callback object is set on the tree + emitEvent("treeNodeExpanding", { tree: $scope.tree, node: node }); + + //standardising + if (!node.children) { + node.children = []; + } + + if (forceReload || (node.hasChildren && node.children.length === 0)) { + //get the children from the tree service + return treeService.loadNodeChildren({ node: node, section: $scope.section, isDialog: $scope.isdialog }) + .then(function (data) { + //emit expanded event + emitEvent("treeNodeExpanded", { tree: $scope.tree, node: node, children: data }); + + return $q.when(data); + }); + } + else { + emitEvent("treeNodeExpanded", { tree: $scope.tree, node: node, children: node.children }); + node.expanded = true; + + return $q.when(node.children); + } + } + + /** Returns the css classses assigned to the node (div element) */ + $scope.getNodeCssClass = function (node) { + if (!node) { + return ''; + } + + // TODO: This is called constantly because as a method in a template it's re-evaluated pretty much all the time + // it would be better if we could cache the processing. The problem is that some of these things are dynamic. + + var css = []; + if (node.cssClasses) { + node.cssClasses.forEach(c => css.push(c)); + } + + return css.join(" "); + }; + + $scope.selectEnabledNodeClass = node => + node && node.selected ? 'icon sprTree icon-check green temporary' : '-hidden'; + + /* helper to force reloading children of a tree node */ + $scope.loadChildren = (node, forceReload) => loadChildren(node, forceReload); + + /** + Method called when the options button next to the root node is called. + The tree doesnt know about this, so it raises an event to tell the parent controller + about it. + */ + $scope.options = function (n, ev) { + emitEvent("treeOptionsClick", { element: $element, node: n, event: ev }); + }; + + /** + Method called when an item is clicked in the tree, this passes the + DOM element, the tree node object and the original click + and emits it as a treeNodeSelect element if there is a callback object + defined on the tree + */ + $scope.select = function (n, ev) { + + if (n.metaData && n.metaData.noAccess === true) { + ev.preventDefault(); + return; + } + + //on tree select we need to remove the current node - + // whoever handles this will need to make sure the correct node is selected + //reset current node selection + $scope.currentNode = null; + + emitEvent("treeNodeSelect", { element: $element, node: n, event: ev }); + }; + + $scope.altSelect = function (n, ev) { + emitEvent("treeNodeAltSelect", { element: $element, tree: $scope.tree, node: n, event: ev }); + }; + + //call the onInit method, if the result is a promise then load the tree after that resolves (if it's not a promise this will just resolve automatically). + //NOTE: The promise cannot be rejected, else the tree won't be loaded and we'll get exceptions if some API calls syncTree or similar. + $q.when($scope.onInit(), function (args) { + + //the promise resolution can pass in parameters + if (args) { + if (args.section) { + $scope.section = args.section; + } + if (args.cacheKey) { + $scope.cachekey = args.cacheKey; + } + if (args.customTreeParams) { + $scope.customtreeparams = args.customTreeParams; + } + } + + //load the tree + loadTree().then(function () { + //because angular doesn't return a promise for the resolve method, we need to resort to some hackery, else + //like normal JS promises we could do resolve(...).then() + if (args && args.onLoaded && Utilities.isFunction(args.onLoaded)) { + args.onLoaded(); + } + }); + + localizationService.localize("visuallyHiddenTexts_openContextNode").then((value) => { + $scope.labels.openContextNode = value; + }); + + }); + } + }; } angular.module("umbraco.directives").directive('umbTree', umbTreeDirective); diff --git a/src/Umbraco.Web.UI.Client/src/common/services/util.service.js b/src/Umbraco.Web.UI.Client/src/common/services/util.service.js index 1fb5884d4c..e69d49d82e 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/util.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/util.service.js @@ -62,23 +62,23 @@ function dateHelper() { return { - convertToServerStringTime: function (momentLocal, serverOffsetMinutes, format) { + convertToServerStringTime: function (momentLocal, serverOffsetMinutes, format = "YYYY-MM-DD HH:mm:ss") { //get the formatted offset time in HH:mm (server time offset is in minutes) - var formattedOffset = (serverOffsetMinutes > 0 ? "+" : "-") + + const formattedOffset = (serverOffsetMinutes > 0 ? "+" : "-") + moment() .startOf('day') .minutes(Math.abs(serverOffsetMinutes)) .format('HH:mm'); - var server = moment.utc(momentLocal).utcOffset(formattedOffset); - return server.format(format ? format : "YYYY-MM-DD HH:mm:ss"); + const server = moment.utc(momentLocal).utcOffset(formattedOffset); + return server.format(format); }, - convertToLocalMomentTime: function (strVal, serverOffsetMinutes) { + convertToLocalMomentTime: function (strVal, serverOffsetMinutes, format = "YYYY-MM-DDTHH:mm:ss") { //get the formatted offset time in HH:mm (server time offset is in minutes) - var formattedOffset = (serverOffsetMinutes > 0 ? "+" : "-") + + const formattedOffset = (serverOffsetMinutes > 0 ? "+" : "-") + moment() .startOf('day') .minutes(Math.abs(serverOffsetMinutes)) @@ -88,12 +88,12 @@ function dateHelper() { //otherwise known as https://en.wikipedia.org/wiki/ISO_8601. This is the default format returned from the server //since that is the default formatter for newtonsoft.json. When it is in this format, we need to tell moment //to load the date as UTC so it's not changed, otherwise load it normally - var isoFormat; + let isoFormat; if (strVal.indexOf("T") > -1 && strVal.endsWith("Z")) { - isoFormat = moment.utc(strVal).format("YYYY-MM-DDTHH:mm:ss") + formattedOffset; + isoFormat = moment.utc(strVal).format(format) + formattedOffset; } else { - isoFormat = moment(strVal).format("YYYY-MM-DDTHH:mm:ss") + formattedOffset; + isoFormat = moment(strVal).format(format) + formattedOffset; } //create a moment with the iso format which will include the offset with the correct time @@ -101,16 +101,16 @@ function dateHelper() { return moment.parseZone(isoFormat).local(); }, - getLocalDate: function (date, culture, format) { + getLocalDate: function (date, culture, format, parsingFormat = "YYYY-MM-DD HH:mm:ss") { if (date) { - var dateVal; - var serverOffset = Umbraco.Sys.ServerVariables.application.serverTimeOffset; - var localOffset = new Date().getTimezoneOffset(); - var serverTimeNeedsOffsetting = -serverOffset !== localOffset; + let dateVal; + const serverOffset = Umbraco.Sys.ServerVariables.application.serverTimeOffset; + const localOffset = new Date().getTimezoneOffset(); + const serverTimeNeedsOffsetting = -serverOffset !== localOffset; if (serverTimeNeedsOffsetting) { - dateVal = this.convertToLocalMomentTime(date, serverOffset); + dateVal = this.convertToLocalMomentTime(date, serverOffset, format); } else { - dateVal = moment(date, 'YYYY-MM-DD HH:mm:ss'); + dateVal = moment(date, parsingFormat); } return dateVal.locale(culture).format(format); } diff --git a/src/Umbraco.Web.UI.Client/src/views/common/login-2fa.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/login-2fa.controller.js index 6c57085c8b..ad821819c6 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/login-2fa.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/login-2fa.controller.js @@ -1,6 +1,7 @@ -angular.module("umbraco").controller("Umbraco.Login2faController", +angular.module("umbraco").controller("Umbraco.Login2faController", function ($scope, userService, authResource) { - let vm = this; + const vm = this; + vm.code = ""; vm.provider = ""; vm.providers = []; diff --git a/src/Umbraco.Web.UI.Client/src/views/common/login-2fa.html b/src/Umbraco.Web.UI.Client/src/views/common/login-2fa.html index fd444f180c..be11a78d76 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/login-2fa.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/login-2fa.html @@ -1,6 +1,6 @@ - diff --git a/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/checkboxlist.controller.js b/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/checkboxlist.controller.js index 54ce876e73..cd8ab17b4a 100644 --- a/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/checkboxlist.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/checkboxlist.controller.js @@ -1,7 +1,7 @@ -angular.module("umbraco").controller("Umbraco.PrevalueEditors.CheckboxListController", +angular.module("umbraco").controller("Umbraco.PrevalueEditors.CheckboxListController", function ($scope) { - var vm = this; + const vm = this; vm.configItems = []; vm.viewItems = []; @@ -9,16 +9,16 @@ function init() { - var prevalues = ($scope.model.config ? $scope.model.config.prevalues : $scope.model.prevalues) || []; + const prevalues = ($scope.model.config ? $scope.model.config.prevalues : $scope.model.prevalues) || []; - var items = []; + let items = []; - for (var i = 0; i < prevalues.length; i++) { - var item = {}; + for (let i = 0; i < prevalues.length; i++) { + const item = {}; if (Utilities.isObject(prevalues[i])) { item.value = prevalues[i].value; - item.label = prevalues[i].label; + item.label = prevalues[i].label || prevalues[i].value; } else { item.value = prevalues[i]; @@ -42,10 +42,10 @@ vm.viewItems = []; - var iConfigItem; - for (var i = 0; i < vm.configItems.length; i++) { + let iConfigItem; + for (let i = 0; i < vm.configItems.length; i++) { iConfigItem = vm.configItems[i]; - var isChecked = _.contains(newVal, iConfigItem.value); + const isChecked = _.contains(newVal, iConfigItem.value); vm.viewItems.push({ checked: isChecked, value: iConfigItem.value, @@ -57,7 +57,7 @@ function change(model, value) { - var index = $scope.model.value.indexOf(value); + const index = $scope.model.value.indexOf(value); if (model === true) { //if it doesn't exist in the model, then add it diff --git a/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/checkboxlist.html b/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/checkboxlist.html index eb114bfea5..acebf2f550 100644 --- a/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/checkboxlist.html +++ b/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/checkboxlist.html @@ -1,8 +1,13 @@ -
+
-
    +
    • - + +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/dropdown.controller.js b/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/dropdown.controller.js new file mode 100644 index 0000000000..73f3558f34 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/dropdown.controller.js @@ -0,0 +1,53 @@ +angular.module("umbraco").controller("Umbraco.PrevalueEditors.DropDownListController", + function ($scope) { + + const vm = this; + + vm.configItems = []; + vm.viewItems = []; + + function init() { + + const prevalues = ($scope.model.config ? $scope.model.config.prevalues : $scope.model.prevalues) || []; + + let items = []; + + for (let i = 0; i < prevalues.length; i++) { + const item = {}; + + if (Utilities.isObject(prevalues[i])) { + item.value = prevalues[i].value; + item.label = prevalues[i].label || prevalues[i].value; + } + else { + item.value = prevalues[i]; + item.label = prevalues[i]; + } + + items.push({ value: item.value, label: item.label }); + } + + vm.configItems = items; + + // update view model. + generateViewModel(); + } + + function generateViewModel() { + + vm.viewItems = []; + + let iConfigItem; + for (let i = 0; i < vm.configItems.length; i++) { + iConfigItem = vm.configItems[i]; + vm.viewItems.push({ + value: iConfigItem.value, + label: iConfigItem.label + }); + } + + } + + init(); + + }); diff --git a/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/dropdown.html b/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/dropdown.html new file mode 100644 index 0000000000..adcb04da16 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/dropdown.html @@ -0,0 +1,11 @@ +
    + + + +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/radiobuttonlist.controller.js b/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/radiobuttonlist.controller.js new file mode 100644 index 0000000000..973fc974db --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/radiobuttonlist.controller.js @@ -0,0 +1,53 @@ +angular.module("umbraco").controller("Umbraco.PrevalueEditors.RadiobuttonListController", + function ($scope) { + + const vm = this; + + vm.configItems = []; + vm.viewItems = []; + + function init() { + + const prevalues = ($scope.model.config ? $scope.model.config.prevalues : $scope.model.prevalues) || []; + + let items = []; + + for (let i = 0; i < prevalues.length; i++) { + const item = {}; + + if (Utilities.isObject(prevalues[i])) { + item.value = prevalues[i].value; + item.label = prevalues[i].label || prevalues[i].value; + } + else { + item.value = prevalues[i]; + item.label = prevalues[i]; + } + + items.push({ value: item.value, label: item.label }); + } + + vm.configItems = items; + + // update view model. + generateViewModel(); + } + + function generateViewModel() { + + vm.viewItems = []; + + let iConfigItem; + for (let i = 0; i < vm.configItems.length; i++) { + iConfigItem = vm.configItems[i]; + vm.viewItems.push({ + value: iConfigItem.value, + label: iConfigItem.label + }); + } + + } + + init(); + + }); diff --git a/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/radiobuttonlist.html b/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/radiobuttonlist.html index a5df09c2a1..e8058773f6 100644 --- a/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/radiobuttonlist.html +++ b/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/radiobuttonlist.html @@ -1,22 +1,12 @@ -
    +
    -
      -
    • - - - {{preval.label || preval.value || preval}} +
        +
      • + - -
      • -
      - -
        -
      • - - - {{value.label}} - -
      diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/prevalue/blockgrid.blockconfiguration.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/prevalue/blockgrid.blockconfiguration.html index a30ef4e8db..ac92a117d8 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/prevalue/blockgrid.blockconfiguration.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/prevalue/blockgrid.blockconfiguration.html @@ -20,14 +20,18 @@ ng-click="vm.openBlockOverlay(block)" data-content-element-type-key="{{block.contentElementTypeKey}}">
      - - +
      + +
      +
      + +
      @@ -79,14 +83,18 @@ ng-click="vm.openBlockOverlay(block)" data-content-element-type-key="{{block.contentElementTypeKey}}">
      - - +
      + +
      +
      + +
      diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/umbBlockGridPropertyEditor.component.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/umbBlockGridPropertyEditor.component.js index c37b41aca3..296d98ab7a 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/umbBlockGridPropertyEditor.component.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/umbBlockGridPropertyEditor.component.js @@ -493,7 +493,7 @@ block.showValidation = true; block.hideContentInOverlay = block.config.forceHideContentEditorInOverlay === true; - block.showContent = !block.hideContentInOverlay && block.content?.variants[0].tabs[0]?.properties.length > 0; + block.showContent = !block.hideContentInOverlay && block.content?.variants[0].tabs?.some(tab=>tab.properties.length) === true; block.showSettings = block.config.settingsElementTypeKey != null; // If we have content, otherwise it doesn't make sense to copy. diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/prevalue/blocklist.blockconfiguration.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/prevalue/blocklist.blockconfiguration.html index cdefd12993..062e2dddee 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/prevalue/blocklist.blockconfiguration.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/prevalue/blocklist.blockconfiguration.html @@ -9,12 +9,12 @@ ng-class="{'--isOpen':vm.openBlock === block}" ng-click="vm.openBlockOverlay(block)">
      - +
      + +
      diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/checkboxlist/checkboxlist.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/checkboxlist/checkboxlist.controller.js index 79c436f080..9226ce14d5 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/checkboxlist/checkboxlist.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/checkboxlist/checkboxlist.controller.js @@ -1,7 +1,7 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.CheckboxListController", function ($scope, validationMessageService) { - var vm = this; + const vm = this; vm.configItems = []; vm.viewItems = []; @@ -15,15 +15,16 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.CheckboxListContro if (Utilities.isObject($scope.model.config.items)) { // formatting the items in the dictionary into an array - var sortedItems = []; - var vals = _.values($scope.model.config.items); - var keys = _.keys($scope.model.config.items); + let sortedItems = []; + let vals = _.values($scope.model.config.items); + let keys = _.keys($scope.model.config.items); + for (var i = 0; i < vals.length; i++) { sortedItems.push({ key: keys[i], sortOrder: vals[i].sortOrder, value: vals[i].value}); } // ensure the items are sorted by the provided sort order - sortedItems.sort(function (a, b) { return (a.sortOrder > b.sortOrder) ? 1 : ((b.sortOrder > a.sortOrder) ? -1 : 0); }); + sortedItems.sort((a, b) => (a.sortOrder > b.sortOrder) ? 1 : ((b.sortOrder > a.sortOrder) ? -1 : 0) ); vm.configItems = sortedItems; @@ -37,10 +38,10 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.CheckboxListContro //watch the model.value in case it changes so that we can keep our view model in sync $scope.$watchCollection("model.value", updateViewModel); } - + // Set the message to use for when a mandatory field isn't completed. // Will either use the one provided on the property type or a localised default. - validationMessageService.getMandatoryMessage($scope.model.validation).then(function (value) { + validationMessageService.getMandatoryMessage($scope.model.validation).then(value => { $scope.mandatoryMessage = value; }); @@ -48,10 +49,10 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.CheckboxListContro function updateViewModel(newVal) { - var i = vm.configItems.length; + let i = vm.configItems.length; while(i--) { - var item = vm.configItems[i]; + const item = vm.configItems[i]; // are this item the same in the model if (item.checked !== (newVal.indexOf(item.value) !== -1)) { @@ -69,22 +70,21 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.CheckboxListContro vm.viewItems = []; - var iConfigItem; - for (var i = 0; i < vm.configItems.length; i++) { + let iConfigItem; + for (let i = 0; i < vm.configItems.length; i++) { iConfigItem = vm.configItems[i]; - var isChecked = _.contains(newVal, iConfigItem.value); + const isChecked = _.contains(newVal, iConfigItem.value); vm.viewItems.push({ checked: isChecked, key: iConfigItem.key, value: iConfigItem.value }); } - } function change(model, value) { - var index = $scope.model.value.indexOf(value); + const index = $scope.model.value.indexOf(value); if (model === true) { //if it doesn't exist in the model, then add it diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/checkboxlist/checkboxlist.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/checkboxlist/checkboxlist.html index 70fa72302e..7682af355e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/checkboxlist/checkboxlist.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/checkboxlist/checkboxlist.html @@ -1,8 +1,8 @@ -
      +
      -
        +
        • - - - -
          - - - -
          - - - - - -
          - -
          - +
          + +
          + +
          + + + + +
          +
          +
          -
          -

          {{mandatoryMessage}}

          -

          {{datePickerForm.datepicker.errorMsg}}

          -

          Invalid date

          -
          +
          +

          {{mandatoryMessage}}

          +

          + {{datePickerForm.datepicker.errorMsg}} +

          +

          + Invalid date +

          +
          -

          - This translates to the following time on the server: {{serverTime}}
          - What does this mean? -

          - -
          +

          + + This translates to the following time on the server: + + {{serverTime}}
          + + What does this mean? + +

          +
          diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker3/umbMediaPicker3PropertyEditor.component.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker3/umbMediaPicker3PropertyEditor.component.js index 7811135bc0..59b798c532 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker3/umbMediaPicker3PropertyEditor.component.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker3/umbMediaPicker3PropertyEditor.component.js @@ -80,7 +80,7 @@ vm.sortableOptions.disabled = vm.readonly || vm.singleMode; }); - vm.$onInit = function() { + vm.$onInit = function() { vm.node = vm.node || editorState.getCurrent(); // If we do not have a node on the scope, then disallow drop media @@ -179,7 +179,8 @@ vm.allowAdd = hasAccessToMedia; mediaUploader.init(uploaderOptions).then(() => { - vm.loading = false; + vm.loading = false; + $scope.$apply(); }); }); }; diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/radiobuttons/radiobuttons.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/radiobuttons/radiobuttons.controller.js index b30c04fdb9..f0293a179f 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/radiobuttons/radiobuttons.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/radiobuttons/radiobuttons.controller.js @@ -1,7 +1,7 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.RadioButtonsController", function ($scope, validationMessageService) { - var vm = this; + const vm = this; vm.viewItems = []; @@ -13,24 +13,25 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.RadioButtonsContro if (Utilities.isObject($scope.model.config.items)) { // formatting the items in the dictionary into an array - var sortedItems = []; - var vals = _.values($scope.model.config.items); - var keys = _.keys($scope.model.config.items); - for (var i = 0; i < vals.length; i++) { + let sortedItems = []; + let vals = _.values($scope.model.config.items); + let keys = _.keys($scope.model.config.items); + + for (let i = 0; i < vals.length; i++) { sortedItems.push({ key: keys[i], sortOrder: vals[i].sortOrder, value: vals[i].value }); } // ensure the items are sorted by the provided sort order - sortedItems.sort(function (a, b) { return (a.sortOrder > b.sortOrder) ? 1 : ((b.sortOrder > a.sortOrder) ? -1 : 0); }); + sortedItems.sort((a, b) => (a.sortOrder > b.sortOrder) ? 1 : ((b.sortOrder > a.sortOrder) ? -1 : 0) ); vm.viewItems = sortedItems; } // Set the message to use for when a mandatory field isn't completed. // Will either use the one provided on the property type or a localised default. - validationMessageService.getMandatoryMessage($scope.model.validation).then(function (value) { + validationMessageService.getMandatoryMessage($scope.model.validation).then(value => { $scope.mandatoryMessage = value; - }); + }); } diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/radiobuttons/radiobuttons.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/radiobuttons/radiobuttons.html index 0f069b0994..49f08fd165 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/radiobuttons/radiobuttons.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/radiobuttons/radiobuttons.html @@ -1,7 +1,7 @@ -
          +
          -
            +
            • (); - if (umbracoRouteValues != null) + if (CheckActiveDynamicRoutingAndNoException(httpContext)) { return null!; } @@ -198,7 +195,7 @@ public class UmbracoRouteValueTransformer : DynamicRouteValueTransformer IPublishedRequest publishedRequest = await RouteRequestAsync(umbracoContext); - umbracoRouteValues = await _routeValuesFactory.CreateAsync(httpContext, publishedRequest); + UmbracoRouteValues? umbracoRouteValues = await _routeValuesFactory.CreateAsync(httpContext, publishedRequest); // now we need to do some public access checks umbracoRouteValues = @@ -243,6 +240,30 @@ public class UmbracoRouteValueTransformer : DynamicRouteValueTransformer return newValues; } + /// + /// Check whether dynamic routing is currently active in an request where no exception has occured. + /// + /// [true] if dynamic routing is active, [false] if inactive or an exception has occured. + private static bool CheckActiveDynamicRoutingAndNoException(HttpContext httpContext) + { + // Don't execute if there are already UmbracoRouteValues assigned. + // This can occur if someone else is dynamically routing and in which case we don't want to overwrite + // the routing work being done there. + UmbracoRouteValues? umbracoRouteValues = httpContext.Features.Get(); + + // No dynamic routing is active currently. + if (umbracoRouteValues == null) + { + return false; + } + + // There is dynamic routing active so we have to check whether an exception occured in the current request. + // If this is the case we do want dynamic routing since it might be an Umbraco content page which is used as an error page. + IExceptionHandlerFeature? exceptionHandlerFeature = httpContext.Features.Get(); + + return exceptionHandlerFeature == null; + } + private async Task RouteRequestAsync(IUmbracoContext umbracoContext) { // ok, process diff --git a/templates/Umbraco.Templates.csproj b/templates/Umbraco.Templates.csproj index 5413f1ee0f..d68cc8a9de 100644 --- a/templates/Umbraco.Templates.csproj +++ b/templates/Umbraco.Templates.csproj @@ -1,7 +1,7 @@ Umbraco CMS - Templates - Coontains templates for Umbraco CMS. + Contains templates for Umbraco CMS, as well as the templates for creating new packages for the Umbraco CMS. Template false false @@ -45,6 +45,9 @@ + + + <_TemplateJsonFiles Include="**\.template.config\template.json" Exclude="bin\**;obj\**" /> @@ -55,9 +58,8 @@ - <_PackageFiles Remove="@(_TemplateJsonFiles)" /> <_PackageFiles Include="%(_TemplateJsonFiles.DestinationFile)"> - %(RelativeDir) + %(_TemplateJsonFiles.RelativeDir) diff --git a/templates/UmbracoProject/appsettings.json b/templates/UmbracoProject/appsettings.json index 03457ad0b7..6678478951 100644 --- a/templates/UmbracoProject/appsettings.json +++ b/templates/UmbracoProject/appsettings.json @@ -36,6 +36,9 @@ }, "Unattended": { "UpgradeUnattended": true + }, + "Security": { + "AllowConcurrentLogins": false } } } diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/HelpPanel/helpLinks.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/HelpPanel/helpLinks.spec.ts new file mode 100644 index 0000000000..882eaf1bbf --- /dev/null +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/HelpPanel/helpLinks.spec.ts @@ -0,0 +1,41 @@ +import { test } from '@umbraco/playwright-testhelpers'; +import { expect } from '@playwright/test'; + +test.describe("Help panel links", () => { + const enCulture = "en-US"; + + test.beforeEach(async ({ umbracoApi }, testInfo) => { + await umbracoApi.report.report(testInfo); + await umbracoApi.login(); + await umbracoApi.users.setCurrentLanguage(enCulture); + }); + + test.afterEach(async ({ umbracoApi }) => { + await umbracoApi.users.setCurrentLanguage(enCulture); + }); + +test("Check the youtube link works as expected", async ({ page, umbracoUi }) => { + // Action + await umbracoUi.clickElement(umbracoUi.getGlobalHelp()); + let watchVideoLink = await page.locator('[key="help_umbracoLearningBase"]'); + await watchVideoLink.click(); + + // Wait for a popup event + const youtubeConsent = page.waitForEvent("popup"); + const youtubePopup = await youtubeConsent; + + if (youtubePopup) { + // If the YouTube consent popup is displayed, interact with it + const rejectAllButton = await youtubePopup.locator('[aria-label="Reject all"]').first(); + await rejectAllButton.waitFor({ state: "visible" }); + await rejectAllButton.click(); + + // Assert the URL of the popup + await expect(youtubePopup).toHaveURL(/.*UmbracoLearningBase/); + await youtubePopup.close(); + + } else { + await expect(page).toHaveURL(/.*UmbracoLearningBase/); + } + }); +}); \ No newline at end of file diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/HelpPanel/systemInformation.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/HelpPanel/systemInformation.spec.ts index a202a60053..643ec2fad9 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/HelpPanel/systemInformation.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/HelpPanel/systemInformation.spec.ts @@ -1,36 +1,38 @@ -import {ConstantHelper, test, UiHelpers} from '@umbraco/playwright-testhelpers'; -import {expect, Page} from "@playwright/test"; +import { ConstantHelper, test, UiHelpers } from '@umbraco/playwright-testhelpers'; +import { expect, Page } from '@playwright/test'; -test.describe('System Information', () => { +test.describe("System Information", () => { const enCulture = "en-US"; const dkCulture = "da-DK"; - test.beforeEach(async ({ page, umbracoApi }, testInfo) => { + test.beforeEach(async ({ umbracoApi }, testInfo) => { await umbracoApi.report.report(testInfo); await umbracoApi.login(); await umbracoApi.users.setCurrentLanguage(enCulture); }); - test.afterEach(async ({page, umbracoApi}) => { + test.afterEach(async ({ umbracoApi }) => { await umbracoApi.users.setCurrentLanguage(enCulture); }); async function openSystemInformation(page: Page, umbracoUi: UiHelpers) { //We have to wait for page to load, if the site is slow await umbracoUi.clickElement(umbracoUi.getGlobalHelp()); - await expect(page.locator('.umb-help-list-item').last()).toBeVisible(); - await umbracoUi.clickElement(page.locator('.umb-help-list-item').last()); - await page.locator('.umb-drawer-content').scrollIntoViewIfNeeded(); + await expect(page.locator(".umb-help-list-item").last()).toBeVisible(); + await umbracoUi.clickElement( + Promise.resolve(page.locator(".umb-help-list-item").last()) + ); + await page.locator(".umb-drawer-content").scrollIntoViewIfNeeded(); } - test('Check System Info Displays', async ({page, umbracoApi, umbracoUi}) => { + test('Check System Info Displays', async ({page, umbracoUi}) => { await openSystemInformation(page, umbracoUi); await expect(page.locator('.table').locator('tr')).toHaveCount(15); await expect(await page.locator("tr", {hasText: "Current Culture"})).toContainText(enCulture); await expect(await page.locator("tr", {hasText: "Current UI Culture"})).toContainText(enCulture); }); - test('Checks language displays correctly after switching', async ({page, umbracoApi, umbracoUi}) => { + test("Checks language displays correctly after switching", async ({ page, umbracoUi }) => { //Navigate to edit user and change language await umbracoUi.clickElement(umbracoUi.getGlobalUser()); await page.locator('[alias="editUser"]').click(); @@ -39,7 +41,6 @@ test.describe('System Information', () => { await umbracoUi.isSuccessNotificationVisible(); await openSystemInformation(page, umbracoUi); - //Assert await expect(await page.locator("tr", {hasText: "Current Culture"})).toContainText(dkCulture); await expect(await page.locator("tr", {hasText: "Current UI Culture"})).toContainText(dkCulture); diff --git a/tests/Umbraco.Tests.Common/Published/PublishedContentXmlAdapter.cs b/tests/Umbraco.Tests.Common/Published/PublishedContentXmlAdapter.cs index 816a5e4fba..ea02bce244 100644 --- a/tests/Umbraco.Tests.Common/Published/PublishedContentXmlAdapter.cs +++ b/tests/Umbraco.Tests.Common/Published/PublishedContentXmlAdapter.cs @@ -12,6 +12,7 @@ using Umbraco.Cms.Core.PropertyEditors; using Umbraco.Cms.Core.Strings; using Umbraco.Cms.Infrastructure.PublishedCache; using Umbraco.Cms.Infrastructure.PublishedCache.DataSource; +using Umbraco.Cms.Infrastructure.PublishedCache.Persistence; using Umbraco.Cms.Infrastructure.Serialization; using Umbraco.Cms.Tests.Common.Builders; using Umbraco.Cms.Tests.Common.Builders.Extensions; diff --git a/tests/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs b/tests/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs index 162713d8fb..7465c7677e 100644 --- a/tests/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs +++ b/tests/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs @@ -238,9 +238,12 @@ namespace Umbraco.Cms.Tests.Integration.TestServerTest .AddWebsite() .AddUmbracoSqlServerSupport() .AddUmbracoSqliteSupport() + .AddDeliveryApi() + .AddComposers() .AddTestServices(TestHelper); // This is the important one! CustomTestSetup(builder); + builder.Build(); } diff --git a/tests/Umbraco.Tests.Integration/TestServerTest/UmbracoWebApplicationFactory.cs b/tests/Umbraco.Tests.Integration/TestServerTest/UmbracoWebApplicationFactory.cs index bfebf0b41b..902326973b 100644 --- a/tests/Umbraco.Tests.Integration/TestServerTest/UmbracoWebApplicationFactory.cs +++ b/tests/Umbraco.Tests.Integration/TestServerTest/UmbracoWebApplicationFactory.cs @@ -16,7 +16,6 @@ public class UmbracoWebApplicationFactory : WebApplicationFactory /// Method to create the IHostBuilder - /// Method to perform an action before IHost starts public UmbracoWebApplicationFactory(Func createHostBuilder) => _createHostBuilder = createHostBuilder; protected override IHostBuilder CreateHostBuilder() => _createHostBuilder(); diff --git a/tests/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTestBase.cs b/tests/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTestBase.cs index 4f5623b7b0..302ce8b35d 100644 --- a/tests/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTestBase.cs +++ b/tests/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTestBase.cs @@ -11,6 +11,8 @@ using NUnit.Framework; using Serilog; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Infrastructure.Persistence; using Umbraco.Cms.Tests.Common.Testing; @@ -26,7 +28,7 @@ namespace Umbraco.Cms.Tests.Integration.Testing; public abstract class UmbracoIntegrationTestBase { private static readonly object s_dbLocker = new(); - private static ITestDatabase s_dbInstance; + private static ITestDatabase? s_dbInstance; private static TestDbMeta s_fixtureDbMeta; private static int s_testCount = 1; private readonly List _fixtureTeardown = new(); @@ -122,9 +124,16 @@ public abstract class UmbracoIntegrationTestBase var databaseFactory = serviceProvider.GetRequiredService(); var loggerFactory = serviceProvider.GetRequiredService(); var connectionStrings = serviceProvider.GetRequiredService>(); + var eventAggregator = serviceProvider.GetRequiredService(); // This will create a db, install the schema and ensure the app is configured to run SetupTestDatabase(testDatabaseFactoryProvider, connectionStrings, databaseFactory, loggerFactory, state); + + if (TestOptions.Database != UmbracoTestOptions.Database.None) + { + eventAggregator.Publish(new UnattendedInstallNotification()); + } + } private void ConfigureTestDatabaseFactory( diff --git a/tests/Umbraco.Tests.UnitTests/TestHelpers/PublishedSnapshotServiceTestBase.cs b/tests/Umbraco.Tests.UnitTests/TestHelpers/PublishedSnapshotServiceTestBase.cs index 2828e812d5..46f950ee60 100644 --- a/tests/Umbraco.Tests.UnitTests/TestHelpers/PublishedSnapshotServiceTestBase.cs +++ b/tests/Umbraco.Tests.UnitTests/TestHelpers/PublishedSnapshotServiceTestBase.cs @@ -212,7 +212,7 @@ public class PublishedSnapshotServiceTestBase } /// - /// Initializes the with a source of data + /// Initializes the with a source of data. /// protected void InitializedCache( IEnumerable contentNodeKits, diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/CoreThings/UdiTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/CoreThings/UdiTests.cs index 98bf4a608c..ccca8f9f3c 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/CoreThings/UdiTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/CoreThings/UdiTests.cs @@ -205,6 +205,28 @@ public class UdiTests Assert.Throws(() => new UdiRange(guidUdi, "x")); } + [Test] + public void TryParseTest() + { + // try parse to "Udi" + var stringUdiString = "umb://document/b9a56165-6c4e-4e79-8277-620430174ad3"; + Assert.IsTrue(UdiParser.TryParse(stringUdiString, out Udi udi1)); + Assert.AreEqual("b9a56165-6c4e-4e79-8277-620430174ad3", udi1 is GuidUdi guidUdi1 ? guidUdi1.Guid.ToString() : string.Empty); + + // try parse to "Udi" + Assert.IsFalse(UdiParser.TryParse("nope", out Udi udi2)); + Assert.IsNull(udi2); + + // try parse to "GuidUdi?" + Assert.IsTrue(UdiParser.TryParse(stringUdiString, out GuidUdi? guidUdi3)); + Assert.AreEqual("b9a56165-6c4e-4e79-8277-620430174ad3", guidUdi3.Guid.ToString()); + + // try parse to "GuidUdi?" + Assert.IsFalse(UdiParser.TryParse("nope", out GuidUdi? guidUdi4)); + Assert.IsNull(guidUdi4); + + } + [Test] public void SerializationTest() { diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Models/VariationTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Models/VariationTests.cs index d7580be8ff..ca2ae76428 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Models/VariationTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Models/VariationTests.cs @@ -118,6 +118,9 @@ public class VariationTests /// /// Asserts the result of /// + /// The variation to validate + /// The culture to validate + /// The segment to validate /// Validate using Exact + Wildcards flags /// Validate using non Exact + no Wildcard flags /// Validate using Exact + no Wildcard flags diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Scoping/ScopeUnitTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Scoping/ScopeUnitTests.cs index c547b1af3e..4f3cd4775f 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Scoping/ScopeUnitTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Scoping/ScopeUnitTests.cs @@ -24,7 +24,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Scoping /// /// Creates a ScopeProvider with mocked internals. /// - /// The mock of the ISqlSyntaxProvider2, used to count method calls. + /// /// private ScopeProvider GetScopeProvider(out Mock lockingMechanism) { diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Controllers/MemberControllerUnitTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Controllers/MemberControllerUnitTests.cs index 84aa1173ef..79038453ad 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Controllers/MemberControllerUnitTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Controllers/MemberControllerUnitTests.cs @@ -515,6 +515,7 @@ public class MemberControllerUnitTests /// Back office security accessor /// Password changer class /// The global settings + /// The two factor login service /// A member controller for the tests private MemberController CreateSut( IMemberService memberService, diff --git a/version.json b/version.json index 8f4e1d83d5..4e0b8cb597 100644 --- a/version.json +++ b/version.json @@ -1,5 +1,5 @@ { - "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json", + "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/main/src/NerdBank.GitVersioning/version.schema.json", "version": "13.0.0-rc", "assemblyVersion": { "precision": "build" @@ -12,7 +12,12 @@ "^refs/heads/main$", "^refs/heads/release/" ], + "release": { + "branchName": "release/{version}", + "tagName": "release-{version}" + }, "cloudBuild": { + "setAllVariables": true, "buildNumber": { "enabled": true }