diff --git a/Directory.Build.props b/Directory.Build.props index 1d8f755b342c..87042fbd86ce 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -35,7 +35,7 @@ true - + $(MSBuildThisFileDirectory) diff --git a/Directory.Packages.props b/Directory.Packages.props index 75ca1ce51b44..513d9dd2a446 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -45,8 +45,8 @@ - - + + diff --git a/build/azure-pipelines.yml b/build/azure-pipelines.yml index aa0ddf1db68a..cc71b984230f 100644 --- a/build/azure-pipelines.yml +++ b/build/azure-pipelines.yml @@ -338,7 +338,9 @@ stages: # Integration Tests (SQL Server) - job: timeoutInMinutes: 120 - condition: or(eq(stageDependencies.Build.A.outputs['build.NBGV_PublicRelease'], 'True'), ${{parameters.sqlServerIntegrationTests}}) + # We are currently encountering issues when running SQL Server Linux tests Microsoft.Data.SqlClient.SqlException (0x80131904) + # condition: or(eq(stageDependencies.Build.A.outputs['build.NBGV_PublicRelease'], 'True'), ${{parameters.sqlServerIntegrationTests}}) + condition: eq(${{parameters.sqlServerIntegrationTests}}, True) displayName: Integration Tests (SQL Server) strategy: matrix: @@ -529,8 +531,8 @@ stages: workingDirectory: tests/Umbraco.Tests.AcceptanceTest # Install Playwright and dependencies - - pwsh: npx playwright install --with-deps - displayName: Install Playwright + - pwsh: npx playwright install chromium + displayName: Install Playwright only with Chromium browser workingDirectory: tests/Umbraco.Tests.AcceptanceTest # Test @@ -560,13 +562,24 @@ stages: displayName: Copy Playwright results condition: succeededOrFailed() - # Publish + # Publish test artifacts - task: PublishPipelineArtifact@1 displayName: Publish test artifacts condition: succeededOrFailed() inputs: targetPath: $(Build.ArtifactStagingDirectory) artifact: "Acceptance Test Results - $(Agent.JobName) - Attempt #$(System.JobAttempt)" + + # Publish test results + - task: PublishTestResults@2 + displayName: "Publish test results" + condition: succeededOrFailed() + inputs: + testResultsFormat: 'JUnit' + testResultsFiles: '*.xml' + searchFolder: "tests/Umbraco.Tests.AcceptanceTest/results" + testRunTitle: "$(Agent.JobName)" + - job: displayName: E2E Tests (SQL Server) condition: or(eq(stageDependencies.Build.A.outputs['build.NBGV_PublicRelease'], 'True'), ${{parameters.sqlServerAcceptanceTests}}) @@ -685,14 +698,13 @@ stages: workingDirectory: tests/Umbraco.Tests.AcceptanceTest # Install Playwright and dependencies - - pwsh: npx playwright install --with-deps - displayName: Install Playwright + - pwsh: npx playwright install chromium + displayName: Install Playwright only with Chromium browser workingDirectory: tests/Umbraco.Tests.AcceptanceTest # Test - pwsh: $(testCommand) displayName: Run Playwright tests - continueOnError: true workingDirectory: tests/Umbraco.Tests.AcceptanceTest env: CI: true @@ -725,7 +737,7 @@ stages: displayName: Copy Playwright results condition: succeededOrFailed() - # Publish + # Publish test artifacts - task: PublishPipelineArtifact@1 displayName: Publish test artifacts condition: succeededOrFailed() @@ -733,6 +745,16 @@ stages: targetPath: $(Build.ArtifactStagingDirectory) artifact: "Acceptance Test Results - $(Agent.JobName) - Attempt #$(System.JobAttempt)" + # Publish test results + - task: PublishTestResults@2 + displayName: "Publish test results" + condition: succeededOrFailed() + inputs: + testResultsFormat: 'JUnit' + testResultsFiles: '*.xml' + searchFolder: "tests/Umbraco.Tests.AcceptanceTest/results" + testRunTitle: "$(Agent.JobName)" + ############################################### ## Release ############################################### @@ -745,6 +767,8 @@ stages: condition: and(succeeded(), or(eq(dependencies.Build.outputs['A.build.NBGV_PublicRelease'], 'True'), ${{parameters.myGetDeploy}})) jobs: - job: + pool: + vmImage: "windows-latest" # NuGetCommand@2 is no longer supported on Ubuntu 24.04 so we'll use windows until an alternative is available. displayName: Push to pre-release feed steps: - checkout: none @@ -771,6 +795,8 @@ stages: condition: and(succeeded(), or(eq(dependencies.Build.outputs['A.build.NBGV_PublicRelease'], 'True'), ${{parameters.nuGetDeploy}})) jobs: - job: + pool: + vmImage: "windows-latest" # NuGetCommand@2 is no longer supported on Ubuntu 24.04 so we'll use windows until an alternative is available. displayName: Push to NuGet steps: - checkout: none diff --git a/src/Umbraco.Cms.Api.Delivery/Caching/DeliveryApiOutputCachePolicy.cs b/src/Umbraco.Cms.Api.Delivery/Caching/DeliveryApiOutputCachePolicy.cs index da1580554cbb..c1c823f8a9ab 100644 --- a/src/Umbraco.Cms.Api.Delivery/Caching/DeliveryApiOutputCachePolicy.cs +++ b/src/Umbraco.Cms.Api.Delivery/Caching/DeliveryApiOutputCachePolicy.cs @@ -1,5 +1,6 @@ -using Microsoft.AspNetCore.OutputCaching; +using Microsoft.AspNetCore.OutputCaching; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Primitives; using Umbraco.Cms.Core.DeliveryApi; namespace Umbraco.Cms.Api.Delivery.Caching; @@ -7,9 +8,13 @@ namespace Umbraco.Cms.Api.Delivery.Caching; internal sealed class DeliveryApiOutputCachePolicy : IOutputCachePolicy { private readonly TimeSpan _duration; + private readonly StringValues _varyByHeaderNames; - public DeliveryApiOutputCachePolicy(TimeSpan duration) - => _duration = duration; + public DeliveryApiOutputCachePolicy(TimeSpan duration, StringValues varyByHeaderNames) + { + _duration = duration; + _varyByHeaderNames = varyByHeaderNames; + } ValueTask IOutputCachePolicy.CacheRequestAsync(OutputCacheContext context, CancellationToken cancellationToken) { @@ -18,8 +23,14 @@ ValueTask IOutputCachePolicy.CacheRequestAsync(OutputCacheContext context, Cance .RequestServices .GetRequiredService(); - context.EnableOutputCaching = requestPreviewService.IsPreview() is false; + IApiAccessService apiAccessService = context + .HttpContext + .RequestServices + .GetRequiredService(); + + context.EnableOutputCaching = requestPreviewService.IsPreview() is false && apiAccessService.HasPublicAccess(); context.ResponseExpirationTimeSpan = _duration; + context.CacheVaryByRules.HeaderNames = _varyByHeaderNames; return ValueTask.CompletedTask; } diff --git a/src/Umbraco.Cms.Api.Delivery/Controllers/Content/ByRouteContentApiController.cs b/src/Umbraco.Cms.Api.Delivery/Controllers/Content/ByRouteContentApiController.cs index 4fed35a28196..7120553fb17e 100644 --- a/src/Umbraco.Cms.Api.Delivery/Controllers/Content/ByRouteContentApiController.cs +++ b/src/Umbraco.Cms.Api.Delivery/Controllers/Content/ByRouteContentApiController.cs @@ -19,7 +19,7 @@ public class ByRouteContentApiController : ContentApiItemControllerBase private readonly IRequestRedirectService _requestRedirectService; private readonly IRequestPreviewService _requestPreviewService; private readonly IRequestMemberAccessService _requestMemberAccessService; - private const string PreviewContentRequestPathPrefix = $"/{Constants.DeliveryApi.Routing.PreviewContentPathPrefix}"; + private const string PreviewContentRequestPathPrefix = $"/{Umbraco.Cms.Core.Constants.DeliveryApi.Routing.PreviewContentPathPrefix}"; [Obsolete($"Please use the constructor that does not accept {nameof(IPublicAccessService)}. Will be removed in V14.")] public ByRouteContentApiController( @@ -145,6 +145,11 @@ private async Task HandleRequest(string path) path = DecodePath(path); path = path.Length == 0 ? "/" : path; + if (_apiContentPathResolver.IsResolvablePath(path) is false) + { + return NotFound(); + } + IPublishedContent? contentItem = GetContent(path); if (contentItem is not null) { diff --git a/src/Umbraco.Cms.Api.Delivery/DependencyInjection/UmbracoBuilderExtensions.cs b/src/Umbraco.Cms.Api.Delivery/DependencyInjection/UmbracoBuilderExtensions.cs index 20141f210853..48399a62e3f1 100644 --- a/src/Umbraco.Cms.Api.Delivery/DependencyInjection/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.Cms.Api.Delivery/DependencyInjection/UmbracoBuilderExtensions.cs @@ -5,6 +5,7 @@ using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Primitives; using Umbraco.Cms.Api.Common.DependencyInjection; using Umbraco.Cms.Api.Delivery.Accessors; using Umbraco.Cms.Api.Delivery.Caching; @@ -108,12 +109,20 @@ private static IUmbracoBuilder AddOutputCache(this IUmbracoBuilder builder) if (outputCacheSettings.ContentDuration.TotalSeconds > 0) { - options.AddPolicy(Constants.DeliveryApi.OutputCache.ContentCachePolicy, new DeliveryApiOutputCachePolicy(outputCacheSettings.ContentDuration)); + options.AddPolicy( + Constants.DeliveryApi.OutputCache.ContentCachePolicy, + new DeliveryApiOutputCachePolicy( + outputCacheSettings.ContentDuration, + new StringValues([Constants.DeliveryApi.HeaderNames.AcceptLanguage, Constants.DeliveryApi.HeaderNames.StartItem]))); } if (outputCacheSettings.MediaDuration.TotalSeconds > 0) { - options.AddPolicy(Constants.DeliveryApi.OutputCache.MediaCachePolicy, new DeliveryApiOutputCachePolicy(outputCacheSettings.MediaDuration)); + options.AddPolicy( + Constants.DeliveryApi.OutputCache.MediaCachePolicy, + new DeliveryApiOutputCachePolicy( + outputCacheSettings.MediaDuration, + Constants.DeliveryApi.HeaderNames.StartItem)); } }); diff --git a/src/Umbraco.Cms.Api.Delivery/Filters/SwaggerContentDocumentationFilter.cs b/src/Umbraco.Cms.Api.Delivery/Filters/SwaggerContentDocumentationFilter.cs index 0d2d35fb2399..748033e4a25f 100644 --- a/src/Umbraco.Cms.Api.Delivery/Filters/SwaggerContentDocumentationFilter.cs +++ b/src/Umbraco.Cms.Api.Delivery/Filters/SwaggerContentDocumentationFilter.cs @@ -1,4 +1,4 @@ -using Microsoft.OpenApi.Any; +using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Models; using Swashbuckle.AspNetCore.SwaggerGen; using Umbraco.Cms.Api.Delivery.Configuration; @@ -21,7 +21,7 @@ protected override void ApplyOperation(OpenApiOperation operation, OperationFilt operation.Parameters.Add(new OpenApiParameter { - Name = "Accept-Language", + Name = Core.Constants.DeliveryApi.HeaderNames.AcceptLanguage, In = ParameterLocation.Header, Required = false, Description = "Defines the language to return. Use this when querying language variant content items.", @@ -37,7 +37,7 @@ protected override void ApplyOperation(OpenApiOperation operation, OperationFilt operation.Parameters.Add(new OpenApiParameter { - Name = "Preview", + Name = Core.Constants.DeliveryApi.HeaderNames.Preview, In = ParameterLocation.Header, Required = false, Description = "Whether to request draft content.", @@ -46,7 +46,7 @@ protected override void ApplyOperation(OpenApiOperation operation, OperationFilt operation.Parameters.Add(new OpenApiParameter { - Name = "Start-Item", + Name = Core.Constants.DeliveryApi.HeaderNames.StartItem, In = ParameterLocation.Header, Required = false, Description = "URL segment or GUID of a root content item.", diff --git a/src/Umbraco.Cms.Api.Delivery/Filters/SwaggerDocumentationFilterBase.cs b/src/Umbraco.Cms.Api.Delivery/Filters/SwaggerDocumentationFilterBase.cs index 52acddaca960..8a450a2f9b97 100644 --- a/src/Umbraco.Cms.Api.Delivery/Filters/SwaggerDocumentationFilterBase.cs +++ b/src/Umbraco.Cms.Api.Delivery/Filters/SwaggerDocumentationFilterBase.cs @@ -1,4 +1,4 @@ -using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc; using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Models; using Swashbuckle.AspNetCore.SwaggerGen; @@ -63,7 +63,7 @@ protected void AddFields(OpenApiOperation operation, OperationFilterContext cont protected void AddApiKey(OpenApiOperation operation) => operation.Parameters.Add(new OpenApiParameter { - Name = "Api-Key", + Name = Core.Constants.DeliveryApi.HeaderNames.ApiKey, In = ParameterLocation.Header, Required = false, Description = "API key specified through configuration to authorize access to the API.", diff --git a/src/Umbraco.Cms.Api.Delivery/Handlers/InitializeMemberApplicationNotificationHandler.cs b/src/Umbraco.Cms.Api.Delivery/Handlers/InitializeMemberApplicationNotificationHandler.cs index 06b6472506a0..32df9adeef18 100644 --- a/src/Umbraco.Cms.Api.Delivery/Handlers/InitializeMemberApplicationNotificationHandler.cs +++ b/src/Umbraco.Cms.Api.Delivery/Handlers/InitializeMemberApplicationNotificationHandler.cs @@ -6,6 +6,7 @@ using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; using Umbraco.Cms.Infrastructure.Security; namespace Umbraco.Cms.Api.Delivery.Handlers; @@ -16,16 +17,21 @@ internal sealed class InitializeMemberApplicationNotificationHandler : INotifica private readonly ILogger _logger; private readonly DeliveryApiSettings _deliveryApiSettings; private readonly IServiceScopeFactory _serviceScopeFactory; + private readonly IServerRoleAccessor _serverRoleAccessor; + private static readonly SemaphoreSlim _locker = new(1); + private static bool _isInitialized = false; public InitializeMemberApplicationNotificationHandler( IRuntimeState runtimeState, IOptions deliveryApiSettings, ILogger logger, - IServiceScopeFactory serviceScopeFactory) + IServiceScopeFactory serviceScopeFactory, + IServerRoleAccessor serverRoleAccessor) { _runtimeState = runtimeState; _logger = logger; _serviceScopeFactory = serviceScopeFactory; + _serverRoleAccessor = serverRoleAccessor; _deliveryApiSettings = deliveryApiSettings.Value; } @@ -36,34 +42,55 @@ public async Task HandleAsync(UmbracoApplicationStartingNotification notificatio return; } - // we cannot inject the IMemberApplicationManager because it ultimately takes a dependency on the DbContext ... and during - // install that is not allowed (no connection string means no DbContext) - using IServiceScope scope = _serviceScopeFactory.CreateScope(); - IMemberApplicationManager memberApplicationManager = scope.ServiceProvider.GetRequiredService(); - - if (_deliveryApiSettings.MemberAuthorization?.AuthorizationCodeFlow?.Enabled is not true) + if (_serverRoleAccessor.CurrentServerRole is ServerRole.Subscriber) { - await memberApplicationManager.DeleteMemberApplicationAsync(cancellationToken); + // subscriber instances should not alter the member application return; } - if (ValidateRedirectUrls(_deliveryApiSettings.MemberAuthorization.AuthorizationCodeFlow.LoginRedirectUrls) is false) + try { - await memberApplicationManager.DeleteMemberApplicationAsync(cancellationToken); - return; - } + await _locker.WaitAsync(cancellationToken); + if (_isInitialized) + { + return; + } + + _isInitialized = true; + + // we cannot inject the IMemberApplicationManager because it ultimately takes a dependency on the DbContext ... and during + // install that is not allowed (no connection string means no DbContext) + using IServiceScope scope = _serviceScopeFactory.CreateScope(); + IMemberApplicationManager memberApplicationManager = scope.ServiceProvider.GetRequiredService(); + + if (_deliveryApiSettings.MemberAuthorization?.AuthorizationCodeFlow?.Enabled is not true) + { + await memberApplicationManager.DeleteMemberApplicationAsync(cancellationToken); + return; + } - if (_deliveryApiSettings.MemberAuthorization.AuthorizationCodeFlow.LogoutRedirectUrls.Any() - && ValidateRedirectUrls(_deliveryApiSettings.MemberAuthorization.AuthorizationCodeFlow.LogoutRedirectUrls) is false) + 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); + } + finally { - await memberApplicationManager.DeleteMemberApplicationAsync(cancellationToken); - return; + _locker.Release(); } - - await memberApplicationManager.EnsureMemberApplicationAsync( - _deliveryApiSettings.MemberAuthorization.AuthorizationCodeFlow.LoginRedirectUrls, - _deliveryApiSettings.MemberAuthorization.AuthorizationCodeFlow.LogoutRedirectUrls, - cancellationToken); } private bool ValidateRedirectUrls(Uri[] redirectUrls) diff --git a/src/Umbraco.Cms.Api.Delivery/Services/ApiAccessService.cs b/src/Umbraco.Cms.Api.Delivery/Services/ApiAccessService.cs index 0ba7df9b4932..0a581af9f59f 100644 --- a/src/Umbraco.Cms.Api.Delivery/Services/ApiAccessService.cs +++ b/src/Umbraco.Cms.Api.Delivery/Services/ApiAccessService.cs @@ -1,4 +1,4 @@ -using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.DeliveryApi; @@ -29,7 +29,7 @@ public ApiAccessService(IHttpContextAccessor httpContextAccessor, IOptionsMonito private bool IfEnabled(Func condition) => _deliveryApiSettings.Enabled && condition(); private bool HasValidApiKey() => _deliveryApiSettings.ApiKey.IsNullOrWhiteSpace() == false - && _deliveryApiSettings.ApiKey.Equals(GetHeaderValue("Api-Key")); + && _deliveryApiSettings.ApiKey.Equals(GetHeaderValue(Core.Constants.DeliveryApi.HeaderNames.ApiKey)); private bool IfMediaEnabled(Func condition) => _deliveryApiSettings is { Enabled: true, Media.Enabled: true } && condition(); } diff --git a/src/Umbraco.Cms.Api.Delivery/Services/RequestPreviewService.cs b/src/Umbraco.Cms.Api.Delivery/Services/RequestPreviewService.cs index 874e2af7bb1e..25689125f538 100644 --- a/src/Umbraco.Cms.Api.Delivery/Services/RequestPreviewService.cs +++ b/src/Umbraco.Cms.Api.Delivery/Services/RequestPreviewService.cs @@ -1,4 +1,4 @@ -using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http; using Umbraco.Cms.Core.DeliveryApi; namespace Umbraco.Cms.Api.Delivery.Services; @@ -11,5 +11,5 @@ public RequestPreviewService(IHttpContextAccessor httpContextAccessor) } /// - public bool IsPreview() => GetHeaderValue("Preview") == "true"; + public bool IsPreview() => string.Equals(GetHeaderValue(Core.Constants.DeliveryApi.HeaderNames.Preview), "true", StringComparison.OrdinalIgnoreCase); } diff --git a/src/Umbraco.Cms.Api.Delivery/Services/RequestStartItemProvider.cs b/src/Umbraco.Cms.Api.Delivery/Services/RequestStartItemProvider.cs index dd72d930bdbe..f3169e1a75b4 100644 --- a/src/Umbraco.Cms.Api.Delivery/Services/RequestStartItemProvider.cs +++ b/src/Umbraco.Cms.Api.Delivery/Services/RequestStartItemProvider.cs @@ -58,5 +58,5 @@ public RequestStartItemProvider( } /// - public string? RequestedStartItem() => GetHeaderValue("Start-Item"); + public string? RequestedStartItem() => GetHeaderValue(Constants.DeliveryApi.HeaderNames.StartItem); } diff --git a/src/Umbraco.Cms.Api.Delivery/Services/RoutingServiceBase.cs b/src/Umbraco.Cms.Api.Delivery/Services/RoutingServiceBase.cs index 32a8affd6158..c12b701e6da5 100644 --- a/src/Umbraco.Cms.Api.Delivery/Services/RoutingServiceBase.cs +++ b/src/Umbraco.Cms.Api.Delivery/Services/RoutingServiceBase.cs @@ -36,7 +36,7 @@ protected Uri GetDefaultRequestUri(string requestedPath) } protected static string GetContentRoute(DomainAndUri domainAndUri, Uri contentRoute) - => $"{domainAndUri.ContentId}{DomainUtilities.PathRelativeToDomain(domainAndUri.Uri, contentRoute.AbsolutePath)}"; + => $"{domainAndUri.ContentId}{DomainUtilities.PathRelativeToDomain(domainAndUri.Uri, contentRoute.LocalPath)}"; // Use LocalPath over AbsolutePath to keep the path decoded. protected DomainAndUri? GetDomainAndUriForRoute(Uri contentUrl) { diff --git a/src/Umbraco.Cms.Api.Delivery/Umbraco.Cms.Api.Delivery.csproj b/src/Umbraco.Cms.Api.Delivery/Umbraco.Cms.Api.Delivery.csproj index 8948a49a1a7b..7c31936545d1 100644 --- a/src/Umbraco.Cms.Api.Delivery/Umbraco.Cms.Api.Delivery.csproj +++ b/src/Umbraco.Cms.Api.Delivery/Umbraco.Cms.Api.Delivery.csproj @@ -13,6 +13,9 @@ <_Parameter1>Umbraco.Tests.UnitTests + + <_Parameter1>Umbraco.Tests.Integration + <_Parameter1>DynamicProxyGenAssembly2 diff --git a/src/Umbraco.Cms.Persistence.EFCore/Scoping/EFCoreScope.cs b/src/Umbraco.Cms.Persistence.EFCore/Scoping/EFCoreScope.cs index 461b09334c4a..382bcf5593e0 100644 --- a/src/Umbraco.Cms.Persistence.EFCore/Scoping/EFCoreScope.cs +++ b/src/Umbraco.Cms.Persistence.EFCore/Scoping/EFCoreScope.cs @@ -127,10 +127,16 @@ public override void Dispose() Locks.ClearLocks(InstanceId); - if (ParentScope is null) + // Since we can nest EFCoreScopes in other scopes derived from CoreScope, we should check whether our ParentScope OR the base ParentScope exists. + // Only if neither do do we take responsibility for ensuring the locks are cleared. + // Eventually the highest parent will clear the locks. + // Further, these locks are a reference to the locks of the highest parent anyway (see the constructor of CoreScope). +#pragma warning disable SA1100 // Do not prefix calls with base unless local implementation exists (justification: provides additional clarify here that this is defined on the base class). + if (ParentScope is null && base.HasParentScope is false) { Locks.EnsureLocksCleared(InstanceId); } +#pragma warning restore SA1100 // Do not prefix calls with base unless local implementation exists _efCoreScopeProvider.PopAmbientScope(); diff --git a/src/Umbraco.Core/Cache/AppCacheExtensions.cs b/src/Umbraco.Core/Cache/AppCacheExtensions.cs index 0f1f242ed01f..56130c4ea589 100644 --- a/src/Umbraco.Core/Cache/AppCacheExtensions.cs +++ b/src/Umbraco.Core/Cache/AppCacheExtensions.cs @@ -43,7 +43,7 @@ public static void InsertCacheItem( public static T? GetCacheItem(this IAppCache provider, string cacheKey) { var result = provider.Get(cacheKey); - if (result == null) + if (IsRetrievedItemNull(result)) { return default; } @@ -54,11 +54,13 @@ public static void InsertCacheItem( public static T? GetCacheItem(this IAppCache provider, string cacheKey, Func getCacheItem) { var result = provider.Get(cacheKey, () => getCacheItem()); - if (result == null) + if (IsRetrievedItemNull(result)) { return default; } return result.TryConvertTo().Result; } + + private static bool IsRetrievedItemNull(object? result) => result is null or (object)Cms.Core.Constants.Cache.NullRepresentationInCache; } diff --git a/src/Umbraco.Core/Cache/CacheKeys.cs b/src/Umbraco.Core/Cache/CacheKeys.cs index 04ae44a64743..db507491ac4d 100644 --- a/src/Umbraco.Core/Cache/CacheKeys.cs +++ b/src/Umbraco.Core/Cache/CacheKeys.cs @@ -22,4 +22,6 @@ public static class CacheKeys public const string ContentRecycleBinCacheKey = "recycleBin_content"; public const string MediaRecycleBinCacheKey = "recycleBin_media"; + + public const string MemberUserNameCachePrefix = "uRepo_userNameKey+"; } diff --git a/src/Umbraco.Core/Cache/DistributedCacheExtensions.cs b/src/Umbraco.Core/Cache/DistributedCacheExtensions.cs index f1a925da7574..9baee336aede 100644 --- a/src/Umbraco.Core/Cache/DistributedCacheExtensions.cs +++ b/src/Umbraco.Core/Cache/DistributedCacheExtensions.cs @@ -159,15 +159,30 @@ public static void RefreshMemberCache(this DistributedCache dc, params IMember[] => dc.RefreshMemberCache(members.AsEnumerable()); public static void RefreshMemberCache(this DistributedCache dc, IEnumerable members) - => dc.RefreshByPayload(MemberCacheRefresher.UniqueId, members.DistinctBy(x => (x.Id, x.Username)).Select(x => new MemberCacheRefresher.JsonPayload(x.Id, x.Username, false))); - + => dc.RefreshByPayload( + MemberCacheRefresher.UniqueId, + GetPayloads(members, false)); [Obsolete("Use the overload accepting IEnumerable instead to avoid allocating arrays. This overload will be removed in Umbraco 13.")] public static void RemoveMemberCache(this DistributedCache dc, params IMember[] members) => dc.RemoveMemberCache(members.AsEnumerable()); public static void RemoveMemberCache(this DistributedCache dc, IEnumerable members) - => dc.RefreshByPayload(MemberCacheRefresher.UniqueId, members.DistinctBy(x => (x.Id, x.Username)).Select(x => new MemberCacheRefresher.JsonPayload(x.Id, x.Username, true))); + => dc.RefreshByPayload( + MemberCacheRefresher.UniqueId, + GetPayloads(members, true)); + + // Internal for unit test. + internal static IEnumerable GetPayloads(IEnumerable members, bool removed) + => members + .DistinctBy(x => (x.Id, x.Username)) + .Select(x => new MemberCacheRefresher.JsonPayload(x.Id, x.Username, removed) + { + PreviousUsername = x.HasAdditionalData && + x.AdditionalData!.TryGetValue(Cms.Core.Constants.Entities.AdditionalDataKeys.MemberPreviousUserName, out var previousUsername) + ? previousUsername?.ToString() + : null, + }); #endregion diff --git a/src/Umbraco.Core/Cache/PartialViewCacheInvalidators/IMemberPartialViewCacheInvalidator.cs b/src/Umbraco.Core/Cache/PartialViewCacheInvalidators/IMemberPartialViewCacheInvalidator.cs new file mode 100644 index 000000000000..289dad528844 --- /dev/null +++ b/src/Umbraco.Core/Cache/PartialViewCacheInvalidators/IMemberPartialViewCacheInvalidator.cs @@ -0,0 +1,16 @@ +namespace Umbraco.Cms.Core.Cache.PartialViewCacheInvalidators; + +/// +/// Defines behaviours for clearing of cached partials views that are configured to be cached individually by member. +/// +public interface IMemberPartialViewCacheInvalidator +{ + /// + /// Clears the partial view cache items for the specified member ids. + /// + /// The member Ids to clear the cache for. + /// + /// Called from the when a member is saved or deleted. + /// + void ClearPartialViewCacheItems(IEnumerable memberIds); +} diff --git a/src/Umbraco.Core/Cache/PartialViewCacheInvalidators/NoopMemberPartialViewCacheInvalidator.cs b/src/Umbraco.Core/Cache/PartialViewCacheInvalidators/NoopMemberPartialViewCacheInvalidator.cs new file mode 100644 index 000000000000..3fd366f2c7f0 --- /dev/null +++ b/src/Umbraco.Core/Cache/PartialViewCacheInvalidators/NoopMemberPartialViewCacheInvalidator.cs @@ -0,0 +1,9 @@ +namespace Umbraco.Cms.Core.Cache.PartialViewCacheInvalidators; + +internal class NoopMemberPartialViewCacheInvalidator : IMemberPartialViewCacheInvalidator +{ + public void ClearPartialViewCacheItems(IEnumerable memberIds) + { + // No operation performed, this is a no-op implementation. + } +} diff --git a/src/Umbraco.Core/Cache/Refreshers/Implement/MemberCacheRefresher.cs b/src/Umbraco.Core/Cache/Refreshers/Implement/MemberCacheRefresher.cs index ac9dac5a09d1..99d6004ec6d9 100644 --- a/src/Umbraco.Core/Cache/Refreshers/Implement/MemberCacheRefresher.cs +++ b/src/Umbraco.Core/Cache/Refreshers/Implement/MemberCacheRefresher.cs @@ -1,5 +1,8 @@ // using Newtonsoft.Json; +using Microsoft.Extensions.DependencyInjection; +using Umbraco.Cms.Core.Cache.PartialViewCacheInvalidators; +using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Notifications; @@ -15,10 +18,37 @@ public sealed class MemberCacheRefresher : PayloadCacheRefresherBase()) + { + } - public MemberCacheRefresher(AppCaches appCaches, IJsonSerializer serializer, IIdKeyMap idKeyMap, IEventAggregator eventAggregator, ICacheRefresherNotificationFactory factory) - : base(appCaches, serializer, eventAggregator, factory) => + public MemberCacheRefresher( + AppCaches appCaches, + IJsonSerializer serializer, + IIdKeyMap idKeyMap, + IEventAggregator eventAggregator, + ICacheRefresherNotificationFactory factory, + IMemberPartialViewCacheInvalidator memberPartialViewCacheInvalidator) + : base(appCaches, serializer, eventAggregator, factory) + { _idKeyMap = idKeyMap; + _memberPartialViewCacheInvalidator = memberPartialViewCacheInvalidator; + } #region Indirect @@ -40,6 +70,8 @@ public JsonPayload(int id, string? username, bool removed) public string? Username { get; } + public string? PreviousUsername { get; set; } + public bool Removed { get; } } @@ -67,16 +99,36 @@ public override void Remove(int id) private void ClearCache(params JsonPayload[] payloads) { - AppCaches.ClearPartialViewCache(); + // Clear the partial views cache for all partials that are cached by member, for the updates members. + _memberPartialViewCacheInvalidator.ClearPartialViewCacheItems(payloads.Select(p => p.Id)); + Attempt memberCache = AppCaches.IsolatedCaches.Get(); foreach (JsonPayload p in payloads) { _idKeyMap.ClearCache(p.Id); - if (memberCache.Success) + if (memberCache.Success is false) + { + continue; + } + + memberCache.Result?.Clear(RepositoryCacheKeys.GetKey(p.Id)); + memberCache.Result?.Clear(RepositoryCacheKeys.GetKey(p.Username)); + + // This specific cache key was introduced to fix an issue where the member username could not be the same as the member id, because the cache keys collided. + // This is done in a bit of a hacky way, because the cache key is created internally in the repository, but we need to clear it here. + // Ideally, we want to use a shared way of generating the key between this and the repository. + // Additionally, the RepositoryCacheKeys actually caches the string to avoid re-allocating memory; we would like to also use this in the repository + // See: + // https://github.com/umbraco/Umbraco-CMS/pull/17350 + // https://github.com/umbraco/Umbraco-CMS/pull/17815 + memberCache.Result?.Clear(RepositoryCacheKeys.GetKey(CacheKeys.MemberUserNameCachePrefix + p.Username)); + + // If provided, clear the cache by the previous user name too. + if (string.IsNullOrEmpty(p.PreviousUsername) is false) { - memberCache.Result?.Clear(RepositoryCacheKeys.GetKey(p.Id)); - memberCache.Result?.Clear(RepositoryCacheKeys.GetKey(p.Username)); + memberCache.Result?.Clear(RepositoryCacheKeys.GetKey(p.PreviousUsername)); + memberCache.Result?.Clear(RepositoryCacheKeys.GetKey(CacheKeys.MemberUserNameCachePrefix + p.PreviousUsername)); } } } diff --git a/src/Umbraco.Core/Configuration/Models/IndexingSettings.cs b/src/Umbraco.Core/Configuration/Models/IndexingSettings.cs index ff3bebd9894a..b6dc8f1ad589 100644 --- a/src/Umbraco.Core/Configuration/Models/IndexingSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/IndexingSettings.cs @@ -15,4 +15,8 @@ public class IndexingSettings /// [DefaultValue(StaticExplicitlyIndexEachNestedProperty)] public bool ExplicitlyIndexEachNestedProperty { get; set; } = StaticExplicitlyIndexEachNestedProperty; + /// + /// Defines how many items to index in a single page + /// + public int IndexingPageSize { get; set; } = 10000; } diff --git a/src/Umbraco.Core/Configuration/Models/SecuritySettings.cs b/src/Umbraco.Core/Configuration/Models/SecuritySettings.cs index e68162e6efe0..46fc9cf07339 100644 --- a/src/Umbraco.Core/Configuration/Models/SecuritySettings.cs +++ b/src/Umbraco.Core/Configuration/Models/SecuritySettings.cs @@ -2,6 +2,7 @@ // See LICENSE for more details. using System.ComponentModel; +using System.ComponentModel.DataAnnotations; namespace Umbraco.Cms.Core.Configuration.Models; @@ -27,6 +28,8 @@ public class SecuritySettings internal const int StaticMemberDefaultLockoutTimeInMinutes = 30 * 24 * 60; internal const int StaticUserDefaultLockoutTimeInMinutes = 30 * 24 * 60; + internal const long StaticUserDefaultFailedLoginDurationInMilliseconds = 1000; + internal const long StaticUserMinimumFailedLoginDurationInMilliseconds = 250; /// /// Gets or sets a value indicating whether to keep the user logged in. @@ -125,4 +128,28 @@ public class SecuritySettings /// [DefaultValue(StaticAllowConcurrentLogins)] public bool AllowConcurrentLogins { get; set; } = StaticAllowConcurrentLogins; + + /// + /// Gets or sets the default duration (in milliseconds) of failed login attempts. + /// + /// + /// The default duration (in milliseconds) of failed login attempts. + /// + /// + /// The user login endpoint ensures that failed login attempts take at least as long as the average successful login. + /// However, if no successful logins have occurred, this value is used as the default duration. + /// + [Range(0, int.MaxValue)] // TODO (V17): Change property type to short and update maximum range to short.MaxValue + [DefaultValue(StaticUserDefaultFailedLoginDurationInMilliseconds)] + public long UserDefaultFailedLoginDurationInMilliseconds { get; set; } = StaticUserDefaultFailedLoginDurationInMilliseconds; + + /// + /// Gets or sets the minimum duration (in milliseconds) of failed login attempts. + /// + /// + /// The minimum duration (in milliseconds) of failed login attempts. + /// + [Range(0, int.MaxValue)] // TODO (V17): Change property type to short and update maximum range to short.MaxValue + [DefaultValue(StaticUserMinimumFailedLoginDurationInMilliseconds)] + public long UserMinimumFailedLoginDurationInMilliseconds { get; set; } = StaticUserMinimumFailedLoginDurationInMilliseconds; } diff --git a/src/Umbraco.Core/Constants-Cache.cs b/src/Umbraco.Core/Constants-Cache.cs new file mode 100644 index 000000000000..dd52b2c4fe1a --- /dev/null +++ b/src/Umbraco.Core/Constants-Cache.cs @@ -0,0 +1,15 @@ +namespace Umbraco.Cms.Core; + +public static partial class Constants +{ + public static class Cache + { + /// + /// Defines the string used to represent a null value in the cache. + /// + /// + /// Used in conjunction with the option to cache null values on the repository caches, so we + /// can distinguish a true null "not found" value and a cached null value. + public const string NullRepresentationInCache = "*NULL*"; + } +} diff --git a/src/Umbraco.Core/Constants-DeliveryApi.cs b/src/Umbraco.Core/Constants-DeliveryApi.cs index 85677a23bc55..35d8513fa7cc 100644 --- a/src/Umbraco.Core/Constants-DeliveryApi.cs +++ b/src/Umbraco.Core/Constants-DeliveryApi.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core; +namespace Umbraco.Cms.Core; public static partial class Constants { @@ -24,14 +24,42 @@ public static class Routing public static class OutputCache { /// - /// Output cache policy name for content + /// Output cache policy name for content. /// public const string ContentCachePolicy = "DeliveryApiContent"; /// - /// Output cache policy name for media + /// Output cache policy name for media. /// public const string MediaCachePolicy = "DeliveryApiMedia"; } + + /// + /// Constants for Delivery API header names. + /// + public static class HeaderNames + { + /// + /// Header name for accept language. + /// + public const string AcceptLanguage = "Accept-Language"; + + /// + /// Header name for API key. + /// + public const string ApiKey = "Api-Key"; + + /// + /// Header name for preview. + /// + public const string Preview = "Preview"; + + /// + /// Header name for start item. + /// + public const string StartItem = "Start-Item"; + + + } } } diff --git a/src/Umbraco.Core/Constants-Entities.cs b/src/Umbraco.Core/Constants-Entities.cs new file mode 100644 index 000000000000..d03f3be60d2e --- /dev/null +++ b/src/Umbraco.Core/Constants-Entities.cs @@ -0,0 +1,14 @@ +namespace Umbraco.Cms.Core; + +public static partial class Constants +{ + public static class Entities + { + public static class AdditionalDataKeys + { + public const string MemberPreviousUserName = "previousUsername"; + + public const string MemberGroupPreviousName = "previousName"; + } + } +} diff --git a/src/Umbraco.Core/DeliveryApi/ApiContentPathResolver.cs b/src/Umbraco.Core/DeliveryApi/ApiContentPathResolver.cs index 4ca6be3932aa..c3967c0bf02d 100644 --- a/src/Umbraco.Core/DeliveryApi/ApiContentPathResolver.cs +++ b/src/Umbraco.Core/DeliveryApi/ApiContentPathResolver.cs @@ -15,6 +15,27 @@ public ApiContentPathResolver(IRequestRoutingService requestRoutingService, IApi _apiPublishedContentCache = apiPublishedContentCache; } + [Obsolete("No longer used in V15. Scheduled for removal in V15.")] + public virtual bool IsResolvablePath(string path) + { + // File requests will blow up with an downstream exception in GetRequiredPublishedSnapshot, which fails due to an UmbracoContext + // not being available for what's considered a static file request. + // See: https://github.com/umbraco/Umbraco-CMS/issues/19051 + // Given a URL segment and hence route can't contain a period, we can safely assume that if the last segment of the path contains + // a period, it's a file request and should return null here. + if (IsFileRequest(path)) + { + return false; + } + + return true; + } + + private static bool IsFileRequest(string path) => path + .Split('/', StringSplitOptions.RemoveEmptyEntries) + .LastOrDefault()? + .Contains('.') is true; + public virtual IPublishedContent? ResolveContentPath(string path) { path = path.EnsureStartsWith("/"); diff --git a/src/Umbraco.Core/DeliveryApi/IApiContentPathResolver.cs b/src/Umbraco.Core/DeliveryApi/IApiContentPathResolver.cs index 391f18998e58..d8301666fbc5 100644 --- a/src/Umbraco.Core/DeliveryApi/IApiContentPathResolver.cs +++ b/src/Umbraco.Core/DeliveryApi/IApiContentPathResolver.cs @@ -4,5 +4,8 @@ namespace Umbraco.Cms.Core.DeliveryApi; public interface IApiContentPathResolver { + [Obsolete("No longer used in V15. Scheduled for removal in V15.")] + bool IsResolvablePath(string path) => true; + IPublishedContent? ResolveContentPath(string path); } diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs index 96d200bf7bf1..1da27b02c6ad 100644 --- a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs +++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs @@ -8,12 +8,14 @@ using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Cache; +using Umbraco.Cms.Core.Cache.PartialViewCacheInvalidators; using Umbraco.Cms.Core.Composing; using Umbraco.Cms.Core.Configuration; using Umbraco.Cms.Core.Configuration.Grid; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Diagnostics; using Umbraco.Cms.Core.Dictionary; +using Umbraco.Cms.Core.DynamicRoot; using Umbraco.Cms.Core.Editors; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Features; @@ -35,7 +37,6 @@ using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Core.Security; using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Core.DynamicRoot; using Umbraco.Cms.Core.Sync; using Umbraco.Cms.Core.Telemetry; using Umbraco.Cms.Core.Templates; @@ -341,6 +342,11 @@ private void AddCoreServices() // Data type configuration cache Services.AddUnique(); Services.AddNotificationHandler(); + + // Partial view cache invalidators (no-op, shipped implementation is added in Umbraco.Web.Website, but we + // need this to ensure we have a service registered for this interface even in headless setups). + // See: https://github.com/umbraco/Umbraco-CMS/issues/19661 + Services.AddUnique(); } } } diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/ar.xml b/src/Umbraco.Core/EmbeddedResources/Lang/ar.xml new file mode 100644 index 000000000000..4f69f147ad40 --- /dev/null +++ b/src/Umbraco.Core/EmbeddedResources/Lang/ar.xml @@ -0,0 +1,3880 @@ + + + + مجتمع أومبراكو + https://docs.umbraco.com/umbraco-cms/extending/language-files + + + الثقافة وأسماء المضيفين + سجل المراجعة + تصفح العقدة + تغيير نوع المستند + نسخ + إنشاء + تصدير + إنشاء حزمة + إنشاء مجموعة + حذف + تعطيل + تحرير الإعدادات + إفراغ سلة المحذوفات + تمكين + تصدير نوع المستند + استيراد نوع المستند + استيراد حزمة + تحرير في اللوحة + خروج + نقل + الإشعارات + تقييد الوصول العام + نشر + إلغاء النشر + إعادة تحميل + إعادة نشر الموقع بالكامل + إزالة + إعادة تسمية + استعادة + اختر مكان النسخ + اختر مكان النقل + اختر مكان الاستيراد + إلى في هيكل الشجرة أدناه + اختر مكان نسخ العنصر/العناصر المحددة + اختر مكان نقل العنصر/العناصر المحددة + تم نقله إلى + تم نسخه إلى + تم حذفه + الصلاحيات + التراجع + إرسال للنشر + إرسال للترجمة + تعيين مجموعة + ترتيب + ترجمة + تحديث + تعيين الصلاحيات + إلغاء القفل + إنشاء قالب المحتوى + إعادة إرسال الدعوة + إخفاء الخيارات غير المتاحة + تغيير نوع البيانات + تحرير المحتوى + + + عنوان الرابط + الرابط + مرساة / سلسلة الاستعلام + الاسم + إغلاق هذه النافذة + هل أنت متأكد من أنك تريد الحذف + %0% من %1% عنصر]]> + هل أنت متأكد من أنك تريد التعطيل + هل أنت متأكد من أنك تريد الإزالة + %0%]]> + هل أنت متأكد؟ + هل أنت متأكد؟ + قص + تحرير عنصر القاموس + تحرير اللغة + تحرير الوسائط المحددة + تحرير الويب هوك + إدراج رابط محلي + إدراج حرف + إدراج عنوان رسومي + إدراج صورة + إدراج رابط + انقر لإضافة ماكرو + إدراج جدول + هذا سيحذف اللغة وجميع المحتوى المتعلق باللغة + تغيير الثقافة للغة قد يكون عملية مكلفة وسيؤدي إلى إعادة بناء تخزين المحتوى المؤقت والفهارس + + آخر تحرير + رابط + رابط داخلي: + عند استخدام الروابط المحلية، أدرج "#" أمام الرابط + فتح في نافذة جديدة؟ + هذا الماكرو لا يحتوي على أي خصائص يمكنك تحريرها + لصق + تحرير الصلاحيات لـ + تعيين الصلاحيات لـ + تعيين الصلاحيات لـ %0% لمجموعة المستخدم %1% + حدد مجموعات المستخدمين التي تريد تعيين صلاحيات لها + العناصر في سلة المحذوفات يتم حذفها الآن. يرجى عدم إغلاق هذه النافذة بينما تحدث هذه العملية + + سلة المحذوفات فارغة الآن + عندما يتم حذف العناصر من سلة المحذوفات، ستختفي للأبد + + regexlib.com تواجه حالياً بعض المشاكل، والتي لا نملك السيطرة عليها. نعتذر بشدة عن هذا الإزعاج.]]> + ابحث عن تعبير نمطي لإضافة التحقق لحقل نموذج. مثال: 'email'، 'zip-code'، 'URL'. + + إزالة الماكرو + حقل مطلوب + تم إعادة فهرسة الموقع + تم تحديث تخزين الموقع المؤقت. جميع المحتوى المنشور محدث الآن. بينما جميع المحتوى غير المنشور لا يزال غير منشور + + سيتم تحديث تخزين الموقع المؤقت. سيتم تحديث جميع المحتوى المنشور، بينما سيبقى المحتوى غير المنشور غير منشور. + + عدد الأعمدة + عدد الصفوف + انقر على الصورة لرؤية الحجم الكامل + اختيار العنصر + عرض عنصر التخزين المؤقت + ربط بالأصل + تضمين التوابع + أودّ المجتمع + رابط للصفحة + يفتح المستند المرتبط في نافذة أو علامة تبويب جديدة + رابط للوسائط + تحديد عقدة بداية المحتوى + تحديد حدث + تحديد وسائط + تحديد نوع الوسائط + تحديد أيقونة + تحديد عنصر + تحديد رابط + تحديد ماكرو + تحديد محتوى + تحديد نوع المحتوى + تحديد عقدة بداية الوسائط + تحديد عضو + تحديد مجموعة الأعضاء + تحديد نوع العضو + تحديد عقدة + تحديد اللغات + تحديد الأقسام + تحديد مستخدم + تحديد مستخدمين + لم يتم العثور على أيقونات + لا توجد معاملات لهذا الماكرو + لا توجد ماكروهات متاحة للإدراج + موفروا تسجيل الدخول الخارجي + تفاصيل الاستثناء + تتبع المكدس + الاستثناء الداخلي + ربط الـ + إلغاء ربط الـ + حساب + تحديد محرر + تحديد مقطع + هذا سيحذف العقدة وجميع لغاتها. إذا كنت تريد حذف لغة واحدة فقط، يجب إلغاء نشر العقدة بتلك اللغة بدلاً من ذلك. + + %0%.]]> + %0% من مجموعة %1%]]> + نعم، إزالة + أنت تحذف التخطيط + تعديل التخطيط سيؤدي إلى فقدان البيانات لأي محتوى موجود يعتمد على هذا التكوين. + تحديد التكوين + + + + لاستيراد عنصر قاموس، ابحث عن ملف ".udt" على جهاز الكمبيوت + + + المحتوى + الإدارة + الهيكل + أخرى + + + السماح بالوصول لتعيين الثقافة وأسماء المضيفين + السماح بالوصول لعرض سجل تاريخ العقدة + السماح بالوصول لعرض العقدة + السماح بالوصول لتغيير نوع المستند للعقدة + السماح بالوصول لنسخ العقدة + السماح بالوصول لإنشاء العقد + السماح بالوصول لحذف العقد + السماح بالوصول لنقل العقدة + السماح بالوصول لتعيين وتغيير قيود الوصول للعقدة + السماح بالوصول لنشر العقدة + السماح بالوصول لإلغاء نشر العقدة + السماح بالوصول لتغيير صلاحيات العقدة + السماح بالوصول للتراجع بالعقدة إلى حالة سابقة + السماح بالوصول لإرسال العقدة للموافقة قبل النشر + السماح بالوصول لإرسال العقدة للترجمة + السماح بالوصول لتغيير ترتيب العقد + السماح بالوصول لترجمة العقدة + السماح بالوصول لحفظ العقدة + السماح بالوصول لإنشاء قالب المحتوى + السماح بالوصول لإعداد الإشعارات لعقد المحتوى + + + المحتوى + معلومات + + + تم رفض الإذن. + إضافة نطاق جديد + إضافة النطاق الحالي + إزالة + عقدة غير صالحة. + نطاق واحد أو أكثر له تنسيق غير صالح. + تم تعيين النطاق بالفعل. + اللغة + النطاق + تم إنشاء النطاق الجديد '%0%' + تم حذف النطاق '%0%' + تم تعيين النطاق '%0%' بالفعل + تم تحديث النطاق '%0%' + تحرير النطاقات الحالية + + + وراثة + الثقافة + + أو وراثة الثقافة من العقد الأصل. سيتم التطبيق أيضاً
+ على العقدة الحالية، ما لم ينطبق نطاق أدناه أيضاً.]]> +
+ النطاقات + + + مسح التحديد + تحديد + افعل شيئاً آخر + عريض + إلغاء مسافة الفقرة البادئة + إدراج حقل النموذج + إدراج عنوان رسومي + تحرير Html + مسافة بادئة للفقرة + مائل + توسيط + محاذاة لليسار + محاذاة لليمين + إدراج رابط + إدراج رابط محلي (مرساة) + قائمة نقطية + قائمة رقمية + إدراج ماكرو + إدراج صورة + نشر وإغلاق + نشر مع التوابع + تحرير العلاقات + العودة للقائمة + حفظ + حفظ وإغلاق + حفظ ونشر + حفظ وإرسال للموافقة + حفظ عرض القائمة + جدولة + حفظ ومعاينة + المعاينة معطلة لأنه لا يوجد قالب معين + اختيار النمط + إظهار الأنماط + إدراج جدول + حفظ وإنشاء النماذج + تراجع + إعادة + حذف العلامة + إلغاء + تأكيد + المزيد من خيارات النشر + إرسال + إنشاء النماذج وإغلاق + + + تم حذف الوسائط + تم نقل الوسائط + تم نسخ الوسائط + تم حفظ الوسائط + تم إنشاء الوسائط + + + عرض لـ + تم حذف المحتوى + تم إلغاء نشر المحتوى + تم حفظ المحتوى ونشره + تم حفظ المحتوى ونشره للغات: %0% + تم حفظ المحتوى + تم حفظ المحتوى للغات: %0% + تم نقل المحتوى + تم نسخ المحتوى + تم التراجع بالمحتوى + تم إرسال المحتوى للنشر + تم إرسال المحتوى للنشر للغات: %0% + تم تنفيذ ترتيب العناصر الفرعية بواسطة المستخدم + %0% + تم تعطيل التنظيف للإصدار: %0% + تم تمكين التنظيف للإصدار: %0% + نسخ + نشر + نشر + نقل + حفظ + جديد + حفظ + حذف + إلغاء نشر + تراجع + إرسال للنشر + إرسال للنشر + ترتيب + مخصص + حفظ + حفظ + التاريخ (جميع المتغيرات) + تم إلغاء نشر المحتوى للغات: %0% + إلغاء نشر + + + لا يمكن أن يحتوي اسم المجلد على أحرف غير قانونية. + فشل في حذف العنصر: %0% + + + منشور + حول هذه الصفحة + الاسم المستعار + (كيف تصف الصورة عبر الهاتف) + الروابط البديلة + انقر لتحرير هذا العنصر + أنشئ بواسطة + المؤلف الأصلي + حُدث بواسطة + تاريخ الإنشاء + تاريخ/وقت إنشاء هذا المستند + نوع المستند + تحرير + إزالة في + تم تغيير هذا العنصر بعد النشر + هذا العنصر غير منشور + آخر نشر + لا توجد عناصر للعرض + لا توجد عناصر للعرض في القائمة. + لم تتم إضافة محتوى + لم تتم إضافة أعضاء + نوع الوسائط + رابط إلى عنصر/عناصر الوسائط + مجموعة الأعضاء + الدور + نوع العضو + لم يتم إجراء تغييرات + لم يتم اختيار تاريخ + عنوان الصفحة + عنصر الوسائط هذا ليس له رابط + الخصائص + هذا المستند منشور ولكنه غير مرئي لأن الأصل '%0%' غير منشور + + هذه الثقافة منشورة ولكنها غير مرئية لأنها غير منشورة على الأصل '%0%' + + هذا المستند منشور ولكنه ليس في التخزين المؤقت + لا يمكن الحصول على الرابط + هذا المستند منشور ولكن رابطه سيتصادم مع المحتوى %0% + هذا المستند منشور ولكن لا يمكن توجيه رابطه + نشر + منشور + منشور (تغييرات معلقة) + حالة النشر + + %0% وجميع عناصر المحتوى أدناه وبالتالي جعل محتواها متاحاً للجمهور.]]> + + + نشر في + إلغاء النشر في + مسح التاريخ + تعيين التاريخ + تم تحديث ترتيب الفرز + لترتيب العقد، ما عليك سوى سحب العقد أو النقر على أحد عناوين الأعمدة. يمكنك تحديد عقد متعددة عن طريق الضغط على مفتاح "shift" أو "control" أثناء التحديد + + إحصائيات + العنوان (اختياري) + النص البديل (اختياري) + التسمية التوضيحية (اختياري) + النوع + إلغاء النشر + غير منشور + غير مُنشأ + آخر تحرير + تاريخ/وقت تحرير هذا المستند + إزالة الملف/الملفات + انقر هنا لإزالة الصورة من عنصر الوسائط + انقر هنا لإزالة الملف من عنصر الوسائط + رابط إلى المستند + عضو في مجموعة/مجموعات + ليس عضواً في مجموعة/مجموعات + العناصر الفرعية + الهدف + هذا يترجم إلى الوقت التالي على الخادم: + + ماذا يعني هذا؟]]> + هل أنت متأكد من أنك تريد حذف هذا العنصر؟ + الخاصية %0% تستخدم المحرر %1% غير المدعوم بواسطة المحتوى المتداخل. + + هل أنت متأكد من أنك تريد حذف جميع العناصر؟ + هل أنت متأكد من أنك تريد حذف هذا العنصر؟ + لا يوجد أنواع محتوى مكونة لهذه الخاصية. + إضافة نوع عنصر + تحديد نوع العنصر + حدد المجموعة التي يجب عرض خصائصها. إذا تُركت فارغة، سيتم استخدام المجموعة الأولى على نوع العنصر. + + أدخل تعبير angular لتقييمه مقابل كل عنصر لاسمه. استخدم + + لعرض فهرس العنصر + نوع العنصر المحدد لا يحتوي على أي مجموعات مدعومة (علامات التبويب غير مدعومة بواسطة هذا المحرر، إما قم بتغييرها إلى مجموعات أو استخدم محرر قائمة الكتل). + إضافة مربع نص آخر + إزالة مربع النص هذا + جذر المحتوى + تضمين عناصر المحتوى غير المنشورة. + هذه القيمة مخفية. إذا كنت بحاجة للوصول لعرض هذه القيمة، يرجى الاتصال بمدير الموقع. + + هذه القيمة مخفية. + ما اللغات التي تريد نشرها؟ + ما اللغات التي تريد إرسالها للموافقة؟ + ما اللغات التي تريد جدولتها؟ + حدد اللغات لإلغاء نشرها. إلغاء نشر لغة إلزامية سيلغي نشر جميع اللغات. + + سيتم حفظ جميع المتغيرات الجديدة. + أي متغيرات تريد نشرها؟ + اختر أي متغيرات سيتم حفظها. + المتغيرات التالية مطلوبة لحدوث النشر: + لسنا مستعدين للنشر + مستعد للنشر؟ + مستعد للحفظ؟ + إعادة تعيين نقطة التركيز + إرسال للموافقة + حدد التاريخ والوقت لنشر و/أو إلغاء نشر عنصر المحتوى. + إنشاء جديد + لصق من الحافظة + إزالة العنصر + هذا العنصر في سلة المحذوفات + لا يمكن إضافة محتوى لهذا العنصر + الحفظ غير مسموح + النشر غير مسموح + الإرسال للموافقة غير مسموح + الجدولة غير مسموحة + إلغاء النشر غير مسموح + تحديد جميع المتغيرات + + + %0%]]> + فارغ + اختيار قالب محتوى + تم إنشاء قالب المحتوى + تم إنشاء قالب محتوى من '%0%' + يوجد بالفعل قالب محتوى آخر بنفس الاسم + قالب المحتوى هو محتوى محدد مسبقاً يمكن للمحرر تحديده لاستخدامه كأساس لإنشاء محتوى جديد + + + + انقر للرفع + أو انقر هنا لاختيار الملفات + لا يمكن رفع هذا الملف، ليس له نوع ملف معتمد + لا يمكن رفع هذا الملف، نوع الوسائط بالاسم المستعار '%0%' غير مسموح هنا + لا يمكن رفع هذا الملف، ليس له اسم ملف صالح + الحد الأقصى لحجم الملف هو + جذر الوسائط + فشل في إنشاء مجلد تحت المعرف الأصل %0% + فشل في إعادة تسمية المجلد بالمعرف %0% + اسحب وأفلت ملفك/ملفاتك في المنطقة + فشل في واحد أو أكثر من تحققات أمان الملفات + لا يمكن أن تكون مجلدات الأصل والوجهة هي نفسها + الرفع غير مسموح في هذا الموقع. + + + إنشاء عضو جديد + جميع الأعضاء + مجموعات الأعضاء ليس لها خصائص إضافية للتحرير. + المصادقة الثنائية + يوجد بالفعل عضو بهذا اسم تسجيل الدخول + العضو موجود بالفعل في المجموعة '%0%' + العضو لديه كلمة مرور محددة بالفعل + القفل غير مُمكن لهذا العضو + العضو ليس في المجموعة '%0%' + + + فشل في نسخ نوع المحتوى + فشل في نقل نوع المحتوى + + + فشل في نسخ نوع الوسائط + فشل في نقل نوع الوسائط + اختيار تلقائي + + + فشل في نسخ نوع العضو + + + أين تريد إنشاء %0% الجديد + إنشاء عنصر تحت + حدد نوع المستند الذي تريد إنشاء قالب محتوى له + أدخل اسم المجلد + اختر نوعاً وعنواناً + + أنواع المستندات داخل قسم الإعدادات، بتحرير أنواع العقد الفرعية المسموحة تحت الصلاحيات.]]> + + أنواع المستندات داخل قسم الإعدادات.]]> + الصفحة المحددة في شجرة المحتوى لا تسمح بإنشاء أي صفحات تحتها. + + تحرير الصلاحيات لنوع المستند هذا + إنشاء نوع مستند جديد + + أنواع المستندات داخل قسم الإعدادات، بتغيير خيار السماح كجذر تحت الصلاحيات.]]> + + أنواع الوسائط داخل قسم الإعدادات، بتحرير أنواع العقد الفرعية المسموحة تحت الصلاحيات.]]> + الوسائط المحددة في الشجرة لا تسمح بإنشاء أي وسائط أخرى تحتها. + + تحرير الصلاحيات لأنواع الوسائط هذه + نوع مستند بدون قالب + نوع مستند بقالب + تعريف البيانات لصفحة محتوى يمكن إنشاؤها بواسطة المحررين في شجرة المحتوى ويمكن الوصول إليها مباشرة عبر رابط. + + نوع المستند + تعريف البيانات لمكون محتوى يمكن إنشاؤه بواسطة المحررين في شجرة المحتوى واختياره على صفحات أخرى ولكن ليس له رابط مباشر. + + نوع العنصر + يحدد المخطط لمجموعة متكررة من الخصائص، على سبيل المثال، في محرر خاصية 'قائمة الكتل' أو 'شبكة الكتل'. + + التركيب + يحدد مجموعة قابلة لإعادة الاستخدام من الخصائص التي يمكن تضمينها في تعريف أنواع مستندات أخرى متعددة. على سبيل المثال، مجموعة من 'إعدادات الصفحة المشتركة'. + + مجلد + يُستخدم لتنظيم أنواع المستندات والتراكيب وأنواع العناصر المُنشأة في شجرة نوع المستند هذه. + + مجلد جديد + نوع بيانات جديد + ملف JavaScript جديد + عرض جزئي فارغ جديد + ماكرو عرض جزئي جديد + عرض جزئي جديد من مقطع + ماكرو عرض جزئي جديد من مقطع + ماكرو عرض جزئي جديد (بدون ماكرو) + ملف ورقة أنماط جديد + ملف ورقة أنماط محرر النص المنسق الجديد + + + تصفح موقعك + - إخفاء + إذا لم يفتح أومبراكو، قد تحتاج للسماح بالنوافذ المنبثقة من هذا الموقع + تم فتحه في نافذة جديدة + إعادة التشغيل + زيارة + أهلاً وسهلاً + + + البقاء + تجاهل التغييرات + لديك تغييرات غير محفوظة + هل أنت متأكد من أنك تريد الانتقال بعيداً عن هذه الصفحة؟ - لديك تغييرات غير محفوظة + + النشر سيجعل العناصر المحددة مرئية على الموقع. + إلغاء النشر سيزيل العناصر المحددة وجميع توابعها من الموقع. + + إلغاء النشر سيزيل هذه الصفحة وجميع توابعها من الموقع. + لديك تغييرات غير محفوظة. إجراء تغييرات على نوع المستند سيتجاهل التغييرات. + + + + تم + تم حذف %0% عنصر + تم حذف %0% عنصر + تم حذف %0% من %1% عنصر + تم حذف %0% من %1% عنصر + تم نشر %0% عنصر + تم نشر %0% عنصر + تم نشر %0% من %1% عنصر + تم نشر %0% من %1% عنصر + تم إلغاء نشر %0% عنصر + تم إلغاء نشر %0% عنصر + تم إلغاء نشر %0% من %1% عنصر + تم إلغاء نشر %0% من %1% عنصر + تم نقل %0% عنصر + تم نقل %0% عنصر + تم نقل %0% من %1% عنصر + تم نقل %0% من %1% عنصر + تم نسخ %0% عنصر + تم نسخ %0% عنصر + تم نسخ %0% من %1% عنصر + تم نسخ %0% من %1% عنصر + + + عنوان الرابط + رابط + مرساة / استعلام + اسم + إغلاق هذه النافذة + هل أنت متأكد أنك تريد حذف + %0% من %1% عناصر]]> + هل أنت متأكد أنك تريد تعطيل + هل أنت متأكد أنك تريد إزالة + %0%]]> + هل أنت متأكد؟ + هل أنت متأكد؟ + قص + تحرير عنصر القاموس + تحرير اللغة + تحرير الوسائط المحددة + تحرير ويب هوك + إدراج رابط محلي + إدراج حرف + إدراج عنوان رسومي + إدراج صورة + إدراج رابط + انقر لإضافة ماكرو + إدراج جدول + سيؤدي هذا إلى حذف اللغة وجميع المحتوى المرتبط بها + تغيير الثقافة للغة قد تكون عملية مكلفة وسيؤدي إلى إعادة بناء ذاكرة التخزين المؤقت للمحتوى والفهارس + آخر تحرير + رابط + رابط داخلي: + عند استخدام الروابط المحلية، أدخل "#" قبل الرابط + فتح في نافذة جديدة؟ + هذا الماكرو لا يحتوي على أي خصائص يمكنك تحريرها + لصق + تحرير الأذونات لـ + تعيين الأذونات لـ + تعيين الأذونات لـ %0% لمجموعة المستخدمين %1% + حدد مجموعات المستخدمين التي تريد تعيين الأذونات لها + يتم الآن حذف العناصر في سلة المحذوفات. يرجى عدم إغلاق هذه النافذة أثناء هذه العملية + سلة المحذوفات فارغة الآن + عند حذف العناصر من سلة المحذوفات، سيتم إزالتها إلى الأبد + + regexlib.com تواجه خدمة الويب بعض المشاكل، والتي ليس لدينا أي سيطرة عليها. نأسف لهذا الإزعاج.]]> + ابحث عن تعبير عادي لإضافة التحقق إلى حقل النموذج. مثال: 'بريد إلكتروني', 'رمز بريدي', 'رابط'. + إزالة الماكرو + حقل مطلوب + تمت إعادة فهرسة الموقع + تم تحديث ذاكرة التخزين المؤقت للموقع. جميع محتويات النشر محدثة الآن. بينما تبقى جميع المحتويات غير المنشورة غير منشورة + سيتم تحديث ذاكرة التخزين المؤقت للموقع. سيتم تحديث جميع المحتويات المنشورة، بينما تبقى المحتويات غير المنشورة غير منشورة. + عدد الأعمدة + عدد الصفوف + انقر على الصورة لرؤية الحجم الكامل + اختر عنصرًا + عرض عنصر الذاكرة المؤقتة + ربط بالأصل + تضمين العناصر الفرعية + أكثر المجتمعات ودية + رابط إلى الصفحة + يفتح المستند المرتبط في نافذة أو علامة تبويب جديدة + رابط إلى الوسائط + حدد عقدة بداية المحتوى + حدد حدثًا + حدد وسائط + حدد نوع الوسائط + حدد أيقونة + حدد عنصرًا + حدد رابطًا + حدد ماكرو + حدد محتوى + حدد نوع المحتوى + حدد عقدة بداية الوسائط + حدد عضوًا + حدد مجموعة أعضاء + حدد نوع العضو + حدد عقدة + حدد اللغات + حدد الأقسام + حدد مستخدمًا + حدد المستخدمين + لم يتم العثور على أي أيقونات + لا توجد معلمات لهذا الماكرو + لا توجد وحدات ماكرو متاحة للإدراج + موفرو تسجيل الدخول الخارجيين + تفاصيل الاستثناء + تتبع المكدس + استثناء داخلي + ربط + إلغاء ربط + الحساب + حدد المحرر + حدد مقتطف + سيؤدي هذا إلى حذف العقدة وجميع لغاتها. إذا كنت تريد حذف لغة واحدة فقط، فيجب عليك إلغاء نشر العقدة بتلك اللغة بدلاً من ذلك. + %0%.]]> + %0% من مجموعة %1%]]> + نعم، إزالة + أنت تحذف التخطيط + سيؤدي تعديل التخطيط إلى فقدان البيانات لأي محتوى موجود يعتمد على هذا التكوين. + حدد التكوين + + + + لاستيراد عنصر قاموس، ابحث عن ملف ".udt" على جهازك عن طريق النقر على زر "استيراد" (سيتم طلب التأكيد في الشاشة التالية) + + عنصر القاموس غير موجود. + العنصر الأب غير موجود. + لا توجد عناصر قاموس. + لا توجد عناصر قاموس في هذا الملف. + لم يتم العثور على أي عناصر قاموس. + إنشاء عنصر قاموس + + + %0%' أدناه + ]]> + اسم الثقافة + + نظرة عامة على القاموس + + + باحثون مكونون + يعرض خصائص وأدوات لأي باحث مكون (مثل باحث متعدد الفهارس) + قيم الحقل + حالة الصحة + حالة صحة الفهرس وما إذا كان يمكن قراءته + الفهارس + معلومات الفهرس + المحتوى في الفهرس + يسرد خصائص الفهرس + إدارة فهارس Examine + يسمح لك بمشاهدة تفاصيل كل فهرس ويوفر بعض الأدوات لإدارة الفهارس + إعادة بناء الفهرس + + اعتمادًا على كمية المحتوى في موقعك، قد يستغرق هذا بعض الوقت.
+ لا يوصى بإعادة بناء الفهرس خلال أوقات حركة مرور عالية على الموقع أو عندما يقوم المحررون بتحرير المحتوى. + ]]> +
+ الباحثون + ابحث في الفهرس وعرض النتائج + أدوات + أدوات لإدارة الفهرس + الحقول + لا يمكن قراءة الفهرس وسيحتاج إلى إعادة بناء + تستغرق العملية وقتًا أطول من المتوقع، تحقق من سجل Umbraco لمعرفة ما إذا كانت هناك أي أخطاء خلال هذه العملية + لا يمكن إعادة بناء هذا الفهرس لأنه لا يحتوي على + IIndexPopulator + لم يتم العثور على نتائج + عرض %0% - %1% من %2% نتيجة - الصفحة %3% من %4% + + + أدخل اسم المستخدم + أدخل كلمة المرور + تأكيد كلمة المرور + سمّ %0%... + أدخل اسمًا... + أدخل بريدًا إلكترونيًا... + أدخل اسم مستخدم... + تسمية... + أدخل وصفًا... + اكتب للبحث... + اكتب للتصفية... + اكتب لإضافة علامات (اضغط إدخال بعد كل علامة)... + أدخل بريدك الإلكتروني + أدخل رسالة... + اسم المستخدم الخاص بك هو عادةً بريدك الإلكتروني + #قيمة أو ?مفتاح=قيمة + أدخل اسمًا مستعارًا... + جارٍ إنشاء اسم مستعار... + إنشاء عنصر + تحرير + الاسم + + + إنشاء عرض قائمة مخصص + إزالة عرض قائمة مخصص + نوع محتوى أو نوع وسائط أو نوع عضو بهذا الاسم المستعار موجود بالفعل + + + تمت إعادة التسمية + أدخل اسم مجلد جديد هنا + تمت إعادة تسمية %0% إلى %1% + + + إضافة قيمة مسبقة + نوع بيانات قاعدة البيانات + معرف محرر الخصائص + محرر الخصائص + أزرار + تمكين الإعدادات المتقدمة لـ + تمكين قائمة السياق + الحد الأقصى للحجم الافتراضي للصور المدرجة + أوراق الأنماط ذات الصلة + إظهار التسمية + العرض والارتفاع + حدد المجلد لنقله + إلى في بنية الشجرة أدناه + تم نقله تحت + تعطيل تغيير محرر الخصائص على نوع بيانات بقيم مخزنة. للسماح بذلك يمكنك تغيير إعداد Umbraco:CMS:DataTypes:CanBeChanged في appsettings.json. + %0% سيحذف الخصائص وبياناتها من العناصر التالية]]> + أنا أفهم أن هذا الإجراء سيحذف الخصائص والبيانات المستندة إلى نوع البيانات هذا + + + تم حفظ بياناتك، ولكن قبل أن تتمكن من نشر هذه الصفحة، هناك بعض الأخطاء التي تحتاج إلى إصلاحها أولاً: + موفر العضوية الحالي لا يدعم تغيير كلمة المرور (يجب أن يكون EnablePasswordRetrieval صحيحًا) + %0% موجود بالفعل + كانت هناك أخطاء: + كانت هناك أخطاء: + يجب أن تكون كلمة المرور بطول %0% أحرف على الأقل وتحتوي على %1% حرف غير أبجدي رقمي على الأقل + %0% يجب أن يكون عددًا صحيحًا + حقل %0% في علامة التبويب %1% إلزامي + %0% حقل إلزامي + %0% في %1% ليس بتنسيق صحيح + %0% ليس بتنسيق صحيح + + + تم استلام خطأ من الخادم + تم منع نوع الملف المحدد من قبل المسؤول + ملاحظة! على الرغم من تمكين CodeMirror عن طريق التكوين، إلا أنه معطل في Internet Explorer لأنه غير مستقر بما فيه الكفاية. + يرجى ملء كل من الاسم المستعار والاسم على نوع الخاصية الجديد! + هناك مشكلة في حق الوصول للقراءة/الكتابة لملف أو مجلد معين + خطأ في تحميل نص Partial View (ملف: %0%) + الرجاء إدخال عنوان + الرجاء اختيار نوع + أنت على وشك جعل الصورة أكبر من الحجم الأصلي. هل أنت متأكد أنك تريد المتابعة؟ + تم حذف عقدة البداية، يرجى الاتصال بمسؤولك + يرجى تحديد المحتوى قبل تغيير النمط + لا توجد أنماط نشطة متاحة + يرجى وضع المؤشر على يسار الخليتين اللتين ترغب في دمجهما + لا يمكنك تقسيم خلية لم يتم دمجها. + هذه الخاصية غير صالحة + حدث فشل غير معروف + فشل التزامن التفاؤلي، تم تعديل الكائن + + + حول + إجراء + إجراءات + إضافة + اسم مستعار + الكل + هل أنت متأكد؟ + رجوع + العودة إلى النظرة العامة + حدود + بواسطة + إلغاء + هوامش الخلية + اختر + مسح + إغلاق + إغلاق النافذة + إغلاق اللوحة + تعليق + تأكيد + تقيد + تقيد النسب + محتوى + متابعة + نسخ + إنشاء + قاعدة البيانات + تاريخ + افتراضي + حذف + تم الحذف + جارٍ الحذف... + تصميم + قاموس + أبعاد + تجاهل + أسفل + تنزيل + تحرير + تم التحرير + عناصر + بريد إلكتروني + خطأ + حقل + بحث + أولاً + نقطة محورية + عام + مجموعات + مجموعة + ارتفاع + مساعدة + إخفاء + تاريخ + أيقونة + معرف + استيراد + البحث في هذا المجلد فقط + معلومات + هوامش داخلية + إدراج + تثبيت + غير صالح + محاذاة + تسمية + لغة + آخر + تخطيط + روابط + جارٍ التحميل... + مقفل + تسجيل الدخول + تسجيل الخروج + تسجيل الخروج + ماكرو + إلزامي + رسالة + نقل + اسم + جديد + التالي + لا + اسم العقدة + من + إيقاف + موافق + فتح + خيارات + تشغيل + أو + ترتيب حسب + كلمة المرور + مسار + لحظة من فضلك... + السابق + خصائص + قراءة المزيد + إعادة بناء + البريد الإلكتروني لاستلام بيانات النموذج + سلة المحذوفات + سلة المحذوفات فارغة + إعادة تحميل + متبقي + إزالة + إعادة تسمية + تجديد + مطلوب + استرجاع + إعادة المحاولة + أذونات + النشر المجدول + معلومات Umbraco + بحث + عذرًا، لا يمكننا العثور على ما تبحث عنه. + لم تتم إضافة أي عناصر + خادم + إعدادات + مشترك + إظهار + إظهار الصفحة عند الإرسال + حجم + فرز + حالة + إرسال + نجاح + نوع + اسم النوع + اكتب للبحث... + تحت + أعلى + تحديث + ترقية + رفع + رابط + مستخدم + اسم المستخدم + قيمة + عرض + مرحبًا... + عرض + نعم + مجلد + نتائج البحث + إعادة ترتيب + لقد انتهيت من إعادة الترتيب + معاينة + تغيير كلمة المرور + إلى + عرض القائمة + جارٍ الحفظ... + حالي + تضمين + محدد + آخر + مقالات + فيديوهات + صورة رمزية لـ + رأس + حقل نظام + آخر تحديث + انتقل إلى القائمة + انتقل إلى المحتوى + أساسي + تغيير + قسم القص + عام + وسائط + تراجع + تحقق + + + أزرق + + + إضافة مجموعة + إضافة خاصية + إضافة محرر + إضافة قالب + إضافة عقدة فرعية + إضافة فرعي + تحرير نوع البيانات + التنقل بين الأقسام + اختصارات + إظهار الاختصارات + تبديل عرض القائمة + تبديل السماح كجذر + تعليق/إلغاء تعليق الأسطر + إزالة سطر + نسخ الأسطر لأعلى + نسخ الأسطر لأسفل + نقل الأسطر لأعلى + نقل الأسطر لأسفل + عام + محرر + تبديل السماح بمتغيرات الثقافة + إضافة علامة تبويب + + + لون الخلفية + عريض + لون النص + خط + نص + + + صفحة + + + لا يمكن للمثبت الاتصال بقاعدة البيانات. + تعذر حفظ ملف web.config. يرجى تعديل سلسلة الاتصال يدويًا. + تم العثور على قاعدة البيانات الخاصة بك وهي معرفة كـ + تكوين قاعدة البيانات + + تثبيت لتثبيت قاعدة بيانات Umbraco %0% + ]]> + + + التالي للمتابعة.]]> + لم يتم العثور على قاعدة البيانات! يرجى التحقق من أن المعلومات في "سلسلة الاتصال" لملف "web.config" صحيحة.

+

للمتابعة، يرجى تحرير ملف "web.config" (باستخدام Visual Studio أو محرر النصوص المفضل لديك)، انتقل إلى الأسفل، أضف سلسلة الاتصال لقاعدة البيانات الخاصة بك في المفتاح المسمى "UmbracoDbDSN" واحفظ الملف.

+

+ انقر على زر إعادة المحاولة عند + الانتهاء.
+ المزيد من المعلومات حول تحرير web.config هنا.

]]>
+ + يرجى الاتصال بمزود خدمة الإنترنت الخاص بك إذا لزم الأمر. + إذا كنت تقوم بالتثبيت على جهاز محلي أو خادم، فقد تحتاج إلى معلومات من مسؤول النظام الخاص بك.]]> + + اضغط على زر ترقية لترقية قاعدة البيانات الخاصة بك إلى Umbraco %0%

+

+ لا تقلق - لن يتم حذف أي محتوى وسيستمر كل شيء في العمل بعد ذلك! +

+ ]]>
+ اضغط التالي للمتابعة. ]]> + + التالي لمتابعة معالج التكوين]]> + + يجب تغيير كلمة مرور المستخدم الافتراضي!]]> + + تم تعطيل المستخدم الافتراضي أو ليس لديه حق الوصول إلى Umbraco!

لا يلزم اتخاذ أي إجراءات أخرى. انقر التالي للمتابعة.]]> + + تم تغيير كلمة مرور المستخدم الافتراضي بنجاح منذ التثبيت!

لا يلزم اتخاذ أي إجراءات أخرى. انقر التالي للمتابعة.]]> + تم تغيير كلمة المرور! + احصل على بداية رائعة، شاهد مقاطع الفيديو التقديمية الخاصة بنا + بالنقر على الزر التالي (أو تعديل umbracoConfigurationStatus في web.config)، فإنك تقبل ترخيص هذا البرنامج كما هو محدد في المربع أدناه. لاحظ أن توزيعة Umbraco هذه تتكون من ترخيصين مختلفين، ترخيص MIT مفتوح المصدر للإطار وترخيص Umbraco المجاني الذي يغطي واجهة المستخدم. + غير مثبت بعد. +الملفات والمجلدات المتأثرة +مزيد من المعلومات حول إعداد أذونات Umbraco هنا +تحتاج إلى منح ASP.NET أذونات التعديل للملفات/المجلدات التالية +إعدادات الأذونات الخاصة بك مثالية تقريبًا!

+يمكنك تشغيل Umbraco دون مشاكل، ولكن لن تتمكن من تثبيت الحزم الموصى بها للاستفادة الكاملة من Umbraco.]]>
+كيفية الحل +انقر هنا لقراءة النسخة النصية +الفيديو التعليمي الخاص بنا حول إعداد أذونات المجلدات لـ Umbraco أو اقرأ النسخة النصية.]]> +إعدادات الأذونات الخاصة بك قد تكون مشكلة! +

+يمكنك تشغيل Umbraco دون مشاكل، ولكن لن تتمكن من إنشاء مجلدات أو تثبيت الحزم الموصى بها للاستفادة الكاملة من Umbraco.]]>
+إعدادات الأذونات الخاصة بك غير جاهزة لـ Umbraco! +

+من أجل تشغيل Umbraco، ستحتاج إلى تحديث إعدادات الأذونات الخاصة بك.]]>
+إعدادات الأذونات الخاصة بك مثالية!

+أنت جاهز لتشغيل Umbraco وتثبيت الحزم!]]>
+حل مشكلة المجلد +اتبع هذا الرابط لمزيد من المعلومات حول مشاكل ASP.NET وإنشاء المجلدات +إعداد أذونات المجلدات + +أريد البدء من الصفر +تعلم كيف) +لا يزال بإمكانك اختيار تثبيت Runway لاحقًا. يرجى الذهاب إلى قسم المطور واختيار الحزم. +]]> +لقد قمت للتو بإعداد منصة Umbraco نظيفة. ماذا تريد أن تفعل بعد ذلك؟ +تم تثبيت Runway + +هذه قائمتنا الموصى بها من الوحدات، حدد تلك التي ترغب في تثبيتها، أو عرض القائمة الكاملة للوحدات +]]> +موصى به فقط للمستخدمين ذوي الخبرة +أريد البدء بموقع ويب بسيط + +"Runway" هو موقع ويب بسيط يوفر بعض أنواع المستندات الأساسية والقوالب. يمكن للمثبت إعداد Runway لك تلقائيًا، +ولكن يمكنك بسهولة تحريره أو توسيعه أو إزالته. ليس ضروريًا ويمكنك استخدام Umbraco بدونه بشكل مثالي. ومع ذلك، +يقدم Runway أساسًا سهلاً مبنيًا على أفضل الممارسات لبدء أسرع من أي وقت مضى. +إذا اخترت تثبيت Runway، يمكنك اختياريًا تحديد لبنات البناء الأساسية المسماة وحدات Runway لتعزيز صفحات Runway الخاصة بك. +

+ +المضمن مع Runway: الصفحة الرئيسية، صفحة البدء، صفحة تثبيت الوحدات.
+الوحدات الاختيارية: التنقل العلوي، خريطة الموقع، الاتصال، المعرض. +
+]]>
+ما هو Runway +الخطوة 1/5 قبول الترخيص +الخطوة 2/5: تكوين قاعدة البيانات +الخطوة 3/5: التحقق من أذونات الملفات +الخطوة 4/5: التحقق من أمان Umbraco +الخطوة 5/5: Umbraco جاهز لبدء العمل +شكرًا لك على اختيار Umbraco +تصفح موقعك الجديد +لقد قمت بتثبيت Runway، فلماذا لا ترى كيف يبدو موقعك الجديد.]]> +مزيد من المساعدة والمعلومات +احصل على المساعدة من مجتمعنا الحائز على جوائز، أو تصفح الوثائق أو شاهد بعض مقاطع الفيديو المجانية حول كيفية إنشاء موقع بسيط، وكيفية استخدام الحزم ودليل سريع لمصطلحات Umbraco]]> +تم تثبيت Umbraco %0% وهو جاهز للاستخدام +/web.config يدويًا وتحديث مفتاح AppSetting UmbracoConfigurationStatus في الأسفل إلى قيمة '%0%'.]]> +البدء فورًا بالنقر على زر "تشغيل Umbraco" أدناه.
إذا كنت جديدًا على Umbraco، +يمكنك العثور على الكثير من الموارد في صفحات البداية الخاصة بنا.]]>
+تشغيل Umbraco +لإدارة موقعك، ما عليك سوى فتح واجهة Umbraco الخلفية والبدء في إضافة المحتوى أو تحديث القوالب أو أوراق الأنماط أو إضافة وظائف جديدة]]> +فشل الاتصال بقاعدة البيانات. +Umbraco الإصدار 3 +Umbraco الإصدار 4 +شاهد +Umbraco %0% لتثبيت جديد أو الترقية من الإصدار 3.0. +

+اضغط "التالي" لبدء المعالج.]]>
+ + +رمز الثقافة +اسم الثقافة + + +لقد كنت خاملاً وسيتم تسجيل الخروج تلقائيًا خلال +جدد الآن لحفظ عملك + + +مرحبًا +مرحبًا +مرحبًا +مرحبًا +مرحبًا +مرحبًا +مرحبًا +تسجيل الدخول أدناه +تسجيل الدخول باستخدام +انتهت الجلسة +عذرًا! لم نتمكن من تسجيل دخولك. يرجى التحقق من بيانات الاعتماد الخاصة بك والمحاولة مرة أخرى. +© 2001 - %0%
Umbraco.com

]]>
+نسيت كلمة المرور؟ +سيتم إرسال بريد إلكتروني إلى العنوان المحدد يحتوي على رابط لإعادة تعيين كلمة المرور الخاصة بك +سيتم إرسال بريد إلكتروني مع تعليمات إعادة تعيين كلمة المرور إلى العنوان المحدد إذا كان مطابقًا لسجلاتنا +إظهار كلمة المرور +إخفاء كلمة المرور +العودة إلى نموذج تسجيل الدخول +يرجى تقديم كلمة مرور جديدة +تم تحديث كلمة المرور الخاصة بك +الرابط الذي نقرت عليه غير صالح أو انتهت صلاحيته +Umbraco: إعادة تعيين كلمة المرور + + + + + + + + + + + +
+ + + + + +
+ +
+ +
+
+ + + + + + +
+
+
+ + + + +
+ + + + +
+

+طلب إعادة تعيين كلمة المرور +

+

+اسم المستخدم الخاص بك لتسجيل الدخول إلى واجهة Umbraco الخلفية هو: %0% +

+

+ + + + + + +
+ +انقر على هذا الرابط لإعادة تعيين كلمة المرور الخاصة بك + +
+

+

إذا لم تتمكن من النقر على الرابط، فانسخ هذا الرابط والصقه في نافذة المتصفح الخاصة بك:

+ + + + +
+ +%1% + +
+

+
+
+


+
+
+ + +]]>
+Umbraco: رمز الأمان +رمز الأمان الخاص بك هو: %0% +خطوة أخيرة +لقد قمت بتمكين المصادقة الثنائية ويجب عليك التحقق من هويتك. +الرجاء اختيار مزود المصادقة الثنائية +رمز التحقق +الرجاء إدخال رمز التحقق +تم إدخال رمز غير صالح + + +لوحة التحكم +الأقسام +المحتوى + + +اختر الصفحة أعلاه... +تم نسخ %0% إلى %1% +حدد أدناه المكان الذي يجب نسخ المستند %0% إليه +تم نقل %0% إلى %1% +حدد أدناه المكان الذي يجب نقل المستند %0% إليه +تم تحديده كجذر لمحتواك الجديد، انقر على 'موافق' أدناه. +لم يتم تحديد أي عقدة بعد، يرجى تحديد عقدة في القائمة أعلاه قبل النقر على 'موافق' +العقدة الحالية غير مسموح بها تحت العقدة المختارة بسبب نوعها +لا يمكن نقل العقدة الحالية إلى إحدى صفحاتها الفرعية ولا يمكن أن يكون الأصل والوجهة متماثلين +لا يمكن أن توجد العقدة الحالية في الجذر +الإجراء غير مسموح به لأن لديك أذونات غير كافية على مستند أو أكثر من المستندات الفرعية. +ربط العناصر المنسوخة بالأصل + + +%0%]]> +تم حفظ إعدادات الإشعارات لـ + +تم تعديل اللغات التالية %0% + + + + + + + + + + + +
+ + + + + +
+ +
+ +
+
+ + + + + + +
+
+
+ + + + +
+ + + + +
+

+مرحبًا %0%، +

+

+هذا بريد آلي لإعلامك بأن المهمة '%1%' تم تنفيذها على الصفحة '%2%' بواسطة المستخدم '%3%' +

+ + + + + + +
+ +
+تحرير
+
+

+

ملخص التحديث:

+%6% +

+

+أتمنى لك يومًا سعيدًا!

+تحياتي من روبوت Umbraco +

+
+
+


+
+
+ + +]]>
+تم تعديل اللغات التالية:

+%0% +]]>
+[%0%] إشعار حول %1% تم تنفيذه على %2% +الإشعارات + + +الإجراءات +تم الإنشاء +إنشاء حزمة + +وتحديد موقع الحزمة. عادةً ما تحتوي حزم Umbraco على امتداد ".umb" أو ".zip". +]]> +سيؤدي هذا إلى حذف الحزمة +تضمين جميع العقد الفرعية +تم التثبيت +الحزم المثبتة +تعليمات التثبيت +هذه الحزمة لا تحتوي على عرض تكوين +لم يتم إنشاء أي حزم بعد +لم يتم تثبيت أي حزم +'الحزم' في أعلى يمين شاشتك]]> +محتوى الحزمة +الترخيص +البحث عن الحزم +نتائج البحث عن +لم نتمكن من العثور على أي شيء لـ +يرجى محاولة البحث عن حزمة أخرى أو التصفح عبر الفئات +شائعة +مميزة +إصدارات جديدة +لديه +نقاط كارما +معلومات +المالك +المساهمون +تم الإنشاء +الإصدار الحالي +إصدار .NET +التحميلات +الإعجابات +التوافق +هذه الحزمة متوافقة مع إصدارات Umbraco التالية، كما أبلغ أعضاء المجتمع. لا يمكن ضمان التوافق الكامل للإصدارات المبلغ عنها أقل من 100% +مصادر خارجية +المؤلف +الوثائق +بيانات وصفية للحزمة +اسم الحزمة +لا تحتوي الحزمة على أي عناصر +
+يمكنك إزالته بأمان من النظام بالنقر على "إلغاء تثبيت الحزمة" أدناه.]]>
+خيارات الحزمة +تشغيل عمليات ترحيل الحزم المعلقة +ملف README للحزمة +مستودع الحزمة +تأكيد إلغاء تثبيت الحزمة +تم إلغاء تثبيت الحزمة +تم إلغاء تثبيت الحزمة بنجاح +إلغاء تثبيت الحزمة + +ملاحظة: أي مستندات أو وسائط وما إلى ذلك تعتمد على العناصر التي تقوم بإزالتها، ستتوقف عن العمل، وقد تؤدي إلى عدم استقرار النظام، +لذا قم بإلغاء التثبيت بحذر. في حالة الشك، اتصل بمؤلف الحزمة.]]> +إصدار الحزمة +تم التحقق من العمل على Umbraco Cloud +تم الانتهاء من عمليات ترحيل الحزمة بنجاح. +تم الانتهاء من جميع عمليات ترحيل الحزمة بنجاح. + + +لصق مع التنسيق الكامل (غير موصى به) +النص الذي تحاول لصقه يحتوي على أحرف خاصة أو تنسيق. قد يكون هذا بسبب نسخ نص من Microsoft Word. يمكن لـ Umbraco إزالة الأحرف الخاصة أو التنسيق تلقائيًا، بحيث يكون المحتوى الملصق أكثر ملاءمة للويب. +لصق كنص خام بدون أي تنسيق على الإطلاق +لصق، ولكن إزالة التنسيق (موصى به) + + +الحماية القائمة على المجموعات +إذا كنت تريد منح الوصول لجميع أعضاء مجموعات أعضاء محددة +تحتاج إلى إنشاء مجموعة أعضاء قبل أن تتمكن من استخدام المصادقة القائمة على المجموعات +صفحة الخطأ +تستخدم عندما يكون الأشخاص مسجلين دخولاً، ولكن ليس لديهم حق الوصول +%0%]]> +%0% محمي الآن]]> +%0%]]> +صفحة تسجيل الدخول +اختر الصفحة التي تحتوي على نموذج تسجيل الدخول +إزالة الحماية... +%0%?]]> +حدد الصفحات التي تحتوي على نموذج تسجيل الدخول ورسائل الخطأ +%0%]]> +%0%]]> +حماية أعضاء محددين +إذا كنت ترغب في منح الوصول لأعضاء محددين + + + + + + + + +تضمين الصفحات الفرعية غير المنشورة +جاري النشر - يرجى الانتظار... +تم نشر %0% من أصل %1% صفحات... +تم نشر %0% +تم نشر %0% والصفحات الفرعية +نشر %0% وجميع صفحاته الفرعية +نشر لنشر %0% وبالتالي جعل محتواه متاحًا للجمهور.

+يمكنك نشر هذه الصفحة وجميع صفحاتها الفرعية عن طريق تحديد تضمين الصفحات الفرعية غير المنشورة أدناه. +]]>
+أذونات مستخدم غير كافية لنشر جميع المستندات الفرعية +لا يمكن نشر %0% لأن العنصر في سلة المحذوفات. +فشل التحقق للغة المطلوبة '%0%'. تم حفظ هذه اللغة ولكن لم يتم نشرها. + + +لم تقم بتكوين أي ألوان معتمدة + + +يمكنك فقط تحديد عناصر من نوع: %0% +لقد حددت عنصر محتوى محذوف حاليًا أو في سلة المحذوفات +لقد حددت عناصر محتوى محذوفة حاليًا أو في سلة المحذوفات +تحديد الجذر +اختر عقدة الجذر +تحديد عبر XPath +تحديد جذر ديناميكي + + +استعلام الجذر الديناميكي +اختر الأصل +حدد الأصل لاستعلام الجذر الديناميكي الخاص بك +الجذر +عقدة الجذر لجلسة التحرير هذه +الأصل +العقدة الأصلية للمصدر في جلسة التحرير هذه +الحالي +عقدة المحتوى التي هي مصدر جلسة التحرير هذه +الموقع +ابحث عن أقرب عقدة باسم مضيف +عقدة محددة +اختر عقدة محددة كأصل لهذا الاستعلام +إضافة خطوة إلى الاستعلام +حدد الخطوة التالية لاستعلام الجذر الديناميكي الخاص بك +أقرب سلف أو نفس العقدة +استعلام أقرب سلف أو نفس العقدة التي تناسب أحد الأنواع المكونة +أبعد سلف أو نفس العقدة +استعلام أبعد سلف أو نفس العقدة التي تناسب أحد الأنواع المكونة +أقرب فرع أو نفس العقدة +استعلام أقرب فرع أو نفس العقدة التي تناسب أحد الأنواع المكونة +أبعد فرع أو نفس العقدة +استعلام أبعد فرع أو نفس العقدة التي تناسب أحد الأنواع المكونة +مخصص +استعلام باستخدام خطوة استعلام مخصصة +إضافة خطوة استعلام +التي تطابق أنواع: +لا يوجد محتوى مطابق +تكوين هذه الخاصية لا يتطابق مع أي محتوى. قم بإنشاء المحتوى المفقود أو اتصل بمسؤولك لضبط إعدادات الجذر الديناميكي لهذه الخاصية. +إلغاء ومسح الاستعلام + + + عنصر محذوف + لقد حددت عنصر وسائط محذوف حاليًا أو في سلة المحذوفات + لقد حددت عناصر وسائط محذوفة حاليًا أو في سلة المحذوفات + في سلة المحذوفات + فتح في مكتبة الوسائط + تغيير عنصر الوسائط + تحرير %0% على %1% + إلغاء الإنشاء؟ + + لقد أجريت تغييرات على هذا المحتوى. هل أنت متأكد أنك تريد تجاهلها؟ + إزالة جميع الوسائط؟ + الحافظة + غير مسموح + فتح منتقي الوسائط + + + + إدخال رابط خارجي + اختيار صفحة داخلية + عنوان + رابط + فتح في نافذة جديدة + أدخل عنوان العرض + أدخل الرابط + + + + إعادة تعيين القص + تم + تراجع عن التعديلات + مخصص من قبل المستخدم + + + + التغييرات + تم الإنشاء + الإصدار الحالي + النص الأحمر سيتم إزالته في الإصدار المحدد، النص الأخضر سيتم إضافته]]> + لا توجد اختلافات بين الإصدار الحالي (المسودة) والإصدار المحدد + تم استعادة المستند + حدد إصدارًا للمقارنة مع الإصدار الحالي + يعرض الإصدار المحدد كـ HTML، إذا كنت ترغب في رؤية الفرق بين إصدارين في نفس الوقت، استخدم عرض المقارنة + الرجوع إلى + اختر الإصدار + عرض + + الإصدارات + إصدار المسودة الحالي + الإصدار المنشور الحالي + + + + تحرير ملف السكريبت + + + + المحتوى + النماذج + الوسائط + الأعضاء + الحزم + السوق + الإعدادات + الترجمة + المستخدمون + + + + الجولات + أفضل دروس الفيديو لـ Umbraco + زيارة our.umbraco.com + زيارة umbraco.tv + شاهد دروس الفيديو المجانية + على قاعدة تعلم Umbraco + + + + القالب الافتراضي + لاستيراد نوع مستند، ابحث عن ملف ".udt" على جهازك بالنقر على زر "استيراد" (سيتم طلب التأكيد في الشاشة التالية) + عنوان علامة تبويب جديدة + نوع العقدة + النوع + ورقة الأنماط + سكريبت + علامة تبويب + عنوان علامة التبويب + علامات التبويب + تم تمكين نوع المحتوى الرئيسي + يستخدم نوع المحتوى هذا + لا توجد خصائص محددة في هذه علامة التبويب. انقر على رابط "إضافة خاصية جديدة" في الأعلى لإنشاء خاصية جديدة. + إنشاء قالب مطابق + إضافة أيقونة + + + + ترتيب الفرز + تاريخ الإنشاء + تم الفرز. + اسحب العناصر المختلفة لأعلى أو لأسفل أدناه لتعيين كيفية ترتيبها. أو انقر على عناوين الأعمدة لفرز مجموعة العناصر بأكملها + + لا توجد عقد فرعية لفرزها في هذه العقدة + + + + التحقق + يجب إصلاح أخطاء التحقق قبل حفظ العنصر + فشل + تم الحفظ + أذونات مستخدم غير كافية، تعذر إكمال العملية + تم الإلغاء + تم إلغاء العملية بواسطة إضافة طرف ثالث + يتم تحميل هذا الملف كجزء من مجلد، ولكن إنشاء مجلد جديد غير مسموح به هنا + إنشاء مجلد جديد غير مسموح به هنا + نوع الخاصية موجود بالفعل + تم إنشاء نوع الخاصية + نوع البيانات: %1%]]> + تم حذف نوع الخاصية + تم حفظ نوع المستند + تم إنشاء علامة تبويب + تم حذف علامة التبويب + تم حذف علامة التبويب بالمعرف: %0% + لم يتم حفظ ورقة الأنماط + تم حفظ ورقة الأنماط + تم حفظ ورقة الأنماط بدون أخطاء + تم حفظ نوع البيانات + تم حفظ عنصر القاموس + تم نشر المحتوى + ومرئي على الموقع + تم حفظ قالب المحتوى + تم حفظ التغييرات بنجاح + تم حفظ المحتوى + تذكر النشر لجعل التغييرات مرئية + تم الإرسال للموافقة + تم إرسال التغييرات للموافقة + تم حفظ الوسائط + تم حفظ الوسائط بدون أخطاء + تم حفظ العضو + تم حفظ خاصية ورقة الأنماط + تم حفظ ورقة الأنماط + تم حفظ القالب + خطأ في حفظ المستخدم (تحقق من السجل) + تم حفظ المستخدم + تم حفظ نوع المستخدم + تم حفظ مجموعة المستخدمين + تم حفظ الثقافات وأسماء المضيفين + خطأ في حفظ الثقافات وأسماء المضيفين + لم يتم حفظ الملف + تعذر حفظ الملف. يرجى التحقق من أذونات الملف + تم حفظ الملف + تم حفظ الملف بدون أخطاء + تم حفظ اللغة + تم حفظ نوع الوسائط + تم حفظ نوع العضو + تم حفظ مجموعة الأعضاء + يوجد بالفعل مجموعة أعضاء أخرى بنفس الاسم + لم يتم حفظ القالب + يرجى التأكد من عدم وجود قالبين بنفس الاسم المستعار + تم حفظ القالب + تم حفظ القالب بدون أخطاء! + تم إلغاء نشر المحتوى + تم حفظ العرض الجزئي + تم حفظ العرض الجزئي بدون أخطاء! + لم يتم حفظ العرض الجزئي + حدث خطأ أثناء حفظ الملف. + تم حفظ الأذونات لـ + تم حذف %0% مجموعات مستخدمين + تم حذف %0% + تم تمكين %0% مستخدمين + تم تعطيل %0% مستخدمين + تم تمكين %0% + تم تعطيل %0% + تم تعيين مجموعات المستخدمين + تم فتح %0% مستخدمين + تم فتح %0% + تم تصدير العضو إلى ملف + حدث خطأ أثناء تصدير العضو + تم حذف المستخدم %0% + دعوة مستخدم + تم إعادة إرسال الدعوة إلى %0% + تم تصدير نوع المستند إلى ملف + حدث خطأ أثناء تصدير نوع المستند + تم تصدير عنصر/عناصر القاموس إلى ملف + حدث خطأ أثناء تصدير عنصر/عناصر القاموس + تم استيراد عنصر/عناصر القاموس التالية! + لم يتم تكوين نطاقات لموقع متعدد اللغات، يرجى الاتصال بالمسؤول، راجع السجل لمزيد من المعلومات + لا يوجد نطاق مكون لـ %0%، يرجى الاتصال بالمسؤول، راجع السجل لمزيد من المعلومات + لا يحتوي المستند على عنوان URL، ربما بسبب تعارض في التسمية مع مستند آخر. يمكن العثور على مزيد من التفاصيل تحت قسم المعلومات. + تم نسخ معلومات النظام بنجاح إلى الحافظة + تعذر نسخ معلومات النظام إلى الحافظة + تم حفظ Webhook + تم الحفظ. لعرض التغييرات يرجى إعادة تحميل المتصفح + تم نشر %0% مستندات وظهرت على الموقع + تم نشر %0% وظهر على الموقع + تم نشر %0% مستندات للغات %1% وظهرت على الموقع + تم تحديث جدول النشر + تم حفظ %0% + تم إرسال تغييرات %0% للموافقة + تم إلغاء نشر متغير المحتوى %0% + تم إلغاء نشر اللغة الإلزامية '%0%'. جميع لغات عنصر المحتوى هذا غير منشورة الآن. + لا يمكن نشر المستند لأن اللغة المطلوبة '%0%' غير منشورة + فشل التحقق للغة '%0%' + لا يمكن أن يكون تاريخ الإصدار في الماضي + لا يمكن جدولة المستند للنشر لأن اللغة المطلوبة '%0%' غير منشورة + لا يمكن جدولة المستند للنشر لأن اللغة المطلوبة '%0%' لها تاريخ نشر لاحق للغة غير إلزامية + لا يمكن أن يكون تاريخ الانتهاء في الماضي + لا يمكن أن يكون تاريخ الانتهاء قبل تاريخ الإصدار + حدث خطأ أثناء تمكين تنظيف الإصدارات لـ %0% + حدث خطأ أثناء تعطيل تنظيف الإصدارات لـ %0% + + + + إضافة نمط + تحرير النمط + أنماط محرر النصوص المنسق + حدد الأنماط التي يجب أن تكون متاحة في محرر النصوص المنسق لورقة الأنماط هذه + تحرير ورقة الأنماط + تحرير خاصية ورقة الأنماط + الاسم المعروض في محدد نمط المحرر + معاينة + كيف سيبدو النص في محرر النصوص المنسق. + محدد + يستخدم صيغة CSS، مثل "h1" أو ".redHeader" + الأنماط + CSS الذي يجب تطبيقه في محرر النصوص المنسق، مثل "color:red;" + الكود + المحرر + + + + Production.]]> + فشل حذف القالب بالمعرف %0% + تحرير القالب + أقسام + إدراج منطقة محتوى + إدراج عنصر نائب لمنطقة المحتوى + إدراج + اختر ما تريد إدراجه في قالبك + عنصر قاموس + عنصر القاموس هو عنصر نائب لنص قابل للترجمة، مما يسهل إنشاء تصاميم لمواقع متعددة اللغات. + ماكرو + الماكرو هو مكون قابل للتكوين رائع للأجزاء القابلة لإعادة الاستخدام من تصميمك، حيث تحتاج إلى خيار لتوفير معلمات، مثل المعارض والنماذج والقوائم. + قيمة + يعرض قيمة حقل مسمى من الصفحة الحالية، مع خيارات لتعديل القيمة أو الرجوع إلى قيم بديلة. + عرض جزئي + العرض الجزئي هو ملف قالب منفصل يمكن تقديمه داخل قالب آخر، وهو رائع لإعادة استخدام ترميز HTML أو لفصل القوالب المعقدة إلى ملفات منفصلة. + القالب الرئيسي + بدون رئيسي + تقديم القالب الفرعي + @RenderBody().]]> + تحديد قسم مسمى + @section { ... }. يمكن تقديم هذا في منطقة محددة من القالب الأصلي لهذا القالب، باستخدام @RenderSection.]]> + تقديم قسم مسمى + @RenderSection(name). يعرض هذا منطقة من القالب الفرعي ملفوفة في تعريف @section [name]{ ... } المقابل.]]> + اسم القسم + القسم إلزامي + @section، وإلا سيظهر خطأ.]]> + منشئ الاستعلام + عناصر تم إرجاعها، في + أريد + كل المحتوى + محتوى من نوع "%0%" + من + موقعي + حيث + و + هو + ليس + قبل + قبل (بما في ذلك التاريخ المحدد) + بعد + بعد (بما في ذلك التاريخ المحدد) + يساوي + لا يساوي + يحتوي على + لا يحتوي على + أكبر من + أكبر من أو يساوي + أقل من + أقل من أو يساوي + المعرف + الاسم + تاريخ الإنشاء + تاريخ آخر تحديث + ترتيب حسب + تصاعدي + تنازلي + قالب + + + + صورة + ماكرو + اختر نوع المحتوى + اختر تخطيطًا + إضافة صف + إضافة محتوى + إسقاط المحتوى + تم تطبيق الإعدادات + هذا المحتوى غير مسموح به هنا + هذا المحتوى مسموح به هنا + انقر للتضمين + انقر لإدراج صورة + انقر لإدراج ماكرو + اكتب هنا... + تخطيطات الشبكة + التخطيطات هي مساحة العمل العامة لمحرر الشبكة، عادة ما تحتاج إلى واحد أو اثنين فقط من التخطيطات المختلفة + إضافة تخطيط شبكة + تحرير تخطيط الشبكة + اضبط التخطيط عن طريق تعيين عرض الأعمدة وإضافة أقسام إضافية + تكوينات الصفوف + الصفوف هي خلايا محددة مسبقًا مرتبة أفقيًا + إضافة تكوين صف + تحرير تكوين الصف + اضبط الصف عن طريق تعيين عرض الخلايا وإضافة خلايا إضافية + لا يوجد تكوين إضافي متاح + أعمدة + إجمالي عدد الأعمدة المدمجة في تخطيط الشبكة + الإعدادات + تكوين الإعدادات التي يمكن للمحررين تغييرها + الأنماط + تكوين الأنماط التي يمكن للمحررين تغييرها + السماح لجميع المحررين + السماح لجميع تكوينات الصفوف + الحد الأقصى للعناصر + اتركه فارغًا أو اضبطه على 0 لغير محدود + تعيين كافتراضي + اختر إضافي + اختر افتراضي + تمت إضافتها + تحذير + سيؤدي تعديل اسم تكوين الصف إلى فقدان البيانات لأي محتوى موجود يعتمد على هذا التكوين.

تعديل التسمية فقط لن يؤدي إلى فقدان البيانات.

]]>
+ أنت تحذف تكوين الصف + سيؤدي حذف اسم تكوين صف إلى فقدان البيانات لأي محتوى موجود يعتمد على هذا التكوين. + أنت تحذف التخطيط + سيؤدي تعديل التخطيط إلى فقدان البيانات لأي محتوى موجود يعتمد على هذا التكوين. + + + + التكوينات + مجموعة + لم تقم بإضافة أي مجموعات + إضافة مجموعة + موروث من + إضافة خاصية + تسمية مطلوبة + تمكين عرض القائمة + يضبط عنصر المحتوى لعرض قائمة قابلة للفرز والبحث من العناصر الفرعية، لن تظهر العناصر الفرعية في الشجرة + القوالب المسموح بها + اختر القوالب المسموح للمحررين باستخدامها على المحتوى من هذا النوع + السماح كجذر + السماح للمحررين بإنشاء محتوى من هذا النوع في جذر شجرة المحتوى. + أنواع العقد الفرعية المسموح بها + السماح بإنشاء محتوى من الأنواع المحددة أسفل المحتوى من هذا النوع. + اختر عقدة فرعية + وراثة علامات التبويب والخصائص من نوع محتوى موجود. ستتم إضافة علامات تبويب جديدة إلى نوع المحتوى الحالي أو دمجها إذا كانت هناك علامة تبويب بنفس الاسم موجودة. + يستخدم نوع المحتوى هذا في تكوين، وبالتالي لا يمكن تكوينه بنفسه. + لا توجد أنواع محتوى متاحة لاستخدامها كتكوين. + سيؤدي إزالة التكوين إلى حذف جميع بيانات الخصائص المرتبطة. بمجرد حفظ نوع المحتوى، لا يوجد طريق للعودة. + إنشاء جديد + استخدام موجود + إعدادات المحرر + التكوين + نعم، احذف + تم نقله تحت + تم نسخه تحت + حدد المجلد للنقل + حدد المجلد للنسخ + إلى في بنية الشجرة أدناه + كل أنواع المستندات + كل المستندات + كل عناصر الوسائط + باستخدام نوع المستند هذا سيتم حذفها نهائيًا، يرجى تأكيد رغبتك في حذفها أيضًا. + باستخدام نوع الوسائط هذا سيتم حذفها نهائيًا، يرجى تأكيد رغبتك في حذفها أيضًا. + باستخدام نوع العضو هذا سيتم حذفها نهائيًا، يرجى تأكيد رغبتك في حذفها أيضًا + وكل المستندات التي تستخدم هذا النوع + وكل عناصر الوسائط التي تستخدم هذا النوع + وكل الأعضاء الذين يستخدمون هذا النوع + يمكن للعضو التحرير + السماح لقيمة هذه الخاصية بالتحرير من قبل العضو في صفحة ملفه الشخصي + بيانات حساسة + إخفاء قيمة هذه الخاصية عن محرري المحتوى الذين ليس لديهم إذن لعرض المعلومات الحساسة + عرض على ملف العضو + السماح بعرض قيمة هذه الخاصية في صفحة ملف العضو الشخصي + علامة التبويب ليس لها ترتيب فرز + أين يتم استخدام هذا التكوين؟ + يستخدم هذا التكوين حاليًا في تكوين أنواع المحتوى التالية: + السماح بالمتغيرات + السماح بالاختلاف حسب الثقافة + السماح بالتجزئة + الاختلاف حسب الثقافة + الاختلاف حسب القطاعات + السماح للمحررين بإنشاء محتوى من هذا النوع بلغات مختلفة. + السماح للمحررين بإنشاء محتوى بلغات مختلفة. + السماح للمحررين بإنشاء قطاعات من هذا المحتوى. + السماح بالاختلاف حسب الثقافة + السماح بالتجزئة + نوع العنصر + نوع عنصر + نوع العنصر مخصص للاستخدام داخل أنواع المستندات الأخرى، وليس في شجرة المحتوى. + لا يمكن تغيير نوع المستند إلى نوع عنصر بمجرد استخدامه لإنشاء عنصر محتوى واحد أو أكثر. + هذا غير قابل للتطبيق لنوع العنصر + لقد أجريت تغييرات على هذه الخاصية. هل أنت متأكد أنك تريد تجاهلها؟ + المظهر + التسمية في الأعلى (عرض كامل) + أنت تقوم بإزالة العقدة الفرعية + ستحد إزالة العقدة الفرعية من خيارات المحررين لإنشاء أنواع محتوى مختلفة أسفل العقدة. + باستخدام هذا المحرر سيتم تحديثه بالإعدادات الجديدة. + تنظيف السجل + السماح بتجاوز إعدادات تنظيف السجل العامة. + احتفظ بكل الإصدارات الأحدث من أيام + احتفظ بأحدث إصدار لكل يوم لمدة أيام + منع التنظيف + تمكين التنظيف + ملاحظة! تنظيف إصدارات المحتوى التاريخية معطل عالميًا. لن يحدث تأثير هذه الإعدادات حتى يتم تمكينه.]]> + لا يمكن نقل المجموعة %0% إلى هذه علامة التبويب لأن المجموعة ستحصل على نفس الاسم المستعار كعلامة تبويب: "%1%". أعد تسمية المجموعة للمتابعة. + التكوينات المتاحة + إنشاء تكوين جديد + %0%?]]> + %0%?]]> + %0%?]]> + سيؤدي هذا أيضًا إلى حذف جميع العناصر أسفل هذه علامة التبويب. + سيؤدي هذا أيضًا إلى حذف جميع العناصر أسفل هذه المجموعة. + إضافة علامة تبويب + تحويل إلى علامة تبويب + اسحب الخصائص هنا لوضعها مباشرة على علامة التبويب + تعطيل تغيير نوع البيانات بقيم مخزنة. للسماح بذلك يمكنك تغيير إعداد Umbraco:CMS:DataTypes:CanBeChanged في appsettings.json. + + + إنشاء ويب هوك + إضافة رأس ويب هوك + إضافة نوع مستند + إضافة نوع وسائط + إنشاء رأس + التسليمات + لم يتم إضافة رؤوس ويب هوك + لم يتم العثور على أحداث. + مفعل + الأحداث + الحدث + الرابط + الأنواع + مفتاح ويب هوك + عدد المحاولات + الرابط الذي سيتم استدعاؤه عند تنشيط ويب هوك. + الأحداث التي يجب أن ينشط عندها ويب هوك. + تنشيط ويب هوك لنوع محتوى معين فقط. + هل ويب هوك مفعل؟ + رؤوس مخصصة لتضمينها في طلب ويب هوك. + نوع المحتوى + الرؤوس + الرجاء تحديد حدث أولاً. + + + إضافة لغة + رمز ISO + لغة إجبارية + يجب ملء خصائص هذه اللغة قبل إمكانية نشر العقدة. + اللغة الافتراضية + يمكن أن يكون لموقع أمبراكو لغة افتراضية واحدة فقط. + تغيير اللغة الافتراضية قد يؤدي إلى فقدان المحتوى الافتراضي. + يعود إلى + لا يوجد لغة للعودة إليها + للسماح للمحتوى متعدد اللغات بالعودة إلى لغة أخرى إذا لم يكن موجودًا في اللغة المطلوبة، حددها هنا. + لغة العودة + لا شيء + %0% مشترك بين جميع اللغات والقطاعات.]]> + %0% مشترك بين جميع اللغات.]]> + %0% مشترك بين جميع القطاعات.]]> + مشترك: اللغات + مشترك: القطاعات + + + إضافة معامل + تعديل معامل + أدخل اسم الماكرو + المعاملات + حدد المعاملات التي يجب أن تكون متاحة عند استخدام هذا الماكرو. + حدد ملف عرض جزئي للماكرو + + + جاري بناء النماذج + هذا قد يستغرق بعض الوقت، لا تقلق + تم إنشاء النماذج + تعذر إنشاء النماذج + فشل إنشاء النماذج، راجع الاستثناء في سجل U + + + إضافة قيمة افتراضية + القيمة الافتراضية + حقل احتياطي + القيمة الافتراضية + حالة الأحرف + التشفير + اختر حقل + تحويل فواصل الأسطر + يستبدل فواصل الأسطر بوسم 'br' HTML + الحقول المخصصة + التاريخ فقط + تنسيق كتاريخ + تشفير HTML + سيستبدل الأحرف الخاصة بما يعادلها في HTML. + سيتم إدراجه بعد قيمة الحقل + سيتم إدراجه قبل قيمة الحقل + أحرف صغيرة + لا شيء + عينة الإخراج + إدراج بعد الحقل + إدراج قبل الحقل + متكرر + نعم، اجعله متكررًا + الحقول القياسية + أحرف كبيرة + تشفير URL + سيقوم بتنسيق الأحرف الخاصة في عناوين URL + سيتم استخدامه فقط عندما تكون قيم الحقول أعلاه فارغة + سيتم استخدام هذا الحقل فقط إذا كان الحقل الأساسي فارغًا + التاريخ والوقت + + + تفاصيل الترجمة + تنزيل XML DTD + الحقول + تضمين الصفحات الفرعية + + لم يتم العثور على مستخدمين مترجمين. الرجاء إنشاء مستخدم مترجم قبل البدء في إرسال المحتوى للترجمة + تم إرسال الصفحة '%0%' للترجمة + إرسال الصفحة '%0%' للترجمة + إجمالي الكلمات + ترجمة إلى + تم الانتهاء من الترجمة. + يمكنك معاينة الصفحات التي ترجمتها للتو بالنقر أدناه. إذا تم العثور على الصفحة الأصلية، ستحصل على مقارنة بين الصفحتين. + فشلت الترجمة، قد يكون ملف XML تالفًا + خيارات الترجمة + المترجم + رفع ملف ترجمة XML + + + المحتوى + قوالب المحتوى + الوسائط + متصفح الذاكرة المؤقتة + سلة المحذوفات + الحزم المنشأة + أنواع البيانات + القاموس + الحزم المثبتة + تثبيت سكين + تثبيت مجموعة البداية + اللغات + تثبيت حزمة محلية + ماكروات + أنواع الوسائط + الأعضاء + مجموعات الأعضاء + أدوار الأعضاء + أنواع الأعضاء + أنواع المستندات + أنواع العلاقات + الحزم + الحزم + العروض الجزئية + ملفات ماكرو العروض الجزئية + التثبيت من مستودع + تثبيت رانوي + وحدات رانوي + ملفات البرمجة + النصوص البرمجية + صفحات الأنماط + القوالب + عارض السجلات + المستخدمون + الإعدادات + القالب + طرف ثالث + ويب هوكس + + + تحديث جديد متاح + %0% جاهز، انقر هنا للتنزيل + لا يوجد اتصال بالخادم + خطأ في التحقق من التحديث. الرجاء مراجعة سجل التتبع لمزيد من المعلومات + + + الوصول + بناءً على المجموعات المخصصة والعقد البدائية، لدى المستخدم حق الوصول إلى العقد التالية + تعيين وصول + مسؤول + حقل الفئة + تم إنشاء المستخدم + تغيير كلمة المرور + تغيير الصورة + كلمة مرور جديدة + %0% حرفًا على الأقل مطلوب! + يجب أن يحتوي على %0% حرفًا خاصًا على الأقل. + لم يتم حظر الحساب + لم يتم تغيير كلمة المرور + تأكيد كلمة المرور الجديدة + يمكنك تغيير كلمة المرور الخاصة بك للوصول إلى واجهة أمبراكو الخلفية عن طريق ملء النموذج أدناه والنقر على زر 'تغيير كلمة المرور' + قناة المحتوى + إنشاء مستخدم آخر + إنشاء مستخدمين جدد لمنحهم حق الوصول إلى أمبراكو. عند إنشاء مستخدم جديد، سيتم إنشاء كلمة مرور يمكنك مشاركتها مع المستخدم. + حقل الوصف + تعطيل المستخدم + نوع المستند + محرر + مطلوب - أدخل عنوان بريد إلكتروني لهذا المستخدم + حقل مقتطف + محاولات تسجيل دخول فاشلة + انتقل إلى ملف المستخدم + إضافة مجموعات لتعيين الوصول والأذونات + دعوة مستخدم آخر + دعوة مستخدمين جدد لمنحهم حق الوصول إلى أمبراكو. سيتم إرسال بريد دعوة إلى المستخدم بمعلومات حول كيفية تسجيل الدخول إلى أمبراكو. تستمر الدعوات لمدة 72 ساعة. + اللغة + تعيين اللغة التي ستظهر في القوائم والنوافذ + تاريخ آخر حظر + آخر تسجيل دخول + تم تغيير كلمة المرور آخر مرة + اسم المستخدم + عقدة بدء الوسائط + تقييم مكتبة الوسائط إلى عقدة بدء محددة + عقد بدء الوسائط + تقييم مكتبة الوسائط إلى عقد بدء محددة + الأقسام + مطلوب - أدخل اسمًا لهذا المستخدم + تعطيل الوصول إلى أمبراكو + لم يسجل الدخول بعد + كلمة المرور القديمة + كلمة المرور + إعادة تعيين كلمة المرور + تم تغيير كلمة المرور الخاصة بك! + تم تغيير كلمة المرور + الرجاء تأكيد كلمة المرور الجديدة + أدخل كلمة المرور الجديدة + كلمة المرور الجديدة لا يمكن أن تكون فارغة! + كلمة المرور الحالية + كلمة المرور الحالية غير صالحة + كان هناك اختلاف بين كلمة المرور الجديدة وكلمة المرور المؤكدة. الرجاء المحاولة مرة أخرى! + كلمة المرور المؤكدة لا تطابق كلمة المرور الجديدة! + استبدال أذونات العقد الفرعية + أنت تقوم حاليًا بتعديل الأذونات للصفحات: + حدد الصفحات لتعديل أذوناتها + إزالة الصورة + الأذونات الافتراضية + أذونات مفصلة + تعيين أذونات لعقد محددة + الملف الشخصي + بحث في جميع العناصر الفرعية + تقييد اللغات التي يمكن للمستخدمين الوصول إليها للتحرير + إضافة أقسام لمنح المستخدمين حق الوصول + اختر مجموعات المستخدمين + لم يتم تحديد عقدة بدء + لم يتم تحديد عقد بدء + عقدة بدء المحتوى + تقييم شجرة المحتوى إلى عقدة بدء محددة + عقد بدء المحتوى + تقييم شجرة المحتوى إلى عقد بدء محددة + تم تحديث المستخدم آخر مرة + تم إنشاؤه + تم إنشاء المستخدم الجديد بنجاح. لتسجيل الدخول إلى أمبراكو استخدم كلمة المرور أدناه. + إدارة المستخدمين + الاسم + أذونات المستخدم + مجموعة المستخدمين + تمت دعوته + تم إرسال دعوة إلى المستخدم الجديد بتفاصيل حول كيفية تسجيل الدخول إلى أمبراكو. + مرحبًا بك في أمبراكو! خلال دقيقة واحدة ستكون جاهزًا، نحتاج فقط منك إعداد كلمة مرور. + مرحبًا بك في أمبراكو! لسوء الحظ، انتهت صلاحية دعوتك. الرجاء الاتصال بمسؤولك واطلب إعادة إرسالها. + كاتب + تغيير + ملفك الشخصي + سجلّك الحديث + تنتهي الجلسة خلال + دعوة مستخدم + إنشاء مستخدم + إرسال دعوة + العودة إلى المستخدمين + أمبراكو: دعوة + + + + + + + + + + + +
+ + + + + +
+ +
+ +
+
+ + + + + + +
+
+
+ + + + +
+ + + + +
+

+ مرحبًا %0%، +

+

+ لقد تمت دعوتك بواسطة %1% إلى واجهة أمبراكو الخلفية. +

+

+ رسالة من %1%: +
+%2% +

+ + + + + + +
+ + + + + + +
+ + انقر على هذا الرابط لقبول الدعوة + +
+
+

إذا لم تتمكن من النقر على الرابط، انسخ والصق هذا الرابط في نافذة متصفحك:

+ + + + +
+ +%3% + +
+

+
+
+


+
+
+ +]]>
+ جاري إعادة إرسال الدعوة... + حذف المستخدم + هل أنت متأكد أنك تريد حذف حساب هذا المستخدم؟ + الكل + نشط + معطل + محظور + موافق عليه + مدعو + غير نشط + الاسم (أ-ي) + الاسم (ي-أ) + الأقدم + الأحدث + آخر تسجيل دخول + لم تتم إضافة مجموعات مستخدمين + إذا كنت ترغب في تعطيل موفر المصادقة الثنائية هذا، فيجب عليك إدخال الرمز المعروض على جهاز المصادقة الخاص بك: + موفر المصادقة الثنائية هذا مفعل + تم الآن تعطيل موفر المصادقة الثنائية هذا + حدث خطأ ما أثناء محاولة تعطيل موفر المصادقة الثنائية هذا + هل تريد تعطيل موفر المصادقة الثنائية هذا لهذا المستخدم؟ + يوجد مستخدم بهذا الاسم بالفعل + يجب أن تحتوي كلمة المرور على رقم واحد على الأقل ('0'-'9') + يجب أن تحتوي كلمة المرور على حرف صغير واحد على الأقل ('a'-'z') + يجب أن تحتوي كلمة المرور على حرف غير أبجدي رقمي واحد على الأقل + يجب أن تستخدم كلمة المرور %0% أحرفًا مختلفة على الأقل + يجب أن تحتوي كلمة المرور على حرف كبير واحد على الأقل ('A'-'Z') + يجب أن تتكون كلمة المرور من %0% أحرف على الأقل + السماح بالوصول إلى جميع اللغات + المستخدم لديه كلمة مرور معينة بالفعل + المستخدم موجود بالفعل في المجموعة '%0%' + الحظر غير مفعل لهذا المستخدم + المستخدم ليس في المجموعة '%0%' + تكوين المصادقة الثنائية + + + التحقق + التحقق كعنوان بريد إلكتروني + التحقق كرقم + التحقق كرابط + ...أو أدخل تحققًا مخصصًا + الحقل إلزامي + أدخل رسالة خطأ تحقق مخصصة (اختياري) + أدخل تعبيرًا منتظمًا + أدخل رسالة خطأ تحقق مخصصة (اختياري) + يجب إضافة %0% على الأقل + يمكنك فقط إضافة %0% + إضافة حتى %0% + عناصر + رابط/روابط + رابط/روابط محددة + عنصر/عناصر محددة + تاريخ غير صالح + ليس رقمًا + ليس حجم خطوة رقمي صالح + بريد إلكتروني غير صالح + القيمة لا يمكن أن تكون فارغة + القيمة لا يمكن أن تكون فارغة + القيمة غير صالحة، لا تطابق النمط الصحيح + تحقق مخصص + %1% إضافي.]]> + %1% إضافي.]]> + لم يتم استيفاء متطلبات كمية المحتوى لواحد أو أكثر من المناطق. + اسم مجموعة عضو غير صالح + اسم مجموعة مستخدم غير صالح + رمز غير صالح + اسم مستخدم غير صالح + البريد الإلكتروني '%0%' مستخدم بالفعل + اسم مجموعة المستخدمين '%0%' مستخدم بالفعل + اسم مجموعة الأعضاء '%0%' مستخدم بالفعل + اسم المستخدم '%0%' مستخدم بالفعل + + + القيمة مضبوطة على القيمة الموصى بها: '%0%'. + القيمة المتوقعة '%1%' لـ '%2%' في ملف التكوين '%3%'، ولكن تم العثور على '%0%'. + تم العثور على قيمة غير متوقعة '%0%' لـ '%2%' في ملف التكوين '%3%'. + تم تعيين MacroErrors على '%0%'. + تم تعيين MacroErrors على '%0%' مما سيمنع بعض أو كل الصفحات في موقعك من التحميل بالكامل إذا كانت هناك أي أخطاء في الماكروات. تصحيح هذا سيضبط القيمة على '%1%'. + شهادة موقعك صالحة. + خطأ في التحقق من الشهادة: '%0%' + انتهت صلاحية شهادة SSL لموقعك. + شهادة SSL لموقعك تنتهي صلاحيتها خلال %0% يومًا. + خطأ في استدعاء الرابط %0% - '%1%' + أنت حاليًا %0% تشاهد الموقع باستخدام مخطط HTTPS. + إعداد التطبيق 'Umbraco:CMS:Global:UseHttps' مضبوط على 'false' في ملف appSettings.json الخاص بك. بمجرد الوصول إلى هذا الموقع باستخدام مخطط HTTPS، يجب ضبطه على 'true'. + إعداد التطبيق 'Umbraco:CMS:Global:UseHttps' مضبوط على '%0%' في ملف appSettings.json الخاص بك، ملفات تعريف الارتباط الخاصة بك %1% معلمة كآمنة. + وضع تصحيح التجميع معطل. + وضع تصحيح التجميع مفعل حاليًا. يوصى بتعطيل هذا الإعداد قبل النشر. + %0%.]]> + إعداد التطبيق 'Umbraco:CMS:WebRouting:UmbracoApplicationUrl' غير مضبوط. + + X-Frame-Options المستخدم للتحكم في إمكانية تضمين موقع داخل إطار من قبل موقع آخر.]]> + + X-Frame-Options المستخدم للتحكم في إمكانية تضمين موقع داخل إطار من قبل موقع آخر.]]> + + X-Content-Type-Options المستخدم للحماية من ثغرات استنشاق MIME.]]> + + X-Content-Type-Options المستخدم للحماية من ثغرات استنشاق MIME.]]> + + Strict-Transport-Security، المعروف أيضًا باسم رأس HSTS.]]> + + Strict-Transport-Security.]]> + + Strict-Transport-Security، المعروف أيضًا باسم رأس HSTS. لا ينبغي أن يكون هذا الرأس موجودًا على localhost.]]> + + + Strict-Transport-Security. لا ينبغي أن يكون هذا الرأس موجودًا على localhost.]]> + + + X-XSS-Protection. يوصى بعدم إضافة هذا الرأس إلى موقعك.
+ يمكنك القراءة عن هذا على موقع Mozilla]]> +
+ + X-XSS-Protection.]]> + + + %0%.]]> + لم يتم العثور على رؤوس تكشف معلومات عن تكنولوجيا الموقع. + + في ملف Web.config، لم يتم العثور على system.net/mailsettings. + في قسم system.net/mailsettings بملف Web.config، لم يتم تكوين المضيف. + + إعدادات SMTP مضبوطة بشكل صحيح والخدمة تعمل كما هو متوقع. + + تعذر الوصول إلى خادم SMTP المضبوط بالمضيف '%0%' والمنفذ '%1%'. يرجى التحقق لضمان صحة إعدادات SMTP في قسم system.net/mailsettings بملف Web.config. + + + %0%.]]> + + %0%.]]> + +

نتائج فحوصات صحة Umbraco المجدولة التي تم تشغيلها في %0% عند %1% هي كما يلي:

%2%]]>
+ حالة فحص صحة Umbraco: %0% + مجموعة الفحص + + يقيم فاحص الصحة مناطق مختلفة من موقعك لإعدادات أفضل الممارسات، التكوين، المشكلات المحتملة، إلخ. يمكنك إصلاح المشكلات بسهولة عن طريق الضغط على زر. + يمكنك إضافة فحوصات الصحة الخاصة بك، ألق نظرة على الوثائق لمزيد من المعلومات حول فحوصات الصحة المخصصة.

+ ]]> +
+ + + تعطيل متتبع URL + تمكين متتبع URL + URL الأصلي + تم التوجيه إلى + إدارة URL المُعاد توجيهه + توجه عناوين URL التالية إلى عنصر المحتوى هذا: + لم يتم إجراء أي عمليات إعادة توجيه + عند إعادة تسمية صفحة منشورة أو نقلها، سيتم توجيهها تلقائيًا إلى الصفحة الجديدة. + + تمت إزالة URL المُعاد توجيهه. + خطأ في إزالة URL المُعاد توجيهه. + سيؤدي هذا إلى إزالة إعادة التوجيه + هل أنت متأكد أنك تريد تعطيل متتبع URL؟ + تم الآن تعطيل متتبع URL. + خطأ في تعطيل متتبع URL، يمكن العثور على مزيد من المعلومات في ملف السجل الخاص بك. + تم الآن تمكين متتبع URL. + خطأ في تمكين متتبع URL، يمكن العثور على مزيد من المعلومات في ملف السجل الخاص بك. + اللغة + + + لا توجد عناصر قاموس للاختيار من بينها + + + %0% أحرف متبقية.]]> + %1% زيادة.]]> + + + تم نقل المحتوى إلى سلة المحذوفات بالمعرف: {0} المتعلق بالمحتوى الأصلي بالمعرف: {1} + تم نقل الوسائط إلى سلة المحذوفات بالمعرف: {0} المتعلق بعنصر الوسائط الأصلي بالمعرف: {1} + لا يمكن استعادة هذا العنصر تلقائيًا + لا يوجد موقع يمكن استعادة هذا العنصر إليه تلقائيًا. يمكنك نقل العنصر يدويًا باستخدام الشجرة أدناه. + + تمت استعادته تحت + + + الاتجاه + من الأب إلى الابن + ثنائي الاتجاه + الأب + الابن + العدد + العلاقة + العلاقات + تم الإنشاء + تعليق + الاسم + لا توجد علاقات لنوع العلاقة هذا + نوع العلاقة + العلاقات + هو تابع + نعم + لا + + + ابدأ هنا + إدارة URL المُعاد توجيهه + المحتوى + مرحبًا + إدارة Examine + حالة النشر + منشئ النماذج + فحص الصحة + التقييم + ابدأ هنا + تثبيت نماذج Umbraco + بيانات القياس عن بعد + + + العودة + التخطيط النشط: + انتقل إلى + مجموعة + تم بنجاح + تحذير + فشل + اقتراح + تم اجتياز الفحص + فشل الفحص + فتح بحث الواجهة الخلفية + فتح/إغلاق مساعدة الواجهة الخلفية + فتح/إغلاق خيارات ملفك الشخصي + إعداد اللغة وأسماء المضيف لـ %0% + إنشاء عقدة جديدة تحت %0% + إعداد قيود الوصول على %0% + إعداد الأذونات على %0% + تغيير ترتيب الفرز لـ %0% + إنشاء قالب محتوى بناءً على %0% + فتح قائمة السياق لـ + اللغة الحالية + تغيير اللغة إلى + إنشاء مجلد جديد + عرض جزئي + ماكرو عرض جزئي + عضو + نوع البيانات + بحث لوحة التحكم لإعادة التوجيه + بحث قسم مجموعة المستخدمين + بحث قسم المستخدمين + إنشاء عنصر + إنشاء + تحرير + الاسم + إضافة صف جديد + عرض المزيد من الخيارات + بحث الواجهة الخلفية لـ Umbraco + ابحث عن عقد المحتوى، عقد الوسائط، إلخ. عبر الواجهة الخلفية. + عند توفر نتائج الإكمال التلقائي، اضغط على أزرار الأعلى والأسفل، أو استخدم مفتاح Tab واستخدم مفتاح Enter للتحديد. + + المسار: + تم العثور عليه في + لديه ترجمة + لا يوجد ترجمة + عناصر القاموس + حدد أحد الخيارات لتحرير العقدة. + تنفيذ الإجراء %0% على عقدة %1% + إضافة تسمية توضيحية للصورة + بحث شجرة المحتوى + الحد الأقصى للكمية + توسيع العناصر الفرعية لـ + فتح عقدة السياق لـ + + + المراجع + لا توجد مراجع لنوع البيانات هذا. + لا توجد مراجع لهذا العنصر. + مستخدم في أنواع المستندات + مستخدم في أنواع الوسائط + مستخدم في أنواع الأعضاء + مستخدم بواسطة + العناصر المستخدمة + التابعون المستخدمون + هذا العنصر أو تابعوه قيد الاستخدام. قد يؤدي الحذف إلى روابط معطلة على موقعك. + هذا العنصر أو تابعوه قيد الاستخدام. قد يؤدي إلغاء النشر إلى روابط معطلة على موقعك. يرجى اتخاذ الإجراءات المناسبة. + هذا العنصر أو تابعوه قيد الاستخدام. لذلك، تم تعطيل الحذف. + العناصر التالية التي تحاول %0% مستخدمة من قبل محتوى آخر. + مرجع من العناصر التالية + تعتمد العناصر التالية على هذا + للعناصر التابعة التالية تبعيات + + + حذف البحث المحفوظ + مستويات السجل + تحديد الكل + إلغاء تحديد الكل + عمليات البحث المحفوظة + حفظ البحث + أدخل اسمًا وصفيًا لاستعلام البحث الخاص بك + تصفية البحث + إجمالي العناصر + الطابع الزمني + المستوى + الجهاز + الرسالة + استثناء + الخصائص + البحث باستخدام Google + ابحث عن هذه الرسالة باستخدام Google + البحث باستخدام Bing + ابحث عن هذه الرسالة باستخدام Bing + البحث في Our Umbraco + ابحث عن هذه الرسالة في منتديات Our Umbraco والوثائق + البحث في Our Umbraco باستخدام Google + ابحث في منتديات Our Umbraco باستخدام Google + البحث في مصدر Umbraco + ابحث داخل كود مصدر Umbraco على Github + البحث في مشكلات Umbraco + ابحث في مشكلات Umbraco على Github + حذف هذا البحث + ابحث عن السجلات باستخدام معرف الطلب + ابحث عن السجلات باستخدام مساحة الاسم + ابحث عن السجلات باستخدام اسم الجهاز + فتح + الاستطلاع + كل ثانيتين + كل 5 ثوان + كل 10 ثوان + كل 20 ثانية + كل 30 ثانية + استطلاع كل ثانيتين + استطلاع كل 5 ثوان + استطلاع كل 10 ثوان + استطلاع كل 20 ثانية + استطلاع كل 30 ثانية + + + نسخ %0% + %0% من %1% + مجموعة من %0% + إزالة جميع العناصر + مسح الحافظة + + + فتح إجراءات الخاصية + إغلاق إجراءات الخاصية + + + تحديث الحالة + ذاكرة التخزين المؤقت + + + + إعادة تحميل + ذاكرة التخزين المؤقت لقاعدة البيانات + + قد تكون إعادة البناء مكلفة. + استخدمه عندما لا يكون إعادة التحميل كافيًا، وتعتقد أن ذاكرة التخزين المؤقت لقاعدة البيانات لم يتم + إنشاؤها بشكل صحيح - مما قد يشير إلى مشكلة حرجة في Umbraco. + ]]> + + إعادة بناء + الداخليات + + + + جمع + حالة ذاكرة التخزين المؤقت المنشورة + ذاكرات التخزين المؤقت + + + تقييم الأداء + + + يعمل Umbraco حاليًا في وضع التصحيح. هذا يعني أنه يمكنك استخدام أداة تقييم الأداء المدمجة لتقييم الأداء عند عرض الصفحات. +

+

+ إذا كنت تريد تنشيط أداة التقييم لعرض صفحة معينة، ما عليك سوى إضافة umbDebug=true إلى سلسلة الاستعلام عند طلب الصفحة. +

+

+ إذا كنت تريد تنشيط أداة التقييم افتراضيًا لجميع عمليات عرض الصفحات، يمكنك استخدام المفتاح أدناه. + سيؤدي هذا إلى تعيين ملف تعريف الارتباط في متصفحك، والذي يقوم بعد ذلك بتنشيط أداة التقييم تلقائيًا. + بمعنى آخر، سيتم تنشيط أداة التقييم افتراضيًا في متصفحك فقط - وليس متصفحات الآخرين. +

+ ]]> +
+ تنشيط أداة التقييم افتراضيًا + تذكير ودود + + + لا يجب أبدًا ترك موقع إنتاج يعمل في وضع التصحيح. يتم إيقاف وضع التصحيح عن طريق تعيين Umbraco:CMS:Hosting:Debug إلى false في appsettings.json، appsettings.{Environment}.json أو عبر متغير البيئة. +

+ ]]> +
+ + + لا يعمل Umbraco حاليًا في وضع التصحيح، لذلك لا يمكنك استخدام أداة التقييم المدمجة. هكذا يجب أن يكون لموقع إنتاج. +

+

+ يتم تشغيل وضع التصحيح عن طريق تعيين Umbraco:CMS:Hosting:Debug إلى true في appsettings.json، appsettings.{Environment}.json أو عبر متغير البيئة. +

+ ]]> +
+ + + ساعات من مقاطع فيديو تدريب Umbraco تبعد نقرة واحدة فقط + + تريد إتقان Umbraco؟ اقضي بضع دقائق في تعلم بعض أفضل الممارسات من خلال مشاهدة أحد هذه الفيديوهات حول استخدام Umbraco. وقم بزيارة umbraco.tv لمزيد من فيديوهات Umbraco

+ ]]> +
+ + تريد إتقان Umbraco؟ اقضي بضع دقائق في تعلم بعض أفضل الممارسات من خلال زيارة قناة Umbraco Learning Base على Youtube. هنا يمكنك العثور على مجموعة من المواد المرئية التي تغطي العديد من جوانب Umbraco.

+ ]]> +
+ لتبدأ + + + ابدأ هنا + يحتوي هذا القسم على اللبنات الأساسية لموقع Umbraco الخاص بك. اتبع الروابط أدناه لمعرفة المزيد حول العمل مع العناصر في قسم الإعدادات + + اكتشف المزيد + + في قسم الوثائق من Our Umbraco + ]]> + + + منتدى المجتمع + ]]> + + + فيديوهاتنا التعليمية المجانية على Umbraco Learning Base + ]]> + + + أدوات تعزيز الإنتاجية والدعم التجاري لدينا + ]]> + + + التدريب والشهادات في الحياة الواقعية + ]]> + + + + مرحبًا بكم في نظام إدارة المحتوى الودود + شكرًا لاختيارك Umbraco - نعتقد أن هذا يمكن أن يكون بداية شيء جميل. بينما قد تشعر بالإرهاق في البداية، فقد بذلنا الكثير لجعل منحنى التعلم سلسًا وسريعًا قدر الإمكان. + + + + نماذج Umbraco + أنشئ نماذج باستخدام واجهة سحب وإفلات سهلة الاستخدام. من نماذج الاتصال البسيطة التي ترسل رسائل بريد إلكتروني إلى الاستبيانات المتقدمة التي تندمج مع أنظمة CRM. سوف يحبه عملاؤك! + + + + اختر نوع العنصر + إرفاق نوع عنصر إعدادات + حدد العرض + حدد ورقة الأنماط + اختر الصورة المصغرة + إنشاء نوع عنصر جديد + ورقة أنماط مخصصة + إضافة ورقة أنماط + مظهر الكتلة + نماذج البيانات + مظهر الكتالوج + لون الخلفية + لون الأيقونة + نموذج المحتوى + التسمية + عرض مخصص + عرض وصف العرض المخصص + تجاوز كيفية ظهور هذه الكتلة في واجهة المستخدم الخلفية. اختر ملف .html يحتوي على العرض الخاص بك. + + نموذج الإعدادات + حجم محرر التراكب + إضافة عرض مخصص + إضافة إعدادات + + %0%?]]> + + %0%?]]> + سيظل محتوى هذه الكتلة موجودًا، لن يكون تحرير هذا المحتوى متاحًا وسيظهر كمحتوى غير مدعوم. + + + %0% وجميع تكوينات الكتل الخاصة بها؟]]> + سيظل محتوى هذه الكتل موجودًا، لن يكون تحرير هذا المحتوى متاحًا وسيظهر كمحتوى غير مدعوم. + + + لا يمكن تحريره لأن نوع العنصر غير موجود. + الصورة المصغرة + إضافة صورة مصغرة + إنشاء فارغ + الحافظة + الإعدادات + متقدم + إخفاء محرر المحتوى + إخفاء زر تحرير المحتوى ومحرر المحتوى من تراكب محرر الكتل. + تحرير مضمن + تمكين التحرير المضمن للخاصية الأولى. يمكن تحرير الخصائص الإضافية في التراكب. + لقد أجريت تغييرات على هذا المحتوى. هل أنت متأكد أنك تريد تجاهلها؟ + تجاهل الإنشاء؟ + + خطأ! + لم يعد نوع العنصر لهذه الكتلة موجودًا + إضافة محتوى + إضافة %0% + الخاصية '%0%' تستخدم المحرر '%1%' غير المدعوم في الكتل. + ضبط التركيز على الكتلة الحاوية + التعريف + التحقق + %0% يجب أن يكون موجودًا على الأقل %2% مرة/مرات.]]> + %0% يجب أن يكون موجودًا بحد أقصى %3% مرة/مرات.]]> + عدد الكتل + السماح فقط بأنواع كتل محددة + أنواع الكتل المسموح بها + حدد أنواع الكتل المسموح بها في هذه المنطقة، واختياريًا عدد كل نوع يجب أن يكون موجودًا. + هل أنت متأكد أنك تريد حذف هذه المنطقة؟ + سيتم حذف أي كتل تم إنشاؤها حاليًا داخل هذه المنطقة. + خيارات التخطيط + هيكلية + خيارات الحجم + حدد خيار حجم واحد أو أكثر، هذا يمكّن تغيير حجم الكتلة + امتدادات الأعمدة المتاحة + حدد عدد الأعمدة المختلفة المسموح لهذه الكتلة بالامتداد عليها. هذا لا يمنع الكتل من وضعها في مناطق ذات امتداد عمود أصغر. + امتدادات الصفوف المتاحة + حدد نطاق صفوف التخطيط المسموح لهذه الكتلة بالامتداد عليها. + السماح في الجذر + اجعل هذه الكتلة متاحة في جذر التخطيط. + السماح في المناطق + اجعل هذه الكتلة متاحة افتراضيًا داخل مناطق الكتل الأخرى (ما لم يتم تعيين أذونات صريحة لهذه المناطق). + افتراضيًا، جميع أنواع الكتل مسموح بها في المنطقة، استخدم هذا الخيار للسماح فقط بالأنواع المحددة. + المناطق + أعمدة الشبكة للمناطق + حدد عدد الأعمدة التي ستكون متاحة للمناطق. إذا لم يتم تحديدها، سيتم استخدام عدد الأعمدة المحدد للتخطيط بأكمله. + المناطق + لتمكين تداخل الكتل داخل هذه الكتلة، حدد منطقة واحدة أو أكثر. تتبع المناطق التخطيط المحدد بواسطة تكوين عمود الشبكة الخاص بها. يمكن تعديل 'امتداد العمود' و'امتداد الصف' لكل منطقة باستخدام مربع مقبض القياس في الزاوية اليمنى السفلية من المنطقة المحددة. + %0% غير مسموح به في هذه البقعة.]]> + ورقة أنماط التخطيط الافتراضية + تم رفض المحتوى غير المسموح به + + + + + اسحب للقياس + تسمية زر الإنشاء + تجاوز نص التسمية لإضافة كتلة جديدة إلى هذه المنطقة، مثال: 'إضافة عنصر واجهة' + عرض خيارات تغيير الحجم + إضافة كتلة + إضافة مجموعة + اختر مجموعة أو كتلة + تعيين حد أدنى + تعيين حد أقصى + كتلة + كتلة + الإعدادات + المناطق + متقدم + الأذونات + تثبيت نموذج التكوين + + تثبيت + وضع الفرز + إنهاء وضع الفرز + يجب أن يكون اسم مستعار هذه المنطقة فريدًا مقارنة بمناطق الكتلة الأخرى. + تكوين المنطقة + حذف المنطقة + إضافة خيار امتداد %0% أعمدة + إدراج كتلة + عرض مضمن مع النص + + + ما هي قوالب المحتوى؟ + قوالب المحتوى هي محتوى محدد مسبقًا يمكن تحديده عند إنشاء عقدة محتوى جديدة. + + كيف يمكنني إنشاء قالب محتوى؟ + + هناك طريقتان لإنشاء قالب محتوى:

+
    +
  • انقر بزر الماوس الأيمن على عقدة محتوى وحدد "إنشاء قالب محتوى" لإنشاء قالب محتوى جديد.
  • +
  • انقر بزر الماوس الأيمن على شجرة قوالب المحتوى في قسم الإعدادات وحدد نوع المستند الذي تريد إنشاء قالب محتوى له.
  • +
+

بمجرد إعطائه اسمًا، يمكن للمحررين البدء في استخدام قالب المحتوى كأساس لصفحتهم الجديدة.

+ ]]> +
+ كيف يمكنني إدارة قوالب المحتوى؟ + يمكنك تحرير وحذف قوالب المحتوى من شجرة "قوالب المحتوى" في قسم الإعدادات. قم بتوسيع نوع المستند الذي يعتمد عليه قالب المحتوى وانقر عليه للتحرير أو الحذف. + + + + إنهاء + إنهاء وضع المعاينة + معاينة الموقع + فتح الموقع في وضع المعاينة + معاينة الموقع؟ + لقد أنهيت وضع المعاينة، هل تريد تمكينه مرة أخرى لعرض أحدث نسخة محفوظة من موقعك؟ + + معاينة أحدث نسخة + عرض النسخة المنشورة + عرض النسخة المنشورة؟ + أنت في وضع المعاينة، هل تريد الخروج لعرض النسخة المنشورة من موقعك؟ + + عرض النسخة المنشورة + البقاء في وضع المعاينة + + + إنشاء مجلد + كتابة ملف للحزم + كتابة ملف + إنشاء مجلد وسائط + + + عنصر واحد مسترد + عناصر مستردة + + + حدد محرر الخاصية + حدد محرر الخاصية + + + الموافقة على بيانات القياس عن بعد + تم حفظ مستوى القياس عن بعد! + نرغب في جمع معلومات النظام والاستخدام من تثبيتك. +
سيتم مشاركة البيانات المجمعة بشكل منتظم بالإضافة إلى الدروس المستفادة من هذه المقاييس. +
نأمل أن تساعدنا في جمع بعض البيانات القيمة. +
+
نحن لن نجمع أي بيانات شخصية مثل المحتوى، الكود، معلومات المستخدم، وستكون جميع البيانات مجهولة بالكامل.]]>
+ سنرسل فقط معرف موقع مجهول لإعلامنا بوجود الموقع. + سنرسل معرف موقع مجهول، إصدار Umbraco، والحزم المثبتة + +
  • معرف موقع مجهول، إصدار Umbraco، والحزم المثبتة.
  • +
  • عدد: العقد الجذرية، عقد المحتوى، الماكروات، الوسائط، أنواع المستندات، أنواع الخصائص، التركيبات، القوالب، اللغات، النطاقات، مجموعة المستخدمين، المستخدمين، الأعضاء، موفري تسجيل الدخول الخارجيين للواجهة الخلفية، ويب هوكس، أنواع بيانات النص المنسق، الكتل المستخدمة في أنواع بيانات النص المنسق، ومحررات الخصائص قيد الاستخدام.
  • +
  • معلومات النظام: خادم الويب، نظام تشغيل الخادم، إطار عمل الخادم، لغة نظام تشغيل الخادم، وموفر قاعدة البيانات.
  • +
  • إعدادات التكوين: وضع منشئ النماذج، إذا كان مسار Umbraco المخصص موجودًا، بيئة ASP، ما إذا كان واجهة برمجة التطبيقات للتوصيل ممكّنة، وتسمح بالوصول العام، وما إذا كنت في وضع التصحيح.
  • + +قد نغير ما نرسله على المستوى التفصيلي في المستقبل. إذا قمت باختيار "تفصيلي"، فأنت توافق على جمع المعلومات المجهولة الحالية والمستقبلية.]]>
    + +
    diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/en.xml b/src/Umbraco.Core/EmbeddedResources/Lang/en.xml index 5f4893976663..2e09b649ba4d 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/en.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/en.xml @@ -294,6 +294,7 @@ Content. Are you sure you want to delete all items? + Are you sure you want to delete this item? No Content Types are configured for this property. Add Element Type Select Element Type @@ -331,6 +332,7 @@ Select the date and time to publish and/or unpublish the content item. Create new Paste from clipboard + Remove item This item is in the Recycle Bin No content can be added for this item Save is not allowed diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml b/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml index f86b0825139c..b9ce1dd2c7f8 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml @@ -290,6 +290,7 @@ What does this mean?]]> Are you sure you want to delete this item? Are you sure you want to delete all items? + Are you sure you want to delete this item? Property %0% uses editor %1% which is not supported by Nested Content. @@ -330,6 +331,7 @@ Select the date and time to publish and/or unpublish the content item. Create new Paste from clipboard + Remove item This item is in the Recycle Bin Save is not allowed Publish is not allowed diff --git a/src/Umbraco.Core/Extensions/IntExtensions.cs b/src/Umbraco.Core/Extensions/IntExtensions.cs index d347993dd07e..9b3c2c60ae6b 100644 --- a/src/Umbraco.Core/Extensions/IntExtensions.cs +++ b/src/Umbraco.Core/Extensions/IntExtensions.cs @@ -1,15 +1,17 @@ // Copyright (c) Umbraco. // See LICENSE for more details. +using System.Diagnostics.CodeAnalysis; + namespace Umbraco.Extensions; public static class IntExtensions { /// - /// Does something 'x' amount of times + /// Does something 'x' amount of times. /// - /// - /// + /// Number of times to execute the action. + /// The action to execute. public static void Times(this int n, Action action) { for (var i = 0; i < n; i++) @@ -19,11 +21,11 @@ public static void Times(this int n, Action action) } /// - /// Creates a Guid based on an integer value + /// Creates a Guid based on an integer value. /// - /// value to convert + /// The value to convert. /// - /// + /// The converted . /// public static Guid ToGuid(this int value) { @@ -31,4 +33,28 @@ public static Guid ToGuid(this int value) BitConverter.GetBytes(value).CopyTo(bytes, 0); return new Guid(bytes); } + + /// + /// Restores a GUID previously created from an integer value using . + /// + /// The value to convert. + /// The converted . + /// + /// True if the value could be created, otherwise false. + /// + /// + /// This is used with Umbraco entities that only have integer references in the database (e.g. users). + /// + public static bool TryParseFromGuid(Guid value, [NotNullWhen(true)] out int? result) + { + if (value.ToString().EndsWith("-0000-0000-0000-000000000000") is false) + { + // We have a proper GUID, not one converted from an integer. + result = null; + return false; + } + + result = BitConverter.ToInt32(value.ToByteArray()); + return true; + } } diff --git a/src/Umbraco.Core/Handlers/PublicAccessHandler.cs b/src/Umbraco.Core/Handlers/PublicAccessHandler.cs index d441509a8571..00911057f45f 100644 --- a/src/Umbraco.Core/Handlers/PublicAccessHandler.cs +++ b/src/Umbraco.Core/Handlers/PublicAccessHandler.cs @@ -21,16 +21,17 @@ public PublicAccessHandler(IPublicAccessService publicAccessService) => private void Handle(IEnumerable affectedEntities) { + var keyName = Constants.Entities.AdditionalDataKeys.MemberGroupPreviousName; foreach (IMemberGroup grp in affectedEntities) { // check if the name has changed - if ((grp.AdditionalData?.ContainsKey("previousName") ?? false) - && grp.AdditionalData["previousName"] != null - && grp.AdditionalData["previousName"]?.ToString().IsNullOrWhiteSpace() == false - && grp.AdditionalData["previousName"]?.ToString() != grp.Name) + if ((grp.AdditionalData?.ContainsKey(keyName) ?? false) + && grp.AdditionalData[keyName] != null + && grp.AdditionalData[keyName]?.ToString().IsNullOrWhiteSpace() == false + && grp.AdditionalData[keyName]?.ToString() != grp.Name) { _publicAccessService.RenameMemberGroupRoleRules( - grp.AdditionalData["previousName"]?.ToString(), + grp.AdditionalData[keyName]?.ToString(), grp.Name); } } diff --git a/src/Umbraco.Core/IO/PhysicalFileSystem.cs b/src/Umbraco.Core/IO/PhysicalFileSystem.cs index ede481b83344..7f43fad5f106 100644 --- a/src/Umbraco.Core/IO/PhysicalFileSystem.cs +++ b/src/Umbraco.Core/IO/PhysicalFileSystem.cs @@ -358,7 +358,7 @@ public string GetFullPath(string path) // nothing prevents us to reach the file, security-wise, yet it is outside // this filesystem's root - throw - throw new UnauthorizedAccessException($"File original: [{originalPath}] full: [{path}] is outside this filesystem's root."); + throw new UnauthorizedAccessException($"Requested path {originalPath} is outside this filesystem's root."); } /// diff --git a/src/Umbraco.Core/Models/ContentEditing/PropertyTypeBasic.cs b/src/Umbraco.Core/Models/ContentEditing/PropertyTypeBasic.cs index 4574e62cde4a..a09cb0a300c0 100644 --- a/src/Umbraco.Core/Models/ContentEditing/PropertyTypeBasic.cs +++ b/src/Umbraco.Core/Models/ContentEditing/PropertyTypeBasic.cs @@ -25,6 +25,7 @@ public class PropertyTypeBasic [Required] [RegularExpression(@"^([a-zA-Z]\w.*)$", ErrorMessage = "Invalid alias")] + [MaxLength(255, ErrorMessage = "Alias is too long")] [DataMember(Name = "alias")] public string Alias { get; set; } = null!; diff --git a/src/Umbraco.Core/Models/DeliveryApi/ApiContentRoute.cs b/src/Umbraco.Core/Models/DeliveryApi/ApiContentRoute.cs index 45fcaaba4bce..f5d56fbf2e2a 100644 --- a/src/Umbraco.Core/Models/DeliveryApi/ApiContentRoute.cs +++ b/src/Umbraco.Core/Models/DeliveryApi/ApiContentRoute.cs @@ -10,5 +10,7 @@ public ApiContentRoute(string path, ApiContentStartItem startItem) public string Path { get; } + public string? QueryString { get; set; } + public IApiContentStartItem StartItem { get; } } diff --git a/src/Umbraco.Core/Models/DeliveryApi/IApiContentRoute.cs b/src/Umbraco.Core/Models/DeliveryApi/IApiContentRoute.cs index 1cc2b36b9de9..cfc0b2984a29 100644 --- a/src/Umbraco.Core/Models/DeliveryApi/IApiContentRoute.cs +++ b/src/Umbraco.Core/Models/DeliveryApi/IApiContentRoute.cs @@ -4,5 +4,10 @@ public interface IApiContentRoute { string Path { get; } + public string? QueryString + { + get => null; set { } + } + IApiContentStartItem StartItem { get; } } diff --git a/src/Umbraco.Core/Models/MemberGroup.cs b/src/Umbraco.Core/Models/MemberGroup.cs index 5ae7a7edd224..ce13938884fd 100644 --- a/src/Umbraco.Core/Models/MemberGroup.cs +++ b/src/Umbraco.Core/Models/MemberGroup.cs @@ -35,7 +35,7 @@ public string? Name // if the name has changed, add the value to the additional data, // this is required purely for event handlers to know the previous name of the group // so we can keep the public access up to date. - AdditionalData["previousName"] = _name; + AdditionalData[Constants.Entities.AdditionalDataKeys.MemberGroupPreviousName] = _name; } SetPropertyValueAndDetectChanges(value, ref _name, nameof(Name)); diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedValueFallback.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedValueFallback.cs index 0524ee98a91f..c1601e8a5289 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedValueFallback.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedValueFallback.cs @@ -30,7 +30,7 @@ public bool TryGetValue(IPublishedProperty property, string? culture, string? se /// public bool TryGetValue(IPublishedProperty property, string? culture, string? segment, Fallback fallback, T? defaultValue, out T? value) { - _variationContextAccessor.ContextualizeVariation(property.PropertyType.Variations, ref culture, ref segment); + _variationContextAccessor.ContextualizeVariation(property.PropertyType.Variations, property.Alias, ref culture, ref segment); foreach (var f in fallback) { @@ -78,7 +78,7 @@ public bool TryGetValue(IPublishedElement content, string alias, string? cult return false; } - _variationContextAccessor.ContextualizeVariation(propertyType.Variations, ref culture, ref segment); + _variationContextAccessor.ContextualizeVariation(propertyType.Variations, alias, ref culture, ref segment); foreach (var f in fallback) { @@ -124,7 +124,7 @@ public virtual bool TryGetValue(IPublishedContent content, string alias, stri IPublishedPropertyType? propertyType = content.ContentType.GetPropertyType(alias); if (propertyType != null) { - _variationContextAccessor.ContextualizeVariation(propertyType.Variations, content.Id, ref culture, ref segment); + _variationContextAccessor.ContextualizeVariation(propertyType.Variations, content.Id, alias, ref culture, ref segment); noValueProperty = content.GetProperty(alias); } @@ -195,7 +195,7 @@ private bool TryGetValueWithAncestorsFallback(IPublishedContent? content, str { culture = null; segment = null; - _variationContextAccessor.ContextualizeVariation(propertyType.Variations, content.Id, ref culture, ref segment); + _variationContextAccessor.ContextualizeVariation(propertyType.Variations, content.Id, alias, ref culture, ref segment); } property = content?.GetProperty(alias); diff --git a/src/Umbraco.Core/Models/PublishedContent/VariationContext.cs b/src/Umbraco.Core/Models/PublishedContent/VariationContext.cs index 92326ae35955..395e0f9c203e 100644 --- a/src/Umbraco.Core/Models/PublishedContent/VariationContext.cs +++ b/src/Umbraco.Core/Models/PublishedContent/VariationContext.cs @@ -25,9 +25,15 @@ public VariationContext(string? culture = null, string? segment = null) public string Segment { get; } /// - /// Gets the segment for the content item + /// Gets the segment for the content item. /// - /// - /// + /// The content Id. public virtual string GetSegment(int contentId) => Segment; + + /// + /// Gets the segment for the content item and property alias. + /// + /// The content Id. + /// The property alias. + public virtual string GetSegment(int contentId, string propertyAlias) => Segment; } diff --git a/src/Umbraco.Core/Models/PublishedContent/VariationContextAccessorExtensions.cs b/src/Umbraco.Core/Models/PublishedContent/VariationContextAccessorExtensions.cs index e8f6e3bdc1a7..566d5e45af08 100644 --- a/src/Umbraco.Core/Models/PublishedContent/VariationContextAccessorExtensions.cs +++ b/src/Umbraco.Core/Models/PublishedContent/VariationContextAccessorExtensions.cs @@ -8,25 +8,45 @@ namespace Umbraco.Extensions; public static class VariationContextAccessorExtensions { + [Obsolete("Please use the method overload that accepts all parameters. Scheduled for removal in Umbraco 18.")] public static void ContextualizeVariation( this IVariationContextAccessor variationContextAccessor, ContentVariation variations, ref string? culture, ref string? segment) - => variationContextAccessor.ContextualizeVariation(variations, null, ref culture, ref segment); + => variationContextAccessor.ContextualizeVariation(variations, null, null, ref culture, ref segment); + public static void ContextualizeVariation( + this IVariationContextAccessor variationContextAccessor, + ContentVariation variations, + string? propertyAlias, + ref string? culture, + ref string? segment) + => variationContextAccessor.ContextualizeVariation(variations, null, propertyAlias, ref culture, ref segment); + + [Obsolete("Please use the method overload that accepts all parameters. Scheduled for removal in Umbraco 18.")] public static void ContextualizeVariation( this IVariationContextAccessor variationContextAccessor, ContentVariation variations, int contentId, ref string? culture, ref string? segment) - => variationContextAccessor.ContextualizeVariation(variations, (int?)contentId, ref culture, ref segment); + => variationContextAccessor.ContextualizeVariation(variations, (int?)contentId, null, ref culture, ref segment); + + public static void ContextualizeVariation( + this IVariationContextAccessor variationContextAccessor, + ContentVariation variations, + int contentId, + string? propertyAlias, + ref string? culture, + ref string? segment) + => variationContextAccessor.ContextualizeVariation(variations, (int?)contentId, propertyAlias, ref culture, ref segment); private static void ContextualizeVariation( this IVariationContextAccessor variationContextAccessor, ContentVariation variations, int? contentId, + string? propertyAlias, ref string? culture, ref string? segment) { @@ -37,18 +57,22 @@ private static void ContextualizeVariation( // use context values VariationContext? publishedVariationContext = variationContextAccessor?.VariationContext; - if (culture == null) - { - culture = variations.VariesByCulture() ? publishedVariationContext?.Culture : string.Empty; - } + culture ??= variations.VariesByCulture() ? publishedVariationContext?.Culture : string.Empty; if (segment == null) { if (variations.VariesBySegment()) { - segment = contentId == null - ? publishedVariationContext?.Segment - : publishedVariationContext?.GetSegment(contentId.Value); + if (contentId == null) + { + segment = publishedVariationContext?.Segment; + } + else + { + segment = propertyAlias == null ? + publishedVariationContext?.GetSegment(contentId.Value) : + publishedVariationContext?.GetSegment(contentId.Value, propertyAlias); + } } else { diff --git a/src/Umbraco.Core/Persistence/Repositories/IExternalLoginWithKeyRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IExternalLoginWithKeyRepository.cs index ec9a79530cdb..0329ceb33bc0 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IExternalLoginWithKeyRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IExternalLoginWithKeyRepository.cs @@ -3,23 +3,29 @@ namespace Umbraco.Cms.Core.Persistence.Repositories; /// -/// Repository for external logins with Guid as key, so it can be shared for members and users +/// Repository for external logins with Guid as key, so it can be shared for members and users. /// public interface IExternalLoginWithKeyRepository : IReadWriteQueryRepository, IQueryRepository { /// - /// Replaces all external login providers for the user/member key + /// Replaces all external login providers for the user/member key. /// void Save(Guid userOrMemberKey, IEnumerable logins); /// - /// Replaces all external login provider tokens for the providers specified for the user/member key + /// Replaces all external login provider tokens for the providers specified for the user/member key. /// void Save(Guid userOrMemberKey, IEnumerable tokens); /// - /// Deletes all external logins for the specified the user/member key + /// Deletes all external logins for the specified the user/member key. /// void DeleteUserLogins(Guid userOrMemberKey); + + /// + /// Deletes external logins that aren't associated with the current collection of providers. + /// + /// The names of the currently configured providers. + void DeleteUserLoginsForRemovedProviders(IEnumerable currentLoginProviders) { } } diff --git a/src/Umbraco.Core/Persistence/Repositories/IMemberRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IMemberRepository.cs index 32c04bdb4bdc..4be021c6166f 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IMemberRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IMemberRepository.cs @@ -38,4 +38,11 @@ public interface IMemberRepository : IContentRepository /// /// int GetCountByQuery(IQuery? query); + + /// + /// Saves only the properties related to login for the member, using an optimized, non-locking update. + /// + /// The member to update. + /// Used to avoid the full save of the member object after a login operation. + Task UpdateLoginPropertiesAsync(IMember member) => Task.CompletedTask; } diff --git a/src/Umbraco.Core/Persistence/Repositories/IUserRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IUserRepository.cs index 35458d6eba10..a8863f135034 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IUserRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IUserRepository.cs @@ -1,4 +1,4 @@ -using System.Linq.Expressions; +using System.Linq.Expressions; using Umbraco.Cms.Core.Models.Membership; using Umbraco.Cms.Core.Persistence.Querying; @@ -109,5 +109,26 @@ IEnumerable GetPagedResultsByQuery( void ClearLoginSession(Guid sessionId); + /// + /// Gets a page of users, ordered by Id and starting from the provided Id. + /// + /// The user Id to start retrieving users from. + /// The number of users to return. + /// A page of instances. + [Obsolete("No longer used in Umbraco. Scheduled for removal in Umbraco 18.")] IEnumerable GetNextUsers(int id, int count); + + /// + /// Gets a page of approved users, ordered by Id and starting from the provided Id. + /// + /// The user Id to start retrieving users from. + /// The number of users to return. + /// A page of instances. + IEnumerable GetNextApprovedUsers(int id, int count) => Enumerable.Empty(); + + /// + /// Invalidates sessions for users that aren't associated with the current collection of providers. + /// + /// The keys for the currently configured providers. + void InvalidateSessionsForRemovedProviders(IEnumerable currentProviderKeys) { } } diff --git a/src/Umbraco.Core/PublishedCache/IPublishedSnapshotService.cs b/src/Umbraco.Core/PublishedCache/IPublishedSnapshotService.cs index 8e661aa75805..02a419f8d585 100644 --- a/src/Umbraco.Core/PublishedCache/IPublishedSnapshotService.cs +++ b/src/Umbraco.Core/PublishedCache/IPublishedSnapshotService.cs @@ -31,6 +31,12 @@ public interface IPublishedSnapshotService : IDisposable /// IPublishedSnapshot CreatePublishedSnapshot(string? previewToken); + /// + /// Indicates if the database cache is in the process of being rebuilt. + /// + /// + bool IsRebuilding() => false; + /// /// Rebuilds internal database caches (but does not reload). /// @@ -61,6 +67,38 @@ void Rebuild( IReadOnlyCollection? mediaTypeIds = null, IReadOnlyCollection? memberTypeIds = null); + /// + /// Rebuilds internal database caches (but does not reload). + /// + /// + /// If not null will process content for the matching content types, if empty will process all + /// content + /// + /// + /// If not null will process content for the matching media types, if empty will process all + /// media + /// + /// + /// If not null will process content for the matching members types, if empty will process all + /// members + /// + /// Flag indicating whether to use a background thread for the operation and immediately return to the caller. + /// + /// + /// Forces the snapshot service to rebuild its internal database caches. For instance, some caches + /// may rely on a database table to store pre-serialized version of documents. + /// + /// + /// This does *not* reload the caches. Caches need to be reloaded, for instance via + /// RefreshAllPublishedSnapshot method. + /// + /// + void Rebuild( + bool useBackgroundThread, + IReadOnlyCollection? contentTypeIds = null, + IReadOnlyCollection? mediaTypeIds = null, + IReadOnlyCollection? memberTypeIds = null) => Rebuild(contentTypeIds, mediaTypeIds, memberTypeIds); + /// /// Rebuilds all internal database caches (but does not reload). @@ -77,6 +115,22 @@ void Rebuild( /// void RebuildAll() => Rebuild(Array.Empty(), Array.Empty(), Array.Empty()); + /// + /// Rebuilds all internal database caches (but does not reload). + /// + /// Flag indicating whether to use a background thread for the operation and immediately return to the caller. + /// + /// + /// Forces the snapshot service to rebuild its internal database caches. For instance, some caches + /// may rely on a database table to store pre-serialized version of documents. + /// + /// + /// This does *not* reload the caches. Caches need to be reloaded, for instance via + /// RefreshAllPublishedSnapshot method. + /// + /// + void RebuildAll(bool useBackgroundThread) => Rebuild(useBackgroundThread, Array.Empty(), Array.Empty(), Array.Empty()); + /* An IPublishedCachesService implementation can rely on transaction-level events to update * its internal, database-level data, as these events are purely internal. However, it cannot * rely on cache refreshers CacheUpdated events to update itself, as these events are external diff --git a/src/Umbraco.Core/Scoping/CoreScope.cs b/src/Umbraco.Core/Scoping/CoreScope.cs index 7fe6c400fbcf..e158641af437 100644 --- a/src/Umbraco.Core/Scoping/CoreScope.cs +++ b/src/Umbraco.Core/Scoping/CoreScope.cs @@ -250,6 +250,8 @@ protected void SetParentScope(ICoreScope coreScope) _parentScope = coreScope; } + protected bool HasParentScope => _parentScope is not null; + protected void HandleScopedNotifications() => _notificationPublisher?.ScopeExit(Completed.HasValue && Completed.Value); private void EnsureNotDisposed() diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs index 45176629a23a..86c06930bb3b 100644 --- a/src/Umbraco.Core/Services/ContentService.cs +++ b/src/Umbraco.Core/Services/ContentService.cs @@ -3627,6 +3627,7 @@ public void DeleteBlueprint(IContent content, int userId = Constants.Security.Su private static readonly string?[] ArrayOfOneNullString = { null }; + /// public IContent CreateContentFromBlueprint(IContent blueprint, string name, int userId = Constants.Security.SuperUserId) { if (blueprint == null) diff --git a/src/Umbraco.Core/Services/DomainService.cs b/src/Umbraco.Core/Services/DomainService.cs index 202d51d64874..03ebac92c5a8 100644 --- a/src/Umbraco.Core/Services/DomainService.cs +++ b/src/Umbraco.Core/Services/DomainService.cs @@ -108,7 +108,7 @@ public IEnumerable GetAssignedDomains(int contentId, bool includeWildca EventMessages eventMessages = EventMessagesFactory.Get(); IDomain[] domains = items.ToArray(); - if (domains.Length == 0) + if (domains.Length == 0 || AreDomainsAlreadySorted(domains)) { return OperationResult.Attempt.NoOperation(eventMessages); } @@ -144,4 +144,18 @@ public IEnumerable GetAssignedDomains(int contentId, bool includeWildca return OperationResult.Attempt.Succeed(eventMessages); } + + private static bool AreDomainsAlreadySorted(IDomain[] domains) + { + // Check if the domains are already sorted by comparing the current sort order with what we'll set to be the new sort order. + for (int i = 0; i < domains.Length; i++) + { + if (domains[i].SortOrder != i) + { + return false; + } + } + + return true; + } } diff --git a/src/Umbraco.Core/Services/ExternalLoginService.cs b/src/Umbraco.Core/Services/ExternalLoginService.cs index be49927b3689..762ec4cacb6a 100644 --- a/src/Umbraco.Core/Services/ExternalLoginService.cs +++ b/src/Umbraco.Core/Services/ExternalLoginService.cs @@ -80,4 +80,14 @@ public void DeleteUserLogins(Guid userOrMemberKey) scope.Complete(); } } + + /// + public void DeleteUserLoginsForRemovedProviders(IEnumerable currentLoginProviders) + { + using (ICoreScope scope = ScopeProvider.CreateCoreScope()) + { + _externalLoginRepository.DeleteUserLoginsForRemovedProviders(currentLoginProviders); + scope.Complete(); + } + } } diff --git a/src/Umbraco.Core/Services/IContentService.cs b/src/Umbraco.Core/Services/IContentService.cs index c7b33589b3e0..8b96cc81df9f 100644 --- a/src/Umbraco.Core/Services/IContentService.cs +++ b/src/Umbraco.Core/Services/IContentService.cs @@ -56,6 +56,9 @@ public interface IContentService : IContentServiceBase /// /// Creates a new content item from a blueprint. /// + /// Warning: If you intend to save the resulting IContent as a content node, you must trigger a + /// notification to ensure that the block ids are regenerated. + /// Failing to do so could lead to caching issues. IContent CreateContentFromBlueprint(IContent blueprint, string name, int userId = Constants.Security.SuperUserId); /// diff --git a/src/Umbraco.Core/Services/IExternalLoginWithKeyService.cs b/src/Umbraco.Core/Services/IExternalLoginWithKeyService.cs index 42f0708aaa73..8ce5bc3fa6b7 100644 --- a/src/Umbraco.Core/Services/IExternalLoginWithKeyService.cs +++ b/src/Umbraco.Core/Services/IExternalLoginWithKeyService.cs @@ -5,47 +5,53 @@ namespace Umbraco.Cms.Core.Services; public interface IExternalLoginWithKeyService : IService { /// - /// Returns all user logins assigned + /// Returns all user logins assigned. /// IEnumerable GetExternalLogins(Guid userOrMemberKey); /// - /// Returns all user login tokens assigned + /// Returns all user login tokens assigned. /// IEnumerable GetExternalLoginTokens(Guid userOrMemberKey); /// /// Returns all logins matching the login info - generally there should only be one but in some cases - /// there might be more than one depending on if an administrator has been editing/removing members + /// there might be more than one depending on if an administrator has been editing/removing members. /// IEnumerable Find(string loginProvider, string providerKey); /// - /// Saves the external logins associated with the user + /// Saves the external logins associated with the user. /// /// - /// The user or member key associated with the logins + /// The user or member key associated with the logins. /// /// /// - /// This will replace all external login provider information for the user + /// This will replace all external login provider information for the user. /// void Save(Guid userOrMemberKey, IEnumerable logins); /// - /// Saves the external login tokens associated with the user + /// Saves the external login tokens associated with the user. /// /// - /// The user or member key associated with the logins + /// The user or member key associated with the logins. /// /// /// - /// This will replace all external login tokens for the user + /// This will replace all external login tokens for the user. /// void Save(Guid userOrMemberKey, IEnumerable tokens); /// - /// Deletes all user logins - normally used when a member is deleted + /// Deletes all user logins - normally used when a member is deleted. /// void DeleteUserLogins(Guid userOrMemberKey); + + /// + /// Deletes external logins that aren't associated with the current collection of providers. + /// + /// The names of the currently configured providers. + void DeleteUserLoginsForRemovedProviders(IEnumerable currentLoginProviders) { } } diff --git a/src/Umbraco.Core/Services/IMemberService.cs b/src/Umbraco.Core/Services/IMemberService.cs index 7d78a979c836..f0fed837204f 100644 --- a/src/Umbraco.Core/Services/IMemberService.cs +++ b/src/Umbraco.Core/Services/IMemberService.cs @@ -343,4 +343,11 @@ IEnumerable FindMembersByDisplayName( /// /// IEnumerable? GetMembersByPropertyValue(string propertyTypeAlias, DateTime value, ValuePropertyMatchType matchType = ValuePropertyMatchType.Exact); + + /// + /// Saves only the properties related to login for the member, using an optimized, non-locking update. + /// + /// The member to update. + /// Used to avoid the full save of the member object after a login operation. + Task UpdateLoginPropertiesAsync(IMember member) => Task.CompletedTask; } diff --git a/src/Umbraco.Core/Services/IUserService.cs b/src/Umbraco.Core/Services/IUserService.cs index 40a3fbd89978..df0965fcde80 100644 --- a/src/Umbraco.Core/Services/IUserService.cs +++ b/src/Umbraco.Core/Services/IUserService.cs @@ -231,8 +231,29 @@ IEnumerable GetAll( /// IEnumerable GetAllNotInGroup(int groupId); + /// + /// Gets a page of users, ordered by Id and starting from the provided Id. + /// + /// The user Id to start retrieving users from. + /// The number of users to return. + /// A page of instances. + [Obsolete("No longer used in Umbraco. Scheduled for removal in Umbraco 18.")] IEnumerable GetNextUsers(int id, int count); + /// + /// Gets a page of approved users, ordered by Id and starting from the provided Id. + /// + /// The user Id to start retrieving users from. + /// The number of users to return. + /// A page of instances. + IEnumerable GetNextApprovedUsers(int id, int count) => Enumerable.Empty(); + + /// + /// Invalidates sessions for users that aren't associated with the current collection of providers. + /// + /// The keys for the currently configured providers. + void InvalidateSessionsForRemovedProviders(IEnumerable currentLoginProviders) { } + #region User groups /// diff --git a/src/Umbraco.Core/Services/MediaService.cs b/src/Umbraco.Core/Services/MediaService.cs index 8738c6c67e4d..744020fbc0c2 100644 --- a/src/Umbraco.Core/Services/MediaService.cs +++ b/src/Umbraco.Core/Services/MediaService.cs @@ -418,7 +418,7 @@ public IEnumerable GetPagedOfType(int contentTypeId, long pageIndex, int } using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true); - scope.ReadLock(Constants.Locks.ContentTree); + scope.ReadLock(Constants.Locks.MediaTree); return _mediaRepository.GetPage(Query()?.Where(x => x.ContentTypeId == contentTypeId), pageIndex, pageSize, out totalRecords, filter, ordering); } @@ -441,7 +441,7 @@ public IEnumerable GetPagedOfTypes(int[] contentTypeIds, long pageIndex, } using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true); - scope.ReadLock(Constants.Locks.ContentTree); + scope.ReadLock(Constants.Locks.MediaTree); return _mediaRepository.GetPage( Query()?.Where(x => contentTypeIds.Contains(x.ContentTypeId)), pageIndex, pageSize, out totalRecords, filter, ordering); } diff --git a/src/Umbraco.Core/Services/MemberService.cs b/src/Umbraco.Core/Services/MemberService.cs index b405519616bc..e9381db3c33f 100644 --- a/src/Umbraco.Core/Services/MemberService.cs +++ b/src/Umbraco.Core/Services/MemberService.cs @@ -770,16 +770,27 @@ public void Save(IMember member, PublishNotificationSaveOptions publishNotificat throw new ArgumentException("Cannot save member with empty name."); } + var previousUsername = _memberRepository.Get(member.Id)?.Username; + scope.WriteLock(Constants.Locks.MemberTree); _memberRepository.Save(member); if (publishNotificationSaveOptions.HasFlag(PublishNotificationSaveOptions.Saved)) { - scope.Notifications.Publish( - savingNotification is null + // If the user name has changed, populate the previous user name in the additional data, so the cache refreshers + // have it available to clear the cache by the old name as well as the new. + if (string.IsNullOrWhiteSpace(previousUsername) is false && + string.Equals(previousUsername, member.Username, StringComparison.OrdinalIgnoreCase) is false) + { + member.AdditionalData![Constants.Entities.AdditionalDataKeys.MemberPreviousUserName] = previousUsername; + } + + MemberSavedNotification memberSavedNotification = savingNotification is null ? new MemberSavedNotification(member, evtMsgs) - : new MemberSavedNotification(member, evtMsgs).WithStateFrom(savingNotification)); + : new MemberSavedNotification(member, evtMsgs).WithStateFrom(savingNotification); + + scope.Notifications.Publish(memberSavedNotification); } Audit(AuditType.Save, 0, member.Id); @@ -820,6 +831,48 @@ public void Save(IEnumerable members) scope.Complete(); } + /// + /// + /// + /// Note that in this optimized member save operation for use in the login process, where we only handle login related + /// properties, we aren't taking any locks. If we were updating "content" properties, that could have relations between each + /// other, we should following what we do for documents and lock. + /// But here we are just updating these system fields, and it's fine if they work in a "last one wins" fashion without locking. + /// + /// + /// Note also that we aren't calling "Audit" here (as well as to optimize performance, this is deliberate, because this is not + /// a full save operation on the member that we'd want to audit who made the changes via the backoffice or API; rather it's + /// just the member logging in as themselves). + /// + /// + /// We are though publishing notifications, to maintain backwards compatibility for any solutions using these for + /// processing following a member login. + /// + /// + /// These notification handlers will ensure that the records to umbracoLog are also added in the same way as they + /// are for a full save operation. + /// + /// + public async Task UpdateLoginPropertiesAsync(IMember member) + { + EventMessages evtMsgs = EventMessagesFactory.Get(); + + using ICoreScope scope = ScopeProvider.CreateCoreScope(); + var savingNotification = new MemberSavingNotification(member, evtMsgs); + savingNotification.State.Add("LoginPropertiesOnly", true); + if (scope.Notifications.PublishCancelable(savingNotification)) + { + scope.Complete(); + return; + } + + await _memberRepository.UpdateLoginPropertiesAsync(member); + + scope.Notifications.Publish(new MemberSavedNotification(member, evtMsgs).WithStateFrom(savingNotification)); + + scope.Complete(); + } + #endregion #region Delete diff --git a/src/Umbraco.Core/Services/NotificationService.cs b/src/Umbraco.Core/Services/NotificationService.cs index 336435d97ad5..4c7edead6757 100644 --- a/src/Umbraco.Core/Services/NotificationService.cs +++ b/src/Umbraco.Core/Services/NotificationService.cs @@ -96,7 +96,7 @@ public void SendNotifications( // see notes above var id = Constants.Security.SuperUserId; - const int pagesz = 400; // load batches of 400 users + const int UserBatchSize = 400; // load batches of 400 users do { var notifications = GetUsersNotifications(new List(), action, Enumerable.Empty(), Constants.ObjectTypes.Document)?.ToList(); @@ -106,10 +106,10 @@ public void SendNotifications( } // users are returned ordered by id, notifications are returned ordered by user id - var users = _userService.GetNextUsers(id, pagesz).Where(x => x.IsApproved).ToList(); - foreach (IUser user in users) + var approvedUsers = _userService.GetNextApprovedUsers(id, UserBatchSize).ToList(); + foreach (IUser approvedUser in approvedUsers) { - Notification[] userNotifications = notifications.Where(n => n.UserId == user.Id).ToArray(); + Notification[] userNotifications = notifications.Where(n => n.UserId == approvedUser.Id).ToArray(); foreach (Notification notification in userNotifications) { // notifications are inherited down the tree - find the topmost entity @@ -130,14 +130,14 @@ public void SendNotifications( } // queue notification - NotificationRequest req = CreateNotificationRequest(operatingUser, user, entityForNotification, prevVersionDictionary[entityForNotification.Id], actionName, siteUri, createSubject, createBody); + NotificationRequest req = CreateNotificationRequest(operatingUser, approvedUser, entityForNotification, prevVersionDictionary[entityForNotification.Id], actionName, siteUri, createSubject, createBody); Enqueue(req); break; } } // load more users if any - id = users.Count == pagesz ? users.Last().Id + 1 : -1; + id = approvedUsers.Count == UserBatchSize ? approvedUsers.Last().Id + 1 : -1; } while (id > 0); } @@ -385,48 +385,7 @@ private NotificationRequest CreateNotificationRequest( // build summary var summary = new StringBuilder(); - if (content.ContentType.VariesByNothing()) - { - if (!_contentSettings.Notifications.DisableHtmlEmail) - { - // create the HTML summary for invariant content - - // list all of the property values like we used to - summary.Append(""); - foreach (IProperty p in content.Properties) - { - // TODO: doesn't take into account variants - var newText = p.GetValue() != null ? p.GetValue()?.ToString() : string.Empty; - var oldText = newText; - - // check if something was changed and display the changes otherwise display the fields - if (oldDoc?.Properties.Contains(p.PropertyType.Alias) ?? false) - { - IProperty? oldProperty = oldDoc.Properties[p.PropertyType.Alias]; - oldText = oldProperty?.GetValue() != null ? oldProperty.GetValue()?.ToString() : string.Empty; - - // replace HTML with char equivalent - ReplaceHtmlSymbols(ref oldText); - ReplaceHtmlSymbols(ref newText); - } - - // show the values - summary.Append(""); - summary.Append( - ""); - summary.Append(""); - summary.Append(""); - } - - summary.Append("
    "); - summary.Append(p.PropertyType.Name); - summary.Append(""); - summary.Append(newText); - summary.Append("
    "); - } - } - else if (content.ContentType.VariesByCulture()) - { + if (content.ContentType.VariesByCulture()) { // it's variant, so detect what cultures have changed if (!_contentSettings.Notifications.DisableHtmlEmail) { @@ -465,8 +424,43 @@ private NotificationRequest CreateNotificationRequest( } else { - // not supported yet... - throw new NotSupportedException(); + if (!_contentSettings.Notifications.DisableHtmlEmail) + { + // create the HTML summary for invariant content + + // list all of the property values like we used to + summary.Append(""); + foreach (IProperty p in content.Properties) + { + // TODO: doesn't take into account variants + var newText = p.GetValue() != null ? p.GetValue()?.ToString() : string.Empty; + var oldText = newText; + + // check if something was changed and display the changes otherwise display the fields + if (oldDoc?.Properties.Contains(p.PropertyType.Alias) ?? false) + { + IProperty? oldProperty = oldDoc.Properties[p.PropertyType.Alias]; + oldText = oldProperty?.GetValue() != null ? oldProperty.GetValue()?.ToString() : string.Empty; + + // replace HTML with char equivalent + ReplaceHtmlSymbols(ref oldText); + ReplaceHtmlSymbols(ref newText); + } + + // show the values + summary.Append(""); + summary.Append( + ""); + summary.Append(""); + summary.Append(""); + } + + summary.Append("
    "); + summary.Append(p.PropertyType.Name); + summary.Append(""); + summary.Append(newText); + summary.Append("
    "); + } } var protocol = _globalSettings.UseHttps ? "https" : "http"; diff --git a/src/Umbraco.Core/Services/UserService.cs b/src/Umbraco.Core/Services/UserService.cs index e0f65cdd5c93..8723917742c5 100644 --- a/src/Umbraco.Core/Services/UserService.cs +++ b/src/Umbraco.Core/Services/UserService.cs @@ -712,6 +712,7 @@ public IEnumerable GetAll(long pageIndex, int pageSize, out long totalRec } } + /// public IEnumerable GetNextUsers(int id, int count) { using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) @@ -720,6 +721,25 @@ public IEnumerable GetNextUsers(int id, int count) } } + /// + public IEnumerable GetNextApprovedUsers(int id, int count) + { + using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) + { + return _userRepository.GetNextApprovedUsers(id, count); + } + } + + /// + public void InvalidateSessionsForRemovedProviders(IEnumerable currentLoginProviders) + { + using (ICoreScope scope = ScopeProvider.CreateCoreScope()) + { + _userRepository.InvalidateSessionsForRemovedProviders(currentLoginProviders); + scope.Complete(); + } + } + /// /// Gets a list of objects associated with a given group /// diff --git a/src/Umbraco.Core/TimedScope.cs b/src/Umbraco.Core/TimedScope.cs new file mode 100644 index 000000000000..6cd009f3035c --- /dev/null +++ b/src/Umbraco.Core/TimedScope.cs @@ -0,0 +1,166 @@ +namespace Umbraco.Cms.Core; + +/// +/// Makes a code block timed (take at least a certain amount of time). This class cannot be inherited. +/// +public sealed class TimedScope : IDisposable, IAsyncDisposable +{ + private readonly TimeSpan _duration; + private readonly TimeProvider _timeProvider; + private readonly CancellationTokenSource _cancellationTokenSource; + private readonly long _startingTimestamp; + + /// + /// Gets the elapsed time. + /// + /// + /// The elapsed time. + /// + public TimeSpan Elapsed + => _timeProvider.GetElapsedTime(_startingTimestamp); + + /// + /// Gets the remaining time. + /// + /// + /// The remaining time. + /// + public TimeSpan Remaining + => TryGetRemaining(out TimeSpan remaining) ? remaining : TimeSpan.Zero; + + /// + /// Initializes a new instance of the class. + /// + /// The number of milliseconds the scope should at least take. + public TimedScope(long millisecondsDuration) + : this(TimeSpan.FromMilliseconds(millisecondsDuration)) + { } + + /// + /// Initializes a new instance of the class. + /// + /// The number of milliseconds the scope should at least take. + /// The cancellation token. + public TimedScope(long millisecondsDuration, CancellationToken cancellationToken) + : this(TimeSpan.FromMilliseconds(millisecondsDuration), cancellationToken) + { } + + /// + /// Initializes a new instance of the class. + /// + /// The number of milliseconds the scope should at least take. + /// The time provider. + public TimedScope(long millisecondsDuration, TimeProvider timeProvider) + : this(TimeSpan.FromMilliseconds(millisecondsDuration), timeProvider) + { } + + /// + /// Initializes a new instance of the class. + /// + /// The number of milliseconds the scope should at least take. + /// The time provider. + /// The cancellation token. + public TimedScope(long millisecondsDuration, TimeProvider timeProvider, CancellationToken cancellationToken) + : this(TimeSpan.FromMilliseconds(millisecondsDuration), timeProvider, cancellationToken) + { } + + /// + /// Initializes a new instance of the class. + /// + /// The duration the scope should at least take. + public TimedScope(TimeSpan duration) + : this(duration, TimeProvider.System) + { } + + /// + /// Initializes a new instance of the class. + /// + /// The duration the scope should at least take. + /// The time provider. + public TimedScope(TimeSpan duration, TimeProvider timeProvider) + : this(duration, timeProvider, new CancellationTokenSource()) + { } + + /// + /// Initializes a new instance of the class. + /// + /// The duration the scope should at least take. + /// The cancellation token. + public TimedScope(TimeSpan duration, CancellationToken cancellationToken) + : this(duration, TimeProvider.System, cancellationToken) + { } + + /// + /// Initializes a new instance of the class. + /// + /// The duration the scope should at least take. + /// The time provider. + /// The cancellation token. + public TimedScope(TimeSpan duration, TimeProvider timeProvider, CancellationToken cancellationToken) + : this(duration, timeProvider, CancellationTokenSource.CreateLinkedTokenSource(cancellationToken)) + { } + + private TimedScope(TimeSpan duration, TimeProvider timeProvider, CancellationTokenSource cancellationTokenSource) + { + _duration = duration; + _timeProvider = timeProvider; + _cancellationTokenSource = cancellationTokenSource; + _startingTimestamp = timeProvider.GetTimestamp(); + } + + /// + /// Cancels the timed scope. + /// + public void Cancel() + => _cancellationTokenSource.Cancel(); + + /// + /// Cancels the timed scope asynchronously. + /// + public async Task CancelAsync() + => await _cancellationTokenSource.CancelAsync().ConfigureAwait(false); + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + /// + /// This will block using until the remaining time has elapsed, if not cancelled. + /// + public void Dispose() + { + if (_cancellationTokenSource.IsCancellationRequested is false && + TryGetRemaining(out TimeSpan remaining)) + { + Thread.Sleep(remaining); + } + + _cancellationTokenSource.Dispose(); + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources asynchronously. + /// + /// + /// A task that represents the asynchronous dispose operation. + /// + /// + /// This will delay using until the remaining time has elapsed, if not cancelled. + /// + public async ValueTask DisposeAsync() + { + if (_cancellationTokenSource.IsCancellationRequested is false && + TryGetRemaining(out TimeSpan remaining)) + { + await Task.Delay(remaining, _timeProvider, _cancellationTokenSource.Token).ConfigureAwait(false); + } + + _cancellationTokenSource.Dispose(); + } + + private bool TryGetRemaining(out TimeSpan remaining) + { + remaining = _duration.Subtract(Elapsed); + + return remaining > TimeSpan.Zero; + } +} diff --git a/src/Umbraco.Examine.Lucene/BackOfficeExamineSearcher.cs b/src/Umbraco.Examine.Lucene/BackOfficeExamineSearcher.cs index e843d7954baf..4c5d28503283 100644 --- a/src/Umbraco.Examine.Lucene/BackOfficeExamineSearcher.cs +++ b/src/Umbraco.Examine.Lucene/BackOfficeExamineSearcher.cs @@ -28,9 +28,7 @@ public class BackOfficeExamineSearcher : IBackOfficeExamineSearcher private readonly IEntityService _entityService; private readonly IExamineManager _examineManager; private readonly ILocalizationService _languageService; - private readonly IPublishedUrlProvider _publishedUrlProvider; private readonly IUmbracoTreeSearcherFields _treeSearcherFields; - private readonly IUmbracoMapper _umbracoMapper; public BackOfficeExamineSearcher( IExamineManager examineManager, @@ -48,8 +46,6 @@ public BackOfficeExamineSearcher( _entityService = entityService; _treeSearcherFields = treeSearcherFields; _appCaches = appCaches; - _umbracoMapper = umbracoMapper; - _publishedUrlProvider = publishedUrlProvider; } public IEnumerable Search( @@ -82,8 +78,6 @@ public IEnumerable Search( query = "\"" + g + "\""; } - IUser? currentUser = _backOfficeSecurityAccessor?.BackOfficeSecurity?.CurrentUser; - switch (entityType) { case UmbracoEntityTypes.Member: @@ -96,7 +90,7 @@ public IEnumerable Search( } if (searchFrom != null && searchFrom != Constants.Conventions.MemberTypes.AllMembersListId && - searchFrom.Trim() != "-1") + searchFrom.Trim() != Constants.System.RootString) { sb.Append("+__NodeTypeAlias:"); sb.Append(searchFrom); @@ -112,10 +106,13 @@ public IEnumerable Search( fieldsToLoad.Add(field); } - var allMediaStartNodes = currentUser != null - ? currentUser.CalculateMediaStartNodeIds(_entityService, _appCaches) - : Array.Empty(); - AppendPath(sb, UmbracoObjectTypes.Media, allMediaStartNodes, searchFrom, ignoreUserStartNodes, _entityService); + AppendPath(sb, UmbracoObjectTypes.Media, searchFrom, ignoreUserStartNodes, out var abortMediaQuery); + if (abortMediaQuery) + { + totalFound = 0; + return []; + } + break; case UmbracoEntityTypes.Document: type = "content"; @@ -125,10 +122,13 @@ public IEnumerable Search( fieldsToLoad.Add(field); } - var allContentStartNodes = currentUser != null - ? currentUser.CalculateContentStartNodeIds(_entityService, _appCaches) - : Array.Empty(); - AppendPath(sb, UmbracoObjectTypes.Document, allContentStartNodes, searchFrom, ignoreUserStartNodes, _entityService); + AppendPath(sb, UmbracoObjectTypes.Document, searchFrom, ignoreUserStartNodes, out var abortContentQuery); + if (abortContentQuery) + { + totalFound = 0; + return []; + } + break; default: throw new NotSupportedException("The " + typeof(BackOfficeExamineSearcher) + @@ -344,67 +344,89 @@ private void AppendNodeNameWithWildcards(StringBuilder sb, string[] querywords, } } - private void AppendPath(StringBuilder sb, UmbracoObjectTypes objectType, int[]? startNodeIds, string? searchFrom, bool ignoreUserStartNodes, IEntityService entityService) + private void AppendPath(StringBuilder sb, UmbracoObjectTypes objectType, string? searchFrom, bool ignoreUserStartNodes, out bool abortQuery) { - if (sb == null) + ArgumentNullException.ThrowIfNull(sb); + + abortQuery = false; + + if (searchFrom is Constants.System.RootString) { - throw new ArgumentNullException(nameof(sb)); + searchFrom = null; } - if (entityService == null) + var userStartNodes = ignoreUserStartNodes ? [Constants.System.Root] : GetUserStartNodes(objectType); + if (searchFrom is null && userStartNodes.Contains(Constants.System.Root)) { - throw new ArgumentNullException(nameof(entityService)); + // If we have no searchFrom and the user either has access to the root node or we are ignoring user + // start nodes, we don't need to filter by path. + return; } - UdiParser.TryParse(searchFrom, true, out Udi? udi); - searchFrom = udi == null ? searchFrom : entityService.GetId(udi).Result.ToString(); - - TreeEntityPath? entityPath = - int.TryParse(searchFrom, NumberStyles.Integer, CultureInfo.InvariantCulture, out var searchFromId) && - searchFromId > 0 - ? entityService.GetAllPaths(objectType, searchFromId).FirstOrDefault() - : null; - if (entityPath != null) + string[] pathsToFilter; + if (searchFrom is null) { - // find... only what's underneath - sb.Append("+__Path:"); - AppendPath(sb, entityPath.Path, false); - sb.Append(" "); + // If we don't want to filter by a specific entity, we can simply use the user start nodes. + pathsToFilter = GetEntityPaths(objectType, userStartNodes); } - else if (startNodeIds?.Length == 0) + else { - // make sure we don't find anything - sb.Append("+__Path:none "); + TreeEntityPath? searchFromPath = GetEntityPath(searchFrom, objectType); + if (searchFromPath is null) + { + // If the searchFrom cannot be found, return no results. + // This is to prevent showing entities outside the intended filter. + abortQuery = true; + return; + } + + var userStartNodePaths = GetEntityPaths(objectType, userStartNodes); + + // If the user has access to the entity, we can simply filter by the entity path. + if (userStartNodePaths.Any(userStartNodePath => StartsWithPath(searchFromPath.Path, userStartNodePath))) + { + sb.Append("+__Path:"); + AppendPath(sb, searchFromPath.Path, false); + sb.Append(' '); + return; + } + + // If the user does not have access to the entity, let's filter the paths by the ones that start with the + // entity path (are descendants of the entity). + pathsToFilter = userStartNodePaths.Where(ep => StartsWithPath(ep, searchFromPath.Path)).ToArray(); } - else if (startNodeIds?.Contains(-1) == false && ignoreUserStartNodes == false) // -1 = no restriction + + // If we have no paths left, no need to perform the query at all, just return no results. + if (pathsToFilter.Length == 0) { - IEnumerable entityPaths = entityService.GetAllPaths(objectType, startNodeIds); + abortQuery = true; + return; + } - // for each start node, find the start node, and what's underneath - // +__Path:(-1*,1234 -1*,1234,* -1*,5678 -1*,5678,* ...) - sb.Append("+__Path:("); - var first = true; - foreach (TreeEntityPath ep in entityPaths) + // For each start node, find the start node, and what's underneath + // +__Path:(-1*,1234 -1*,1234,* -1*,5678 -1*,5678,* ...) + sb.Append("+__Path:("); + var first = true; + foreach (string pathToFilter in pathsToFilter) + { + if (first) { - if (first) - { - first = false; - } - else - { - sb.Append(" "); - } - - AppendPath(sb, ep.Path, true); + first = false; + } + else + { + sb.Append(' '); } - sb.Append(") "); + AppendPath(sb, pathToFilter, true); } + + sb.Append(") "); } - private void AppendPath(StringBuilder sb, string path, bool includeThisNode) + private static void AppendPath(StringBuilder sb, string path, bool includeThisNode) { - path = path.Replace("-", "\\-").Replace(",", "\\,"); + path = path.Replace("-", "\\-"); if (includeThisNode) { sb.Append(path); @@ -412,6 +434,68 @@ private void AppendPath(StringBuilder sb, string path, bool includeThisNode) } sb.Append(path); - sb.Append("\\,*"); + sb.Append(",*"); + } + + private static bool StartsWithPath(string path1, string path2) + { + if (path1.StartsWith(path2) == false) + { + return false; + } + + return path1.Length == path2.Length || path1[path2.Length] == ','; + } + + private int[] GetUserStartNodes(UmbracoObjectTypes objectType) + { + IUser? currentUser = _backOfficeSecurityAccessor.BackOfficeSecurity?.CurrentUser; + if (currentUser is null) + { + return []; + } + + var startNodes = objectType switch + { + UmbracoObjectTypes.Document => currentUser.CalculateContentStartNodeIds(_entityService, _appCaches), + UmbracoObjectTypes.Media => currentUser.CalculateMediaStartNodeIds(_entityService, _appCaches), + _ => throw new NotSupportedException($"The object type {objectType} is not supported for start nodes."), + }; + + return startNodes ?? [Constants.System.Root]; // If no start nodes are defined, we assume the user has access to the root node (-1). + } + + private string[] GetEntityPaths(UmbracoObjectTypes objectType, int[] entityIds) => + entityIds switch + { + [] => [], + _ when entityIds.Contains(Constants.System.Root) => [Constants.System.RootString], + _ => _entityService.GetAllPaths(objectType, entityIds).Select(x => x.Path).ToArray(), + }; + + private TreeEntityPath? GetEntityPath(string? searchFrom, UmbracoObjectTypes objectType) + { + if (searchFrom is null) + { + return null; + } + + Guid? entityKey = null; + if (Guid.TryParse(searchFrom, out Guid entityGuid)) + { + entityKey = entityGuid; + } // fallback to Udi for legacy reasons as the calling methods take string? + else if (UdiParser.TryParse(searchFrom, true, out Udi? udi) && udi is GuidUdi guidUdi) + { + entityKey = guidUdi.Guid; + } + else if (int.TryParse(searchFrom, NumberStyles.Integer, CultureInfo.InvariantCulture, out var entityId) + && entityId > 0 + && _entityService.GetKey(entityId, objectType) is { Success: true } attempt) + { + entityKey = attempt.Result; + } + + return entityKey is null ? null : _entityService.GetAllPaths(objectType, entityKey.Value).FirstOrDefault(); } } diff --git a/src/Umbraco.Infrastructure/Cache/DefaultRepositoryCachePolicy.cs b/src/Umbraco.Infrastructure/Cache/DefaultRepositoryCachePolicy.cs index 9494ed2eea58..0ac9f89b7924 100644 --- a/src/Umbraco.Infrastructure/Cache/DefaultRepositoryCachePolicy.cs +++ b/src/Umbraco.Infrastructure/Cache/DefaultRepositoryCachePolicy.cs @@ -24,8 +24,6 @@ public class DefaultRepositoryCachePolicy : RepositoryCachePolicyB private static readonly TEntity[] _emptyEntities = new TEntity[0]; // const private readonly RepositoryCachePolicyOptions _options; - private const string NullRepresentationInCache = "*NULL*"; - public DefaultRepositoryCachePolicy(IAppPolicyCache cache, IScopeAccessor scopeAccessor, RepositoryCachePolicyOptions options) : base(cache, scopeAccessor) => _options = options ?? throw new ArgumentNullException(nameof(options)); @@ -139,10 +137,8 @@ public override void Delete(TEntity entity, Action persistDeleted) return fromCache; } - // Because TEntity can never be a string, we will never be in a position where the proxy value collides withs a real value. - // Therefore this point can only be reached if there is a proxy null value => becomes null when cast to TEntity above OR the item simply does not exist. // If we've cached a "null" value, return null. - if (_options.CacheNullValues && Cache.GetCacheItem(cacheKey) == NullRepresentationInCache) + if (_options.CacheNullValues && Cache.GetCacheItem(cacheKey) == Constants.Cache.NullRepresentationInCache) { return null; } @@ -273,7 +269,7 @@ protected virtual void InsertNull(string cacheKey) // a value that does exist but isn't yet cached, or a value that has been explicitly cached with a null value. // Both would return null when we retrieve from the cache and we couldn't distinguish between the two. // So we cache a special value that represents null, and then we can check for that value when we retrieve from the cache. - Cache.Insert(cacheKey, () => NullRepresentationInCache, TimeSpan.FromMinutes(5), true); + Cache.Insert(cacheKey, () => Constants.Cache.NullRepresentationInCache, TimeSpan.FromMinutes(5), true); } protected virtual void InsertEntities(TId[]? ids, TEntity[]? entities) diff --git a/src/Umbraco.Infrastructure/DeliveryApi/ApiRichTextElementParser.cs b/src/Umbraco.Infrastructure/DeliveryApi/ApiRichTextElementParser.cs index ceaf1b48bd4b..f3a045aad24e 100644 --- a/src/Umbraco.Infrastructure/DeliveryApi/ApiRichTextElementParser.cs +++ b/src/Umbraco.Infrastructure/DeliveryApi/ApiRichTextElementParser.cs @@ -1,13 +1,10 @@ -using HtmlAgilityPack; -using Microsoft.Extensions.DependencyInjection; +using HtmlAgilityPack; using Microsoft.Extensions.Logging; using Umbraco.Cms.Core; using Umbraco.Cms.Core.DeliveryApi; -using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Models.Blocks; using Umbraco.Cms.Core.Models.DeliveryApi; using Umbraco.Cms.Core.PublishedCache; -using Umbraco.Cms.Core.Routing; using Umbraco.Cms.Infrastructure.Extensions; using Umbraco.Extensions; @@ -101,9 +98,9 @@ private T ParseElement(HtmlNode element, IPublishedSnapshot publishedSnapshot // - non-#comment nodes // - non-#text nodes // - non-empty #text nodes - // - empty #text between inline elements (see #17037) + // - empty #text between inline elements (see #17037) but not #text with only newlines (see #19388) HtmlNode[] childNodes = element.ChildNodes - .Where(c => c.Name != CommentNodeName && (c.Name != TextNodeName || c.NextSibling is not null || string.IsNullOrWhiteSpace(c.InnerText) is false)) + .Where(c => c.Name != CommentNodeName && (c.Name != TextNodeName || IsNonEmptyElement(c))) .ToArray(); var tag = TagName(element); @@ -124,6 +121,9 @@ private T ParseElement(HtmlNode element, IPublishedSnapshot publishedSnapshot return createElement(tag, attributes, childElements); } + private static bool IsNonEmptyElement(HtmlNode htmlNode) => + string.IsNullOrWhiteSpace(htmlNode.InnerText) is false || htmlNode.InnerText.Any(c => c != '\n' && c != '\r'); + private string TagName(HtmlNode htmlNode) => htmlNode.Name; private void ReplaceLocalLinks(IPublishedSnapshot publishedSnapshot, Dictionary attributes) diff --git a/src/Umbraco.Infrastructure/DeliveryApi/ApiRichTextMarkupParser.cs b/src/Umbraco.Infrastructure/DeliveryApi/ApiRichTextMarkupParser.cs index 42c882986871..263ac9a8b56a 100644 --- a/src/Umbraco.Infrastructure/DeliveryApi/ApiRichTextMarkupParser.cs +++ b/src/Umbraco.Infrastructure/DeliveryApi/ApiRichTextMarkupParser.cs @@ -56,7 +56,7 @@ private void ReplaceLocalLinks(HtmlDocument doc, IPublishedSnapshot publishedSna link.GetAttributeValue("href", string.Empty), route => { - link.SetAttributeValue("href", route.Path); + link.SetAttributeValue("href", $"{route.Path}{route.QueryString}"); link.SetAttributeValue("data-start-item-path", route.StartItem.Path); link.SetAttributeValue("data-start-item-id", route.StartItem.Id.ToString("D")); }, diff --git a/src/Umbraco.Infrastructure/DeliveryApi/ApiRichTextParserBase.cs b/src/Umbraco.Infrastructure/DeliveryApi/ApiRichTextParserBase.cs index 7723fc835c7f..34e4384c9e87 100644 --- a/src/Umbraco.Infrastructure/DeliveryApi/ApiRichTextParserBase.cs +++ b/src/Umbraco.Infrastructure/DeliveryApi/ApiRichTextParserBase.cs @@ -4,6 +4,7 @@ using Umbraco.Cms.Core.Models.DeliveryApi; using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Cms.Core.PublishedCache; +using Umbraco.Extensions; namespace Umbraco.Cms.Infrastructure.DeliveryApi; @@ -41,6 +42,7 @@ protected void ReplaceLocalLinks(IPublishedSnapshot publishedSnapshot, string hr : null; if (route != null) { + route.QueryString = match.Groups["query"].Value.NullOrWhiteSpaceAsNull(); handled = true; handleContentRoute(route); } @@ -79,6 +81,6 @@ protected void ReplaceLocalImages(IPublishedSnapshot publishedSnapshot, string u handleMediaUrl(_apiMediaUrlProvider.GetUrl(media)); } - [GeneratedRegex("{localLink:(?umb:.+)}")] + [GeneratedRegex("{localLink:(?umb:.+)}(?[^\"]*)")] private static partial Regex LocalLinkRegex(); } diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs index ef0e25204c60..83f43e099cd0 100644 --- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs +++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs @@ -240,6 +240,7 @@ public static IUmbracoBuilder AddCoreInitialServices(this IUmbracoBuilder builde builder.Services.AddSingleton(); builder.Services.AddSingleton(); + builder.Services.AddSingleton(); return builder; } diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Examine.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Examine.cs index 4cd7df61a090..bac98ab6704c 100644 --- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Examine.cs +++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Examine.cs @@ -57,7 +57,7 @@ public static IUmbracoBuilder AddExamine(this IUmbracoBuilder builder) builder.Services.AddUnique(); builder.Services.AddUnique(); builder.Services.AddSingleton(); - + builder.Services.AddSingleton(); builder.Services.AddSingleton(); //TODO remove in Umbraco 15. Only the interface should be in the service provider builder.Services.AddUnique(); diff --git a/src/Umbraco.Infrastructure/Examine/ContentIndexPopulator.cs b/src/Umbraco.Infrastructure/Examine/ContentIndexPopulator.cs index eb9e9f135fdd..6b97fe7595df 100644 --- a/src/Umbraco.Infrastructure/Examine/ContentIndexPopulator.cs +++ b/src/Umbraco.Infrastructure/Examine/ContentIndexPopulator.cs @@ -1,5 +1,10 @@ +using System.Text.RegularExpressions; using Examine; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Persistence.Querying; using Umbraco.Cms.Core.Services; @@ -14,6 +19,8 @@ public class ContentIndexPopulator : IndexPopulator { private readonly IContentService _contentService; private readonly IValueSetBuilder _contentValueSetBuilder; + private readonly IIndexRebuildStatusManager _indexRebuildStatusManager; + private IndexingSettings _indexingSettings; private readonly ILogger _logger; private readonly int? _parentId; @@ -28,6 +35,7 @@ public class ContentIndexPopulator : IndexPopulator /// /// Default constructor to lookup all content data /// + [Obsolete("Use the constructor with IOptionsMonitor")] public ContentIndexPopulator( ILogger logger, IContentService contentService, @@ -40,13 +48,14 @@ public ContentIndexPopulator( /// /// Optional constructor allowing specifying custom query parameters /// + [Obsolete("Use the constructor with IOptionsMonitor")] public ContentIndexPopulator( ILogger logger, bool publishedValuesOnly, int? parentId, IContentService contentService, IUmbracoDatabaseFactory umbracoDatabaseFactory, - IValueSetBuilder contentValueSetBuilder) + IValueSetBuilder contentValueSetBuilder) : this(logger, publishedValuesOnly, parentId, contentService, umbracoDatabaseFactory, contentValueSetBuilder, StaticServiceProvider.Instance.GetRequiredService>()) { _contentService = contentService ?? throw new ArgumentNullException(nameof(contentService)); _umbracoDatabaseFactory = umbracoDatabaseFactory ?? throw new ArgumentNullException(nameof(umbracoDatabaseFactory)); @@ -55,7 +64,39 @@ public ContentIndexPopulator( _publishedValuesOnly = publishedValuesOnly; _parentId = parentId; } + public ContentIndexPopulator( + ILogger logger, + IContentService contentService, + IUmbracoDatabaseFactory umbracoDatabaseFactory, + IContentValueSetBuilder contentValueSetBuilder, + IOptionsMonitor indexingSettings) + : this(logger, false, null, contentService, umbracoDatabaseFactory, contentValueSetBuilder,indexingSettings) + { + } + public ContentIndexPopulator( + ILogger logger, + bool publishedValuesOnly, + int? parentId, + IContentService contentService, + IUmbracoDatabaseFactory umbracoDatabaseFactory, + IValueSetBuilder contentValueSetBuilder, + IOptionsMonitor indexingSettings, + IIndexRebuildStatusManager indexRebuildStatusManager) + { + _contentService = contentService ?? throw new ArgumentNullException(nameof(contentService)); + _umbracoDatabaseFactory = umbracoDatabaseFactory ?? throw new ArgumentNullException(nameof(umbracoDatabaseFactory)); + _contentValueSetBuilder = contentValueSetBuilder ?? throw new ArgumentNullException(nameof(contentValueSetBuilder)); + _indexRebuildStatusManager = indexRebuildStatusManager; + _indexingSettings = indexingSettings.CurrentValue; + indexingSettings.OnChange(change => + { + _indexingSettings = change; + }); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + _publishedValuesOnly = publishedValuesOnly; + _parentId = parentId; + } private IQuery PublishedQuery => _publishedQuery ??= _umbracoDatabaseFactory.SqlContext.Query().Where(x => x.Published); @@ -75,7 +116,7 @@ protected override void PopulateIndexes(IReadOnlyList indexes) return; } - const int pageSize = 10000; + var pageIndex = 0; var contentParentId = -1; @@ -86,33 +127,44 @@ protected override void PopulateIndexes(IReadOnlyList indexes) if (_publishedValuesOnly) { - IndexPublishedContent(contentParentId, pageIndex, pageSize, indexes); + IndexPublishedContent(contentParentId, pageIndex, _indexingSettings.IndexingPageSize, indexes); } else { - IndexAllContent(contentParentId, pageIndex, pageSize, indexes); + IndexAllContent(contentParentId, pageIndex, _indexingSettings.IndexingPageSize, indexes); } } protected void IndexAllContent(int contentParentId, int pageIndex, int pageSize, IReadOnlyList indexes) { IContent[] content; - + var totalBatches = 0; do { - content = _contentService.GetPagedDescendants(contentParentId, pageIndex, pageSize, out _).ToArray(); + content = _contentService.GetPagedDescendants(contentParentId, pageIndex, pageSize, out var total).ToArray(); + if (totalBatches == 0) + { + totalBatches = (int)Math.Ceiling((decimal)total / pageSize); + } var valueSets = _contentValueSetBuilder.GetValueSets(content).ToArray(); // ReSharper disable once PossibleMultipleEnumeration foreach (IIndex index in indexes) { + _indexRebuildStatusManager.UpdatePopulatorStatus(index.Name, GetType().Name, true, pageIndex, totalBatches); + index.IndexItems(valueSets); } pageIndex++; } while (content.Length == pageSize); + + foreach (IIndex index in indexes) + { + _indexRebuildStatusManager.UpdatePopulatorStatus(index.Name, GetType().Name, false, totalBatches, totalBatches); + } } protected void IndexPublishedContent(int contentParentId, int pageIndex, int pageSize, IReadOnlyList indexes) diff --git a/src/Umbraco.Infrastructure/Examine/DeliveryApiContentIndexHelper.cs b/src/Umbraco.Infrastructure/Examine/DeliveryApiContentIndexHelper.cs index 744502b3ded4..d7f2fc0c69af 100644 --- a/src/Umbraco.Infrastructure/Examine/DeliveryApiContentIndexHelper.cs +++ b/src/Umbraco.Infrastructure/Examine/DeliveryApiContentIndexHelper.cs @@ -1,4 +1,4 @@ -using Microsoft.Extensions.Options; +using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Persistence.Querying; @@ -28,21 +28,28 @@ public DeliveryApiContentIndexHelper( public void EnumerateApplicableDescendantsForContentIndex(int rootContentId, Action actionToPerform) { const int pageSize = 10000; - var pageIndex = 0; + EnumerateApplicableDescendantsForContentIndex(rootContentId, actionToPerform, pageSize); + } + + internal void EnumerateApplicableDescendantsForContentIndex(int rootContentId, Action actionToPerform, int pageSize) + { + var itemIndex = 0; + long total; - IContent[] descendants; IQuery query = _umbracoDatabaseFactory.SqlContext.Query().Where(content => content.Trashed == false); + + IContent[] descendants; do { descendants = _contentService - .GetPagedDescendants(rootContentId, pageIndex, pageSize, out _, query, Ordering.By("Path")) + .GetPagedDescendants(rootContentId, itemIndex / pageSize, pageSize, out total, query, Ordering.By("Path")) .Where(descendant => _deliveryApiSettings.IsAllowedContentType(descendant.ContentType.Alias)) .ToArray(); - actionToPerform(descendants.ToArray()); + actionToPerform(descendants); - pageIndex++; + itemIndex += pageSize; } - while (descendants.Length == pageSize); + while (descendants.Length > 0 && itemIndex < total); } } diff --git a/src/Umbraco.Infrastructure/Examine/ExamineIndexRebuilder.cs b/src/Umbraco.Infrastructure/Examine/ExamineIndexRebuilder.cs index bcaa77da7073..cf193498ec5b 100644 --- a/src/Umbraco.Infrastructure/Examine/ExamineIndexRebuilder.cs +++ b/src/Umbraco.Infrastructure/Examine/ExamineIndexRebuilder.cs @@ -2,8 +2,10 @@ // See LICENSE for more details. using Examine; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Umbraco.Cms.Core; +using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Runtime; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Infrastructure.HostedServices; @@ -16,6 +18,7 @@ public class ExamineIndexRebuilder : IIndexRebuilder private readonly IBackgroundTaskQueue _backgroundTaskQueue; private readonly IExamineManager _examineManager; private readonly ILogger _logger; + private readonly IIndexRebuildStatusManager _indexRebuildStatusManager; private readonly IMainDom _mainDom; private readonly IEnumerable _populators; private readonly object _rebuildLocker = new(); @@ -24,17 +27,29 @@ public class ExamineIndexRebuilder : IIndexRebuilder /// /// Initializes a new instance of the class. /// + [Obsolete("Use constructor with IIndexRebuildStatusManager. This is scheduled for removal in Umbraco 15.")] public ExamineIndexRebuilder( IMainDom mainDom, IRuntimeState runtimeState, ILogger logger, IExamineManager examineManager, IEnumerable populators, + IBackgroundTaskQueue backgroundTaskQueue) : this(mainDom, runtimeState, logger, StaticServiceProvider.Instance.GetRequiredService(), examineManager, populators, backgroundTaskQueue) + { + } + public ExamineIndexRebuilder( + IMainDom mainDom, + IRuntimeState runtimeState, + ILogger logger, + IIndexRebuildStatusManager indexRebuildStatusManager, + IExamineManager examineManager, + IEnumerable populators, IBackgroundTaskQueue backgroundTaskQueue) { _mainDom = mainDom; _runtimeState = runtimeState; _logger = logger; + _indexRebuildStatusManager = indexRebuildStatusManager; _examineManager = examineManager; _populators = populators; _backgroundTaskQueue = backgroundTaskQueue; @@ -147,7 +162,7 @@ private void RebuildIndex(string indexName, TimeSpan delay, CancellationToken ca { throw new InvalidOperationException($"No index found with name {indexName}"); } - + _indexRebuildStatusManager.SetReindexStatus(index, _populators.Where(x=>x.IsRegistered(index))); index.CreateIndex(); // clear the index foreach (IIndexPopulator populator in _populators) { @@ -175,6 +190,7 @@ private void RebuildIndexes(bool onlyEmptyIndexes, TimeSpan delay, CancellationT { Thread.Sleep(delay); } + IEnumerable? rebuildedIndex = null; try { @@ -189,7 +205,8 @@ private void RebuildIndexes(bool onlyEmptyIndexes, TimeSpan delay, CancellationT IIndex[] indexes = (onlyEmptyIndexes ? _examineManager.Indexes.Where(ShouldRebuild) : _examineManager.Indexes).ToArray(); - + rebuildedIndex = indexes.Select(x => x.Name); + _indexRebuildStatusManager.SetRebuildingIndexStatus(rebuildedIndex, true); if (indexes.Length == 0) { return; @@ -217,10 +234,15 @@ private void RebuildIndexes(bool onlyEmptyIndexes, TimeSpan delay, CancellationT _logger.LogError(e, "Index populating failed for populator {Populator}", populator.GetType()); } } + } } finally { + if (rebuildedIndex != null && rebuildedIndex.Any()) + { + _indexRebuildStatusManager.SetRebuildingIndexStatus(rebuildedIndex, false); + } if (Monitor.IsEntered(_rebuildLocker)) { Monitor.Exit(_rebuildLocker); diff --git a/src/Umbraco.Infrastructure/Examine/IIndexRebuildStatusManager.cs b/src/Umbraco.Infrastructure/Examine/IIndexRebuildStatusManager.cs new file mode 100644 index 000000000000..2d0b45b151ae --- /dev/null +++ b/src/Umbraco.Infrastructure/Examine/IIndexRebuildStatusManager.cs @@ -0,0 +1,30 @@ +using Examine; + +namespace Umbraco.Cms.Infrastructure.Examine; + +public interface IIndexRebuildStatusManager +{ + IndexStatus? GetRebuildingIndexStatus(string index); + void SetReindexStatus(IIndex index, IEnumerable where); + + public void UpdatePopulatorStatus(string index, string populator, bool isRunning, int currentBatch, int totalBatches); +} + +public class IndexStatus +{ + public bool IsRebuilding { get; set; } + public IEnumerable? PopulatorStatuses { get; set; } +} + +public class PopulatorStatus +{ + public PopulatorStatus(string name) + { + Name = name; + } + + public string Name { get; set; } + public bool IsRunning { get; set; } + public int CurrentBatch { get; set; } + public int TotalBatches { get; set; } +} diff --git a/src/Umbraco.Infrastructure/Examine/IndexRebuildStatusManager.cs b/src/Umbraco.Infrastructure/Examine/IndexRebuildStatusManager.cs new file mode 100644 index 000000000000..322283b557d4 --- /dev/null +++ b/src/Umbraco.Infrastructure/Examine/IndexRebuildStatusManager.cs @@ -0,0 +1,92 @@ +using Examine; + +namespace Umbraco.Cms.Infrastructure.Examine; + +/// +/// +/// +public class IndexRebuildStatusManager : IIndexRebuildStatusManager +{ + private readonly IExamineManager _examineManager; + IDictionary _rebuildingStatus = new Dictionary(); + + public IndexRebuildStatusManager(IExamineManager examineManager) + { + _examineManager = examineManager; + foreach (var index in examineManager.Indexes) + { + _rebuildingStatus.Add(index.Name, new IndexStatus()); + } + } + + public IndexStatus? GetRebuildingIndexStatus(string index) => + _rebuildingStatus.TryGetValue(index, out var status) ? status : null; + + public void SetReindexStatus(IIndex index, IEnumerable where) + { + if (!_rebuildingStatus.TryGetValue(index.Name, out var status)) + { + status = new IndexStatus(); + _rebuildingStatus.Add(index.Name, status); + } + + status.IsRebuilding = true; + status.PopulatorStatuses = where.Select(x => new PopulatorStatus(x.GetType().Name)); + } + + public void UpdatePopulatorStatus( + string index, + string populator, + bool isRunning, + int currentBatch, + int totalBatches) + { + var examineIndex = _examineManager.GetIndex(index); + + if (_rebuildingStatus.TryGetValue(index, out var status)) + { + var populatorStatus = status?.PopulatorStatuses?.FirstOrDefault(x => x.Name == populator); + if (populatorStatus != null) + { + populatorStatus.IsRunning = isRunning; + populatorStatus.CurrentBatch = currentBatch; + populatorStatus.TotalBatches = totalBatches; + } + + examineIndex.IndexItems( + [ + new ValueSet( + populator, + "populator", + new Dictionary() + { + { "isRunning", isRunning }, + { "currentBatch", currentBatch }, + { "totalBatches", totalBatches } + }) + ]); + return; + } + var currentStatus= new IndexStatus(); + _rebuildingStatus.Add(index,currentStatus); + var newPopulatorStatus = new PopulatorStatus(populator) + { + IsRunning = isRunning, + CurrentBatch = currentBatch, + TotalBatches = totalBatches + }; + currentStatus.PopulatorStatuses = new List { newPopulatorStatus }; + examineIndex.IndexItems( + [ + new ValueSet( + populator, + "populator", + new Dictionary() + { + { "isRunning", isRunning }, + { "currentBatch", currentBatch }, + { "totalBatches", totalBatches } + }) + ]); + } +} diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentTypeRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentTypeRepository.cs index cd9f104bc085..4415e70946d9 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentTypeRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentTypeRepository.cs @@ -5,6 +5,7 @@ using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Persistence.Querying; using Umbraco.Cms.Core.Persistence.Repositories; +using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Strings; using Umbraco.Cms.Infrastructure.Persistence.Dtos; using Umbraco.Cms.Infrastructure.Persistence.Querying; @@ -25,8 +26,9 @@ public ContentTypeRepository( ILogger logger, IContentTypeCommonRepository commonRepository, ILanguageRepository languageRepository, - IShortStringHelper shortStringHelper) - : base(scopeAccessor, cache, logger, commonRepository, languageRepository, shortStringHelper) + IShortStringHelper shortStringHelper, + Lazy idKeyMap) + : base(scopeAccessor, cache, logger, commonRepository, languageRepository, shortStringHelper, idKeyMap) { } diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs index e9d6348e45f8..8dfae51f6b1f 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs @@ -29,15 +29,22 @@ internal abstract class ContentTypeRepositoryBase : EntityRepositoryBas where TEntity : class, IContentTypeComposition { private readonly IShortStringHelper _shortStringHelper; - - protected ContentTypeRepositoryBase(IScopeAccessor scopeAccessor, AppCaches cache, - ILogger> logger, IContentTypeCommonRepository commonRepository, - ILanguageRepository languageRepository, IShortStringHelper shortStringHelper) + private readonly Lazy _idKeyMap; + + protected ContentTypeRepositoryBase( + IScopeAccessor scopeAccessor, + AppCaches cache, + ILogger> logger, + IContentTypeCommonRepository commonRepository, + ILanguageRepository languageRepository, + IShortStringHelper shortStringHelper, + Lazy idKeyMap) : base(scopeAccessor, cache, logger) { _shortStringHelper = shortStringHelper; CommonRepository = commonRepository; LanguageRepository = languageRepository; + _idKeyMap = idKeyMap; } protected IContentTypeCommonRepository CommonRepository { get; } @@ -287,7 +294,7 @@ protected void PersistNewBaseContentType(IContentTypeComposition entity) // If the Id of the DataType is not set, we resolve it from the db by its PropertyEditorAlias if (propertyType.DataTypeId == 0 || propertyType.DataTypeId == default) { - AssignDataTypeFromPropertyEditor(propertyType); + AssignDataTypeIdFromProvidedKeyOrPropertyEditor(propertyType); } PropertyTypeDto propertyTypeDto = @@ -590,7 +597,7 @@ protected void PersistUpdatedBaseContentType(IContentTypeComposition entity) // if the Id of the DataType is not set, we resolve it from the db by its PropertyEditorAlias if (propertyType.DataTypeId == 0 || propertyType.DataTypeId == default) { - AssignDataTypeFromPropertyEditor(propertyType); + AssignDataTypeIdFromProvidedKeyOrPropertyEditor(propertyType); } // validate the alias @@ -1434,37 +1441,59 @@ protected void ValidateAlias(TEntity entity) protected abstract TEntity? PerformGet(Guid id); /// - /// Try to set the data type id based on its ControlId + /// Try to set the data type Id based on the provided key or property editor alias. /// /// - private void AssignDataTypeFromPropertyEditor(IPropertyType propertyType) + private void AssignDataTypeIdFromProvidedKeyOrPropertyEditor(IPropertyType propertyType) { - // we cannot try to assign a data type of it's empty - if (propertyType.PropertyEditorAlias.IsNullOrWhiteSpace() == false) + // If a key is provided, use that. + if (propertyType.DataTypeKey != Guid.Empty) { - Sql sql = Sql() - .Select(dt => dt.Select(x => x.NodeDto)) - .From() - .InnerJoin().On((dt, n) => dt.NodeId == n.NodeId) - .Where( - "propertyEditorAlias = @propertyEditorAlias", - new { propertyEditorAlias = propertyType.PropertyEditorAlias }) - .OrderBy(typeDto => typeDto.NodeId); - DataTypeDto? datatype = Database.FirstOrDefault(sql); - - // we cannot assign a data type if one was not found - if (datatype != null) + Attempt dataTypeIdAttempt = _idKeyMap.Value.GetIdForKey(propertyType.DataTypeKey, UmbracoObjectTypes.DataType); + if (dataTypeIdAttempt.Success) { - propertyType.DataTypeId = datatype.NodeId; - propertyType.DataTypeKey = datatype.NodeDto.UniqueId; + propertyType.DataTypeId = dataTypeIdAttempt.Result; + return; } else { Logger.LogWarning( - "Could not assign a data type for the property type {PropertyTypeAlias} since no data type was found with a property editor {PropertyEditorAlias}", - propertyType.Alias, propertyType.PropertyEditorAlias); + "Could not assign a data type for the property type {PropertyTypeAlias} since no integer Id was found matching the key {DataTypeKey}. Falling back to look up via the property editor alias.", + propertyType.Alias, + propertyType.DataTypeKey); } } + + // Otherwise if a property editor alias is provided, try to find a data type that uses that alias. + if (propertyType.PropertyEditorAlias.IsNullOrWhiteSpace()) + { + // We cannot try to assign a data type if it's empty. + return; + } + + Sql sql = Sql() + .Select(dt => dt.Select(x => x.NodeDto)) + .From() + .InnerJoin().On((dt, n) => dt.NodeId == n.NodeId) + .Where( + "propertyEditorAlias = @propertyEditorAlias", + new { propertyEditorAlias = propertyType.PropertyEditorAlias }) + .OrderBy(typeDto => typeDto.NodeId); + DataTypeDto? datatype = Database.FirstOrDefault(sql); + + // we cannot assign a data type if one was not found + if (datatype != null) + { + propertyType.DataTypeId = datatype.NodeId; + propertyType.DataTypeKey = datatype.NodeDto.UniqueId; + } + else + { + Logger.LogWarning( + "Could not assign a data type for the property type {PropertyTypeAlias} since no data type was found with a property editor {PropertyEditorAlias}", + propertyType.Alias, + propertyType.PropertyEditorAlias); + } } protected abstract TEntity? PerformGet(string alias); diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ExternalLoginRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ExternalLoginRepository.cs index 352b1dd3fd47..bb96c7c8c8ea 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ExternalLoginRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ExternalLoginRepository.cs @@ -56,6 +56,19 @@ public int Count(IQuery query) public void DeleteUserLogins(Guid userOrMemberKey) => Database.Delete("WHERE userOrMemberKey=@userOrMemberKey", new { userOrMemberKey }); + /// + public void DeleteUserLoginsForRemovedProviders(IEnumerable currentLoginProviders) + { + Sql sql = Sql() + .Select(x => x.Id) + .From() + .Where(x => !x.LoginProvider.StartsWith(Constants.Security.MemberExternalAuthenticationTypePrefix)) // Only remove external logins relating to backoffice users, not members. + .WhereNotIn(x => x.LoginProvider, currentLoginProviders); + + var toDelete = Database.Query(sql).Select(x => x.Id).ToList(); + DeleteExternalLogins(toDelete); + } + /// public void Save(Guid userOrMemberKey, IEnumerable logins) { @@ -94,13 +107,7 @@ public void Save(Guid userOrMemberKey, IEnumerable logins) } // do the deletes, updates and inserts - if (toDelete.Count > 0) - { - // Before we can remove the external login, we must remove the external login tokens associated with that external login, - // otherwise we'll get foreign key constraint errors - Database.DeleteMany().Where(x => toDelete.Contains(x.ExternalLoginId)).Execute(); - Database.DeleteMany().Where(x => toDelete.Contains(x.Id)).Execute(); - } + DeleteExternalLogins(toDelete); foreach (KeyValuePair u in toUpdate) { @@ -110,6 +117,19 @@ public void Save(Guid userOrMemberKey, IEnumerable logins) Database.InsertBulk(toInsert.Select(i => ExternalLoginFactory.BuildDto(userOrMemberKey, i))); } + private void DeleteExternalLogins(List externalLoginIds) + { + if (externalLoginIds.Count == 0) + { + return; + } + + // Before we can remove the external login, we must remove the external login tokens associated with that external login, + // otherwise we'll get foreign key constraint errors + Database.DeleteMany().Where(x => externalLoginIds.Contains(x.ExternalLoginId)).Execute(); + Database.DeleteMany().Where(x => externalLoginIds.Contains(x.Id)).Execute(); + } + /// public void Save(Guid userOrMemberKey, IEnumerable tokens) { diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MediaTypeRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MediaTypeRepository.cs index 51a4c367527c..137d3c98044f 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MediaTypeRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MediaTypeRepository.cs @@ -5,6 +5,7 @@ using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Persistence.Querying; using Umbraco.Cms.Core.Persistence.Repositories; +using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Strings; using Umbraco.Cms.Infrastructure.Persistence.Dtos; using Umbraco.Cms.Infrastructure.Persistence.Querying; @@ -24,8 +25,9 @@ public MediaTypeRepository( ILogger logger, IContentTypeCommonRepository commonRepository, ILanguageRepository languageRepository, - IShortStringHelper shortStringHelper) - : base(scopeAccessor, cache, logger, commonRepository, languageRepository, shortStringHelper) + IShortStringHelper shortStringHelper, + Lazy idKeyMap) + : base(scopeAccessor, cache, logger, commonRepository, languageRepository, shortStringHelper, idKeyMap) { } diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberRepository.cs index 0815595c0695..6fd32b2d798b 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberRepository.cs @@ -38,7 +38,6 @@ public class MemberRepository : ContentRepositoryBase GetPage(IQuery? query, } public IMember? GetByUsername(string? username) => - _memberByUsernameCachePolicy.GetByUserName(UsernameCacheKey, username, PerformGetByUsername, PerformGetAllByUsername); + _memberByUsernameCachePolicy.GetByUserName(CacheKeys.MemberUserNameCachePrefix, username, PerformGetByUsername, PerformGetAllByUsername); public int[] GetMemberIds(string[] usernames) { @@ -511,7 +510,7 @@ protected virtual Sql GetBaseQuery(QueryType queryType, bool curren protected override void PersistDeletedItem(IMember entity) { - _memberByUsernameCachePolicy.DeleteByUserName(UsernameCacheKey, entity.Username); + _memberByUsernameCachePolicy.DeleteByUserName(CacheKeys.MemberUserNameCachePrefix, entity.Username); base.PersistDeletedItem(entity); } @@ -844,10 +843,55 @@ protected override void PersistUpdatedItem(IMember entity) OnUowRefreshedEntity(new MemberRefreshNotification(entity, new EventMessages())); - _memberByUsernameCachePolicy.DeleteByUserName(UsernameCacheKey, entity.Username); + _memberByUsernameCachePolicy.DeleteByUserName(CacheKeys.MemberUserNameCachePrefix, entity.Username); entity.ResetDirtyProperties(); } + /// + public async Task UpdateLoginPropertiesAsync(IMember member) + { + var updatedLastLoginDate = member.IsPropertyDirty(nameof(member.LastLoginDate)); + var updatedSecurityStamp = member.IsPropertyDirty(nameof(member.SecurityStamp)); + if (updatedLastLoginDate is false && updatedSecurityStamp is false) + { + return; + } + + NPocoSqlExtensions.SqlUpd GetMemberSetExpression(IMember member, NPocoSqlExtensions.SqlUpd m) + { + var setExpression = new NPocoSqlExtensions.SqlUpd(SqlContext); + if (updatedLastLoginDate) + { + setExpression.Set(x => x.LastLoginDate, member.LastLoginDate); + } + + if (updatedSecurityStamp) + { + setExpression.Set(x => x.SecurityStampToken, member.SecurityStamp); + } + + return setExpression; + } + + member.UpdatingEntity(); + + Sql updateMemberQuery = Sql() + .Update(m => GetMemberSetExpression(member, m)) + .Where(m => m.NodeId == member.Id); + await Database.ExecuteAsync(updateMemberQuery); + + Sql updateContentVersionQuery = Sql() + .Update(m => m.Set(x => x.VersionDate, member.UpdateDate)) + .Where(m => m.NodeId == member.Id && m.Current == true); + await Database.ExecuteAsync(updateContentVersionQuery); + + OnUowRefreshedEntity(new MemberRefreshNotification(member, new EventMessages())); + + _memberByUsernameCachePolicy.DeleteByUserName(CacheKeys.MemberUserNameCachePrefix, member.Username); + + member.ResetDirtyProperties(); + } + #endregion } diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberTypeRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberTypeRepository.cs index d4790a387a3b..08a525fb1ef1 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberTypeRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberTypeRepository.cs @@ -5,6 +5,7 @@ using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Persistence.Querying; using Umbraco.Cms.Core.Persistence.Repositories; +using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Strings; using Umbraco.Cms.Infrastructure.Persistence.Dtos; using Umbraco.Cms.Infrastructure.Persistence.Factories; @@ -27,9 +28,10 @@ public MemberTypeRepository( ILogger logger, IContentTypeCommonRepository commonRepository, ILanguageRepository languageRepository, - IShortStringHelper shortStringHelper) - : base(scopeAccessor, cache, logger, commonRepository, languageRepository, shortStringHelper) => - _shortStringHelper = shortStringHelper; + IShortStringHelper shortStringHelper, + Lazy idKeyMap) + : base(scopeAccessor, cache, logger, commonRepository, languageRepository, shortStringHelper, idKeyMap) + => _shortStringHelper = shortStringHelper; protected override bool SupportsPublishing => MemberType.SupportsPublishingConst; diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TagRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TagRepository.cs index a722bea2a03a..71e6d5797743 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TagRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TagRepository.cs @@ -128,10 +128,13 @@ public void Assign(int contentId, int propertyTypeId, IEnumerable tags, bo var group = SqlSyntax.GetQuotedColumnName("group"); // insert tags + // - Note we are checking in the subquery for the existence of the tag, so we don't insert duplicates, using a case-insensitive comparison (the + // LOWER keyword is consistent across SQLite and SQLServer). This ensures consistent behavior across databases as by default, SQLServer will + // perform a case-insensitive comparison, while SQLite will not. var sql1 = $@"INSERT INTO cmsTags (tag, {group}, languageId) SELECT tagSet.tag, tagSet.{group}, tagSet.languageId FROM {tagSetSql} -LEFT OUTER JOIN cmsTags ON (tagSet.tag = cmsTags.tag AND tagSet.{group} = cmsTags.{group} AND COALESCE(tagSet.languageId, -1) = COALESCE(cmsTags.languageId, -1)) +LEFT OUTER JOIN cmsTags ON (LOWER(tagSet.tag) = LOWER(cmsTags.tag) AND LOWER(tagSet.{group}) = LOWER(cmsTags.{group}) AND COALESCE(tagSet.languageId, -1) = COALESCE(cmsTags.languageId, -1)) WHERE cmsTags.id IS NULL"; Database.Execute(sql1); @@ -142,7 +145,7 @@ LEFT OUTER JOIN cmsTags ON (tagSet.tag = cmsTags.tag AND tagSet.{group} = cmsTag FROM ( SELECT t.Id FROM {tagSetSql} - INNER JOIN cmsTags as t ON (tagSet.tag = t.tag AND tagSet.{group} = t.{group} AND COALESCE(tagSet.languageId, -1) = COALESCE(t.languageId, -1)) + INNER JOIN cmsTags as t ON (LOWER(tagSet.tag) = LOWER(t.tag) AND LOWER(tagSet.{group}) = LOWER(t.{group}) AND COALESCE(tagSet.languageId, -1) = COALESCE(t.languageId, -1)) ) AS tagSet2 LEFT OUTER JOIN cmsTagRelationship r ON (tagSet2.id = r.tagId AND r.nodeId = {contentId} AND r.propertyTypeID = {propertyTypeId}) WHERE r.tagId IS NULL"; @@ -245,14 +248,18 @@ private class TagComparer : IEqualityComparer { public bool Equals(ITag? x, ITag? y) => ReferenceEquals(x, y) // takes care of both being null - || (x != null && y != null && x.Text == y.Text && x.Group == y.Group && x.LanguageId == y.LanguageId); + || (x != null && + y != null && + string.Equals(x.Text, y.Text, StringComparison.OrdinalIgnoreCase) && + string.Equals(x.Group, y.Group, StringComparison.OrdinalIgnoreCase) && + x.LanguageId == y.LanguageId); public int GetHashCode(ITag obj) { unchecked { - var h = obj.Text.GetHashCode(); - h = (h * 397) ^ obj.Group.GetHashCode(); + var h = StringComparer.OrdinalIgnoreCase.GetHashCode(obj.Text); + h = (h * 397) ^ StringComparer.OrdinalIgnoreCase.GetHashCode(obj.Group); h = (h * 397) ^ (obj.LanguageId?.GetHashCode() ?? 0); return h; } diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserRepository.cs index eda072f04963..611da2dcacf3 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserRepository.cs @@ -1053,13 +1053,25 @@ private Sql ApplySort(Sql sql, Expression GetNextUsers(int id, int count) + /// + public IEnumerable GetNextUsers(int id, int count) => PerformGetNextUsers(id, false, count); + + /// + public IEnumerable GetNextApprovedUsers(int id, int count) => PerformGetNextUsers(id, true, count); + + private IEnumerable PerformGetNextUsers(int id, bool approvedOnly, int count) { Sql idsQuery = SqlContext.Sql() .Select(x => x.Id) .From() - .Where(x => x.Id >= id) - .OrderBy(x => x.Id); + .Where(x => x.Id >= id); + + if (approvedOnly) + { + idsQuery = idsQuery.Where(x => x.Disabled == false); + } + + idsQuery = idsQuery.OrderBy(x => x.Id); // first page is index 1, not zero var ids = Database.Page(1, count, idsQuery).Items.ToArray(); @@ -1070,5 +1082,45 @@ public IEnumerable GetNextUsers(int id, int count) : GetMany(ids).OrderBy(x => x.Id) ?? Enumerable.Empty(); } + /// + public void InvalidateSessionsForRemovedProviders(IEnumerable currentLoginProviders) + { + // Get all the user keys associated with the removed providers. + Sql idsQuery = SqlContext.Sql() + .Select(x => x.UserOrMemberKey) + .From() + .Where(x => !x.LoginProvider.StartsWith(Constants.Security.MemberExternalAuthenticationTypePrefix)) // Only invalidate sessions relating to backoffice users, not members. + .WhereNotIn(x => x.LoginProvider, currentLoginProviders); + List userKeysAssociatedWithRemovedProviders = Database.Fetch(idsQuery); + if (userKeysAssociatedWithRemovedProviders.Count == 0) + { + return; + } + + // Convert to user integer IDs. + var userIdsAssociatedWithRemovedProviders = userKeysAssociatedWithRemovedProviders + .Select(ConvertUserKeyToUserId) + .Where(x => x.HasValue) + .Select(x => x!.Value) + .ToList(); + if (userIdsAssociatedWithRemovedProviders.Count == 0) + { + return; + } + + // Invalidate the security stamps on the users associated with the removed providers. + Sql updateQuery = Sql() + .Update(u => u.Set(x => x.SecurityStampToken, "0".PadLeft(32, '0'))) + .WhereIn(x => x.Id, userIdsAssociatedWithRemovedProviders); + Database.Execute(updateQuery); + } + + private static int? ConvertUserKeyToUserId(Guid userOrMemberKey) => + + // User Ids are stored as integers in the umbracoUser table, but as a GUID representation + // of that integer in umbracoExternalLogin (converted via IntExtensions.ToGuid()). + // We need to parse that to get the user Ids to invalidate. + IntExtensions.TryParseFromGuid(userOrMemberKey, out int? userId) ? userId : null; + #endregion } diff --git a/src/Umbraco.Infrastructure/PropertyEditors/BlockEditorPropertyValueEditor.cs b/src/Umbraco.Infrastructure/PropertyEditors/BlockEditorPropertyValueEditor.cs index e986905aa77f..af75e5626fc9 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/BlockEditorPropertyValueEditor.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/BlockEditorPropertyValueEditor.cs @@ -88,7 +88,7 @@ public override IEnumerable GetTags(object? value, object? dataTypeConfigu { var rawJson = value == null ? string.Empty : value is string str ? str : value.ToString(); - BlockEditorData? blockEditorData = BlockEditorValues.DeserializeAndClean(rawJson); + BlockEditorData? blockEditorData = SafeParseBlockEditorData(rawJson); if (blockEditorData is null) { yield break; @@ -115,17 +115,7 @@ public override object ToEditor(IProperty property, string? culture = null, stri { var val = property.GetValue(culture, segment); - BlockEditorData? blockEditorData; - try - { - blockEditorData = BlockEditorValues.DeserializeAndClean(val); - } - catch (JsonSerializationException) - { - // if this occurs it means the data is invalid, shouldn't happen but has happened if we change the data format. - return string.Empty; - } - + BlockEditorData? blockEditorData = SafeParseBlockEditorData(val); if (blockEditorData == null) { return string.Empty; @@ -150,17 +140,7 @@ public override object ToEditor(IProperty property, string? culture = null, stri return null; } - BlockEditorData? blockEditorData; - try - { - blockEditorData = BlockEditorValues.DeserializeAndClean(editorValue.Value); - } - catch (JsonSerializationException) - { - // if this occurs it means the data is invalid, shouldn't happen but has happened if we change the data format. - return string.Empty; - } - + BlockEditorData? blockEditorData = SafeParseBlockEditorData(editorValue.Value); if (blockEditorData == null || blockEditorData.BlockValue.ContentData.Count == 0) { return string.Empty; @@ -171,4 +151,23 @@ public override object ToEditor(IProperty property, string? culture = null, stri // return json return JsonConvert.SerializeObject(blockEditorData.BlockValue, Formatting.None); } + + // We don't throw on error here because we want to be able to parse what we can, even if some of the data is invalid. In cases where migrating + // from nested content to blocks, we don't want to trigger a fatal error for retrieving references, as this isn't vital to the operation. + // See: https://github.com/umbraco/Umbraco-CMS/issues/19784 and Umbraco support cases. + private BlockEditorData? SafeParseBlockEditorData(object? value) + { + try + { + return BlockEditorValues.DeserializeAndClean(value); + } + catch (JsonSerializationException ex) + { + _logger.LogWarning( + "Could not deserialize the provided property value into a block editor value: {PropertyValue}. Error: {ErrorMessage}.", + value, + ex.Message); + return null; + } + } } diff --git a/src/Umbraco.Infrastructure/PropertyEditors/RichTextPropertyEditor.cs b/src/Umbraco.Infrastructure/PropertyEditors/RichTextPropertyEditor.cs index 772e72283377..902f9da3c24f 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/RichTextPropertyEditor.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/RichTextPropertyEditor.cs @@ -176,6 +176,7 @@ internal class RichTextPropertyValueEditor : BlockValuePropertyValueEditorBase private readonly IJsonSerializer _jsonSerializer; private readonly IBlockEditorElementTypeCache _elementTypeCache; private readonly IRichTextRequiredValidator _richTextRequiredValidator; + private readonly IRichTextRegexValidator _richTextRegexValidator; private readonly ILogger _logger; [Obsolete("Use non-obsolete constructor. This is schedules for removal in v16.")] @@ -215,10 +216,11 @@ public RichTextPropertyValueEditor( elementTypeCache, propertyValidationService, dataValueReferenceFactoryCollection, - StaticServiceProvider.Instance.GetRequiredService()) + StaticServiceProvider.Instance.GetRequiredService(), + StaticServiceProvider.Instance.GetRequiredService()) { - } + public RichTextPropertyValueEditor( DataEditorAttribute attribute, PropertyEditorCollection propertyEditors, @@ -238,6 +240,49 @@ public RichTextPropertyValueEditor( IPropertyValidationService propertyValidationService, DataValueReferenceFactoryCollection dataValueReferenceFactoryCollection, IRichTextRequiredValidator richTextRequiredValidator) + : this( + attribute, + propertyEditors, + dataTypeReadCache, + logger, + backOfficeSecurityAccessor, + localizedTextService, + shortStringHelper, + imageSourceParser, + localLinkParser, + pastedImages, + jsonSerializer, + ioHelper, + htmlSanitizer, + macroParameterParser, + elementTypeCache, + propertyValidationService, + dataValueReferenceFactoryCollection, + richTextRequiredValidator, + StaticServiceProvider.Instance.GetRequiredService()) + { + } + + public RichTextPropertyValueEditor( + DataEditorAttribute attribute, + PropertyEditorCollection propertyEditors, + IDataTypeConfigurationCache dataTypeReadCache, + ILogger logger, + IBackOfficeSecurityAccessor backOfficeSecurityAccessor, + ILocalizedTextService localizedTextService, + IShortStringHelper shortStringHelper, + HtmlImageSourceParser imageSourceParser, + HtmlLocalLinkParser localLinkParser, + RichTextEditorPastedImages pastedImages, + IJsonSerializer jsonSerializer, + IIOHelper ioHelper, + IHtmlSanitizer htmlSanitizer, + IHtmlMacroParameterParser macroParameterParser, + IBlockEditorElementTypeCache elementTypeCache, + IPropertyValidationService propertyValidationService, + DataValueReferenceFactoryCollection dataValueReferenceFactoryCollection, + IRichTextRequiredValidator richTextRequiredValidator, + IRichTextRegexValidator richTextRegexValidator) : base(attribute, propertyEditors, dataTypeReadCache, localizedTextService, logger, shortStringHelper, jsonSerializer, ioHelper, dataValueReferenceFactoryCollection) { _backOfficeSecurityAccessor = backOfficeSecurityAccessor; @@ -249,6 +294,7 @@ public RichTextPropertyValueEditor( _macroParameterParser = macroParameterParser; _elementTypeCache = elementTypeCache; _richTextRequiredValidator = richTextRequiredValidator; + _richTextRegexValidator = richTextRegexValidator; _jsonSerializer = jsonSerializer; _logger = logger; @@ -257,6 +303,8 @@ public RichTextPropertyValueEditor( public override IValueRequiredValidator RequiredValidator => _richTextRequiredValidator; + public override IValueFormatValidator FormatValidator => _richTextRegexValidator; + /// public override object? Configuration { diff --git a/src/Umbraco.Infrastructure/PropertyEditors/RichTextPropertyEditorHelper.cs b/src/Umbraco.Infrastructure/PropertyEditors/RichTextPropertyEditorHelper.cs index 72f7d10dc535..7d0d7d497545 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/RichTextPropertyEditorHelper.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/RichTextPropertyEditorHelper.cs @@ -1,4 +1,4 @@ -using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.CodeAnalysis; using Microsoft.Extensions.Logging; using Umbraco.Cms.Core.Serialization; using Umbraco.Extensions; @@ -18,12 +18,20 @@ public static class RichTextPropertyEditorHelper /// True if the parsing succeeds, false otherwise /// /// The passed value can be: + /// - a instance (which will be the case if the rich text property is hidden from the editor). /// - a JSON string. /// - a JSON object. /// - a raw markup string (for backwards compatability). /// public static bool TryParseRichTextEditorValue(object? value, IJsonSerializer jsonSerializer, ILogger logger, [NotNullWhen(true)] out RichTextEditorValue? richTextEditorValue) { + if (value is RichTextEditorValue existingRichTextEditorValue) + { + // already a RichTextEditorValue instance + richTextEditorValue = existingRichTextEditorValue; + return true; + } + var stringValue = value as string ?? value?.ToString(); if (stringValue is null) { diff --git a/src/Umbraco.Infrastructure/PropertyEditors/RichTextPropertyIndexValueFactory.cs b/src/Umbraco.Infrastructure/PropertyEditors/RichTextPropertyIndexValueFactory.cs index bd7835caff08..9d91c5d2b726 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/RichTextPropertyIndexValueFactory.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/RichTextPropertyIndexValueFactory.cs @@ -1,5 +1,6 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; +using System.Text.RegularExpressions; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Blocks; @@ -50,9 +51,11 @@ public RichTextPropertyIndexValueFactory( : null; // index the stripped HTML values combined with "blocks values resume" value + var richTextWithoutMarkup = StripHtmlForIndexing(richTextEditorValue.Markup); + yield return new KeyValuePair>( property.Alias, - new object[] { $"{richTextEditorValue.Markup.StripHtml()} {blocksIndexValuesResume}" }); + new object[] { $"{richTextWithoutMarkup} {blocksIndexValuesResume}" }); // store the raw value yield return new KeyValuePair>( @@ -75,4 +78,28 @@ public RichTextPropertyIndexValueFactory( protected override IEnumerable GetDataItems(RichTextEditorValue input) => input.Blocks?.ContentData ?? new List(); + + /// + /// Strips HTML tags from content while preserving whitespace from line breaks. + /// This addresses the issue where <br> tags don't create word boundaries when HTML is stripped. + /// + /// The HTML content to strip + /// Plain text with proper word boundaries + private static string StripHtmlForIndexing(string html) + { + if (string.IsNullOrWhiteSpace(html)) + { + return string.Empty; + } + + // Replace
    and
    tags (with any amount of whitespace and attributes) with spaces + // This regex matches: + // -
    (with / without spaces or attributes) + // -
    (with / without spaces or attributes) + html = Regex.Replace(html, @"]*/?>\s*", " ", RegexOptions.IgnoreCase); + + // Use the existing Microsoft StripHtml function for everything else + return html.StripHtml(); + } + } diff --git a/src/Umbraco.Infrastructure/PropertyEditors/Validators/IRichTextRegexValidator.cs b/src/Umbraco.Infrastructure/PropertyEditors/Validators/IRichTextRegexValidator.cs new file mode 100644 index 000000000000..8b6955432215 --- /dev/null +++ b/src/Umbraco.Infrastructure/PropertyEditors/Validators/IRichTextRegexValidator.cs @@ -0,0 +1,5 @@ +namespace Umbraco.Cms.Core.PropertyEditors.Validators; + +internal interface IRichTextRegexValidator : IValueFormatValidator +{ +} diff --git a/src/Umbraco.Infrastructure/PropertyEditors/Validators/RichTextRegexValidator.cs b/src/Umbraco.Infrastructure/PropertyEditors/Validators/RichTextRegexValidator.cs new file mode 100644 index 000000000000..aefbe3a5deaa --- /dev/null +++ b/src/Umbraco.Infrastructure/PropertyEditors/Validators/RichTextRegexValidator.cs @@ -0,0 +1,30 @@ +using System.ComponentModel.DataAnnotations; +using Microsoft.Extensions.Logging; +using Umbraco.Cms.Core.Serialization; +using Umbraco.Cms.Core.Services; + +namespace Umbraco.Cms.Core.PropertyEditors.Validators; + +internal class RichTextRegexValidator : IRichTextRegexValidator +{ + private readonly RegexValidator _regexValidator; + private readonly IJsonSerializer _jsonSerializer; + private readonly ILogger _logger; + + public RichTextRegexValidator( + IJsonSerializer jsonSerializer, + ILogger logger, + RegexValidator regexValidator) + { + _jsonSerializer = jsonSerializer; + _logger = logger; + _regexValidator = regexValidator; + } + + public IEnumerable ValidateFormat(object? value, string? valueType, string format) => _regexValidator.ValidateFormat(GetValue(value), valueType, format); + + private object? GetValue(object? value) => + RichTextPropertyEditorHelper.TryParseRichTextEditorValue(value, _jsonSerializer, _logger, out RichTextEditorValue? richTextEditorValue) + ? richTextEditorValue?.Markup + : value; +} diff --git a/src/Umbraco.Infrastructure/Security/MemberUserStore.cs b/src/Umbraco.Infrastructure/Security/MemberUserStore.cs index 82f456bef0bd..e8c80222c669 100644 --- a/src/Umbraco.Infrastructure/Security/MemberUserStore.cs +++ b/src/Umbraco.Infrastructure/Security/MemberUserStore.cs @@ -161,7 +161,7 @@ public override Task CreateAsync( } /// - public override Task UpdateAsync( + public override async Task UpdateAsync( MemberIdentityUser user, CancellationToken cancellationToken = default) { @@ -189,9 +189,21 @@ public override Task UpdateAsync( var isLoginsPropertyDirty = user.IsPropertyDirty(nameof(MemberIdentityUser.Logins)); var isTokensPropertyDirty = user.IsPropertyDirty(nameof(MemberIdentityUser.LoginTokens)); - if (UpdateMemberProperties(found, user, out var updateRoles)) + IReadOnlyList propertiesUpdated = UpdateMemberProperties(found, user, out var updateRoles); + + if (propertiesUpdated.Count > 0) { - _memberService.Save(found); + // As part of logging in members we update the last login date, and, if concurrent logins are disabled, the security stamp. + // If and only if we are updating these properties, we can avoid the overhead of a full save of the member with the associated + // locking, property updates, tag handling etc., and make a more efficient update. + if (UpdatingOnlyLoginProperties(propertiesUpdated)) + { + await _memberService.UpdateLoginPropertiesAsync(found); + } + else + { + _memberService.Save(found); + } if (updateRoles) { @@ -222,15 +234,21 @@ public override Task UpdateAsync( } scope.Complete(); - return Task.FromResult(IdentityResult.Success); + return IdentityResult.Success; } catch (Exception ex) { - return Task.FromResult( - IdentityResult.Failed(new IdentityError { Code = GenericIdentityErrorCode, Description = ex.Message })); + return IdentityResult.Failed(new IdentityError { Code = GenericIdentityErrorCode, Description = ex.Message }); } } + private static bool UpdatingOnlyLoginProperties(IReadOnlyList propertiesUpdated) + { + string[] loginPropertyUpdates = [nameof(MemberIdentityUser.LastLoginDateUtc), nameof(MemberIdentityUser.SecurityStamp)]; + return (propertiesUpdated.Count == 2 && propertiesUpdated.ContainsAll(loginPropertyUpdates)) || + (propertiesUpdated.Count == 1 && propertiesUpdated.ContainsAny(loginPropertyUpdates)); + } + /// public override Task DeleteAsync( MemberIdentityUser user, @@ -681,9 +699,9 @@ public override Task SetTokenAsync(MemberIdentityUser user, string loginProvider return user; } - private bool UpdateMemberProperties(IMember member, MemberIdentityUser identityUser, out bool updateRoles) + private IReadOnlyList UpdateMemberProperties(IMember member, MemberIdentityUser identityUser, out bool updateRoles) { - var anythingChanged = false; + var updatedProperties = new List(); updateRoles = false; // don't assign anything if nothing has changed as this will trigger the track changes of the model @@ -692,7 +710,7 @@ private bool UpdateMemberProperties(IMember member, MemberIdentityUser identityU || (identityUser.LastLoginDateUtc.HasValue && member.LastLoginDate?.ToUniversalTime() != identityUser.LastLoginDateUtc.Value)) { - anythingChanged = true; + updatedProperties.Add(nameof(MemberIdentityUser.LastLoginDateUtc)); // if the LastLoginDate is being set to MinValue, don't convert it ToLocalTime DateTime dt = identityUser.LastLoginDateUtc == DateTime.MinValue @@ -706,14 +724,14 @@ private bool UpdateMemberProperties(IMember member, MemberIdentityUser identityU || (identityUser.LastPasswordChangeDateUtc.HasValue && member.LastPasswordChangeDate?.ToUniversalTime() != identityUser.LastPasswordChangeDateUtc.Value)) { - anythingChanged = true; + updatedProperties.Add(nameof(MemberIdentityUser.LastPasswordChangeDateUtc)); member.LastPasswordChangeDate = identityUser.LastPasswordChangeDateUtc?.ToLocalTime() ?? DateTime.Now; } if (identityUser.IsPropertyDirty(nameof(MemberIdentityUser.Comments)) && member.Comments != identityUser.Comments && identityUser.Comments.IsNullOrWhiteSpace() == false) { - anythingChanged = true; + updatedProperties.Add(nameof(MemberIdentityUser.Comments)); member.Comments = identityUser.Comments; } @@ -723,34 +741,34 @@ private bool UpdateMemberProperties(IMember member, MemberIdentityUser identityU || ((member.EmailConfirmedDate.HasValue == false || member.EmailConfirmedDate.Value == default) && identityUser.EmailConfirmed)) { - anythingChanged = true; + updatedProperties.Add(nameof(MemberIdentityUser.EmailConfirmed)); member.EmailConfirmedDate = identityUser.EmailConfirmed ? DateTime.Now : null; } if (identityUser.IsPropertyDirty(nameof(MemberIdentityUser.Name)) && member.Name != identityUser.Name && identityUser.Name.IsNullOrWhiteSpace() == false) { - anythingChanged = true; + updatedProperties.Add(nameof(MemberIdentityUser.Name)); member.Name = identityUser.Name ?? string.Empty; } if (identityUser.IsPropertyDirty(nameof(MemberIdentityUser.Email)) && member.Email != identityUser.Email && identityUser.Email.IsNullOrWhiteSpace() == false) { - anythingChanged = true; + updatedProperties.Add(nameof(MemberIdentityUser.Email)); member.Email = identityUser.Email!; } if (identityUser.IsPropertyDirty(nameof(MemberIdentityUser.AccessFailedCount)) && member.FailedPasswordAttempts != identityUser.AccessFailedCount) { - anythingChanged = true; + updatedProperties.Add(nameof(MemberIdentityUser.AccessFailedCount)); member.FailedPasswordAttempts = identityUser.AccessFailedCount; } if (member.IsLockedOut != identityUser.IsLockedOut) { - anythingChanged = true; + updatedProperties.Add(nameof(MemberIdentityUser.IsLockedOut)); member.IsLockedOut = identityUser.IsLockedOut; if (member.IsLockedOut) @@ -762,14 +780,14 @@ private bool UpdateMemberProperties(IMember member, MemberIdentityUser identityU if (member.IsApproved != identityUser.IsApproved) { - anythingChanged = true; + updatedProperties.Add(nameof(MemberIdentityUser.IsApproved)); member.IsApproved = identityUser.IsApproved; } if (identityUser.IsPropertyDirty(nameof(MemberIdentityUser.UserName)) && member.Username != identityUser.UserName && identityUser.UserName.IsNullOrWhiteSpace() == false) { - anythingChanged = true; + updatedProperties.Add(nameof(MemberIdentityUser.UserName)); member.Username = identityUser.UserName!; } @@ -777,33 +795,33 @@ private bool UpdateMemberProperties(IMember member, MemberIdentityUser identityU && member.RawPasswordValue != identityUser.PasswordHash && identityUser.PasswordHash.IsNullOrWhiteSpace() == false) { - anythingChanged = true; + updatedProperties.Add(nameof(MemberIdentityUser.PasswordHash)); member.RawPasswordValue = identityUser.PasswordHash; member.PasswordConfiguration = identityUser.PasswordConfig; } if (member.PasswordConfiguration != identityUser.PasswordConfig) { - anythingChanged = true; + updatedProperties.Add(nameof(MemberIdentityUser.PasswordConfig)); member.PasswordConfiguration = identityUser.PasswordConfig; } if (member.SecurityStamp != identityUser.SecurityStamp) { - anythingChanged = true; + updatedProperties.Add(nameof(MemberIdentityUser.SecurityStamp)); member.SecurityStamp = identityUser.SecurityStamp; } if (identityUser.IsPropertyDirty(nameof(MemberIdentityUser.Roles))) { - anythingChanged = true; + updatedProperties.Add(nameof(MemberIdentityUser.Roles)); updateRoles = true; } // reset all changes identityUser.ResetDirtyProperties(false); - return anythingChanged; + return updatedProperties.AsReadOnly(); } /// diff --git a/src/Umbraco.PublishedCache.NuCache/ContentStore.cs b/src/Umbraco.PublishedCache.NuCache/ContentStore.cs index 1a463420b876..b8ae19a95456 100644 --- a/src/Umbraco.PublishedCache.NuCache/ContentStore.cs +++ b/src/Umbraco.PublishedCache.NuCache/ContentStore.cs @@ -321,17 +321,12 @@ private void EnsureLocked() { if (_writeLock.CurrentCount != 0) { - throw new InvalidOperationException("Write lock must be acquried."); + throw new InvalidOperationException("Write lock must be acquired."); } } private void Lock(WriteLockInfo lockInfo, bool forceGen = false) { - if (_writeLock.CurrentCount == 0) - { - throw new InvalidOperationException("Recursive locks not allowed"); - } - if (_writeLock.Wait(_monitorTimeout)) { lockInfo.Taken = true; diff --git a/src/Umbraco.PublishedCache.NuCache/Property.cs b/src/Umbraco.PublishedCache.NuCache/Property.cs index 09716ef43a35..2d5635a12818 100644 --- a/src/Umbraco.PublishedCache.NuCache/Property.cs +++ b/src/Umbraco.PublishedCache.NuCache/Property.cs @@ -114,7 +114,7 @@ public Property(Property origin, PublishedContent content) // determines whether a property has value public override bool HasValue(string? culture = null, string? segment = null) { - _content.VariationContextAccessor.ContextualizeVariation(_variations, _content.Id, ref culture, ref segment); + _content.VariationContextAccessor.ContextualizeVariation(_variations, _content.Id, PropertyType.Alias, ref culture, ref segment); var value = GetSourceValue(culture, segment); var hasValue = PropertyType.IsValue(value, PropertyValueLevel.Source); @@ -148,7 +148,7 @@ public override bool HasValue(string? culture = null, string? segment = null) public override object? GetSourceValue(string? culture = null, string? segment = null) { - _content.VariationContextAccessor.ContextualizeVariation(_sourceVariations, _content.Id, ref culture, ref segment); + _content.VariationContextAccessor.ContextualizeVariation(_sourceVariations, _content.Id, PropertyType.Alias, ref culture, ref segment); // source values are tightly bound to the property/schema culture and segment configurations, so we need to // sanitize the contextualized culture/segment states before using them to access the source values. @@ -240,7 +240,7 @@ private CacheValues GetCacheValues(IAppCache? cache) EnsureSourceValuesInitialized(); - var k = new CompositeStringStringKey(culture, segment); + var k = new CompositeStringStringKey(culture ?? string.Empty, segment ?? string.Empty); // Null values are not valid when creating a CompositeStringStringKey. SourceInterValue vvalue = _sourceValues!.GetOrAdd(k, _ => new SourceInterValue @@ -262,7 +262,7 @@ private CacheValues GetCacheValues(IAppCache? cache) public override object? GetValue(string? culture = null, string? segment = null) { - _content.VariationContextAccessor.ContextualizeVariation(_variations, _content.Id, ref culture, ref segment); + _content.VariationContextAccessor.ContextualizeVariation(_variations, _content.Id, PropertyType.Alias, ref culture, ref segment); object? value; CacheValue cacheValues = GetCacheValues(PropertyType.CacheLevel).For(culture, segment); @@ -285,7 +285,7 @@ private CacheValues GetCacheValues(IAppCache? cache) [Obsolete("The current implementation of XPath is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public override object? GetXPathValue(string? culture = null, string? segment = null) { - _content.VariationContextAccessor.ContextualizeVariation(_variations, _content.Id, ref culture, ref segment); + _content.VariationContextAccessor.ContextualizeVariation(_variations, _content.Id, PropertyType.Alias, ref culture, ref segment); CacheValue cacheValues = GetCacheValues(PropertyType.CacheLevel).For(culture, segment); @@ -304,7 +304,7 @@ private CacheValues GetCacheValues(IAppCache? cache) public override object? GetDeliveryApiValue(bool expanding, string? culture = null, string? segment = null) { - _content.VariationContextAccessor.ContextualizeVariation(_variations, _content.Id, ref culture, ref segment); + _content.VariationContextAccessor.ContextualizeVariation(_variations, _content.Id, PropertyType.Alias, ref culture, ref segment); object? value; CacheValue cacheValues = GetCacheValues(expanding ? PropertyType.DeliveryApiCacheLevelForExpansion : PropertyType.DeliveryApiCacheLevel).For(culture, segment); diff --git a/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotService.cs b/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotService.cs index 8aa012d11f4a..af6b8ef4cbe2 100644 --- a/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotService.cs +++ b/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotService.cs @@ -15,6 +15,7 @@ using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Services.Changes; using Umbraco.Cms.Core.Sync; +using Umbraco.Cms.Infrastructure.HostedServices; using Umbraco.Cms.Infrastructure.PublishedCache.DataSource; using Umbraco.Cms.Infrastructure.PublishedCache.Persistence; using Umbraco.Extensions; @@ -31,6 +32,9 @@ internal class PublishedSnapshotService : IPublishedSnapshotService // means faster execution, but uses memory - not sure if we want it // so making it configurable. public static readonly bool FullCacheWhenPreviewing = true; + + private const string IsRebuildingDatabaseCacheRuntimeCacheKey = "temp_database_cache_rebuild_op"; + private readonly NuCacheSettings _config; private readonly ContentDataSerializer _contentDataSerializer; private readonly IDefaultCultureAccessor _defaultCultureAccessor; @@ -51,6 +55,8 @@ internal class PublishedSnapshotService : IPublishedSnapshotService private readonly object _storesLock = new(); private readonly ISyncBootStateAccessor _syncBootStateAccessor; private readonly IVariationContextAccessor _variationContextAccessor; + private readonly IBackgroundTaskQueue _backgroundTaskQueue; + private readonly IAppPolicyCache _runtimeCache; private long _contentGen; @@ -91,7 +97,9 @@ public PublishedSnapshotService( IPublishedModelFactory publishedModelFactory, IHostingEnvironment hostingEnvironment, IOptions config, - ContentDataSerializer contentDataSerializer) + ContentDataSerializer contentDataSerializer, + IBackgroundTaskQueue backgroundTaskQueue, + AppCaches appCaches) { _options = options; _syncBootStateAccessor = syncBootStateAccessor; @@ -111,6 +119,8 @@ public PublishedSnapshotService( _contentDataSerializer = contentDataSerializer; _config = config.Value; _publishedModelFactory = publishedModelFactory; + _backgroundTaskQueue = backgroundTaskQueue; + _runtimeCache = appCaches.RuntimeCache; } protected PublishedSnapshot? CurrentPublishedSnapshot @@ -349,12 +359,66 @@ public IPublishedSnapshot CreatePublishedSnapshot(string? previewToken) return new PublishedSnapshot(this, preview); } + /// + public bool IsRebuilding() => _runtimeCache.Get(IsRebuildingDatabaseCacheRuntimeCacheKey) is not null; + /// public void Rebuild( IReadOnlyCollection? contentTypeIds = null, IReadOnlyCollection? mediaTypeIds = null, IReadOnlyCollection? memberTypeIds = null) - => _publishedContentService.Rebuild(contentTypeIds, mediaTypeIds, memberTypeIds); + => Rebuild(false, contentTypeIds, mediaTypeIds, memberTypeIds); + + /// + public void Rebuild( + bool useBackgroundThread, + IReadOnlyCollection? contentTypeIds = null, + IReadOnlyCollection? mediaTypeIds = null, + IReadOnlyCollection? memberTypeIds = null) + { + if (useBackgroundThread) + { + _logger.LogInformation("Starting async background thread for rebuilding database cache."); + + _backgroundTaskQueue.QueueBackgroundWorkItem( + cancellationToken => + { + // Do not flow AsyncLocal to the child thread + using (ExecutionContext.SuppressFlow()) + { + Task.Run(() => PerformRebuild(contentTypeIds, mediaTypeIds, memberTypeIds)); + + // immediately return so the request isn't waiting. + return Task.CompletedTask; + } + }); + } + else + { + PerformRebuild(contentTypeIds, mediaTypeIds, memberTypeIds); + } + } + + private void PerformRebuild( + IReadOnlyCollection? contentTypeIds = null, + IReadOnlyCollection? mediaTypeIds = null, + IReadOnlyCollection? memberTypeIds = null) + { + try + { + SetIsRebuilding(); + + _publishedContentService.Rebuild(contentTypeIds, mediaTypeIds, memberTypeIds); + } + finally + { + ClearIsRebuilding(); + } + } + + private void SetIsRebuilding() => _runtimeCache.Insert(IsRebuildingDatabaseCacheRuntimeCacheKey, () => "tempValue", TimeSpan.FromMinutes(10)); + + private void ClearIsRebuilding() => _runtimeCache.Clear(IsRebuildingDatabaseCacheRuntimeCacheKey); public async Task CollectAsync() { diff --git a/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotStatus.cs b/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotStatus.cs index c706e35ca6f4..61213bf2e524 100644 --- a/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotStatus.cs +++ b/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotStatus.cs @@ -29,6 +29,11 @@ public string GetStatus() $"The current {typeof(IPublishedSnapshotService)} is not the default type. A status cannot be determined."; } + if (_service.IsRebuilding()) + { + return "Rebuild in progress. Please wait."; + } + // TODO: This should be private _service.EnsureCaches(); diff --git a/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs b/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs index aedb545a44dd..4b46bca7a1e1 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs @@ -74,6 +74,8 @@ public class AuthenticationController : UmbracoApiControllerBase private readonly IUserService _userService; private readonly WebRoutingSettings _webRoutingSettings; + private static long? _loginDurationAverage; + // TODO: We need to review all _userManager.Raise calls since many/most should be on the usermanager or signinmanager, very few should be here [ActivatorUtilitiesConstructor] public AuthenticationController( @@ -129,12 +131,17 @@ public AuthenticationController( AuthorizationPolicies.BackOfficeAccess)] // Needed to enforce the principle set on the request, if one exists. public IDictionary GetPasswordConfig(int userId) { + if (HttpContext.HasActivePasswordResetFlowSession(userId)) + { + return _passwordConfiguration.GetConfiguration(); + } + Attempt currentUserId = _backofficeSecurityAccessor.BackOfficeSecurity?.GetUserId() ?? Attempt.Fail(); - return _passwordConfiguration.GetConfiguration( - currentUserId.Success - ? currentUserId.Result != userId - : true); + + return currentUserId.Success + ? _passwordConfiguration.GetConfiguration(currentUserId.Result != userId) + : new Dictionary(); } /// @@ -415,42 +422,63 @@ public async Task IsAuthenticated() [Authorize(Policy = AuthorizationPolicies.DenyLocalLoginIfConfigured)] public async Task> PostLogin(LoginModel loginModel) { - // Sign the user in with username/password, this also gives a chance for developers to - // custom verify the credentials and auto-link user accounts with a custom IBackOfficePasswordChecker - SignInResult result = await _signInManager.PasswordSignInAsync( - loginModel.Username, loginModel.Password, true, true); + HttpContext.EndPasswordResetFlowSession(); - if (result.Succeeded) - { - // return the user detail - return GetUserDetail(_userService.GetByUsername(loginModel.Username)); - } + // Start a timed scope to ensure failed responses return is a consistent time + var loginDuration = Math.Max(_loginDurationAverage ?? _securitySettings.UserDefaultFailedLoginDurationInMilliseconds, _securitySettings.UserMinimumFailedLoginDurationInMilliseconds); + await using var timedScope = new TimedScope(loginDuration, HttpContext.RequestAborted); - if (result.RequiresTwoFactor) + // Sign the user in with username/password, this also gives a chance for developers to + // custom verify the credentials and auto-link user accounts with a custom IBackOfficePasswordChecker + SignInResult result = await _signInManager.PasswordSignInAsync(loginModel.Username, loginModel.Password, true, true); + if (result.Succeeded is false) { - var twofactorView = _backOfficeTwoFactorOptions.GetTwoFactorView(loginModel.Username); + BackOfficeIdentityUser? user = await _userManager.FindByNameAsync(loginModel.Username.Trim()); - IUser? attemptedUser = _userService.GetByUsername(loginModel.Username); + if (user is not null && + await _userManager.CheckPasswordAsync(user, loginModel.Password)) + { + // The credentials were correct, so cancel timed scope and provide a more detailed failure response + await timedScope.CancelAsync(); - // create a with information to display a custom two factor send code view - var verifyResponse = - new ObjectResult(new { twoFactorView = twofactorView, userId = attemptedUser?.Id }) + if (result.RequiresTwoFactor) { - StatusCode = StatusCodes.Status402PaymentRequired - }; + var twofactorView = _backOfficeTwoFactorOptions.GetTwoFactorView(loginModel.Username); + + IUser? attemptedUser = _userService.GetByUsername(loginModel.Username); + + // create a with information to display a custom two factor send code view + var verifyResponse = + new ObjectResult(new { twoFactorView = twofactorView, userId = attemptedUser?.Id }) + { + StatusCode = StatusCodes.Status402PaymentRequired + }; + + return verifyResponse; + } - return verifyResponse; + // TODO: We can check for these and respond differently if we think it's important + // result.IsLockedOut + // result.IsNotAllowed + } + + // Return BadRequest (400), we don't want to return a 401 because that get's intercepted + // by our angular helper because it thinks that we need to re-perform the request once we are + // authorized and we don't want to return a 403 because angular will show a warning message indicating + // that the user doesn't have access to perform this function, we just want to return a normal invalid message. + return BadRequest(); } - // TODO: We can check for these and respond differently if we think it's important - // result.IsLockedOut - // result.IsNotAllowed + // Set initial or update average (successful) login duration + _loginDurationAverage = _loginDurationAverage is long average + ? (average + (long)timedScope.Elapsed.TotalMilliseconds) / 2 + : (long)timedScope.Elapsed.TotalMilliseconds; - // return BadRequest (400), we don't want to return a 401 because that get's intercepted - // by our angular helper because it thinks that we need to re-perform the request once we are - // authorized and we don't want to return a 403 because angular will show a warning message indicating - // that the user doesn't have access to perform this function, we just want to return a normal invalid message. - return BadRequest(); + // Cancel the timed scope (we don't want to unnecessarily wait on a successful response) + await timedScope.CancelAsync(); + + // Return the user detail + return GetUserDetail(_userService.GetByUsername(loginModel.Username)); } /// @@ -469,6 +497,8 @@ public async Task PostRequestPasswordReset(RequestPasswordResetMo return BadRequest(); } + HttpContext.EndPasswordResetFlowSession(); + BackOfficeIdentityUser? identityUser = await _userManager.FindByEmailAsync(model.Email); await Task.Delay(RandomNumberGenerator.GetInt32(400, 2500)); // To randomize response time preventing user enumeration @@ -625,6 +655,8 @@ public async Task PostSend2FACode([FromBody] string provider) [AllowAnonymous] public async Task PostSetPassword(SetPasswordModel model) { + HttpContext.EndPasswordResetFlowSession(); + BackOfficeIdentityUser? identityUser = await _userManager.FindByIdAsync(model.UserId.ToString(CultureInfo.InvariantCulture)); if (identityUser is null) diff --git a/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs index e81db799c3a3..9a019c537a8d 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs @@ -230,10 +230,12 @@ public async Task VerifyInvite(string invite) // sign the user in DateTime? previousLastLoginDate = identityUser.LastLoginDateUtc; + var securityStamp = identityUser.SecurityStamp; await _signInManager.SignInAsync(identityUser, false); - // reset the lastlogindate back to previous as the user hasn't actually logged in, to add a flag or similar to BackOfficeSignInManager would be a breaking change + // reset the lastlogindate and securitystamp back to previous as the user hasn't actually logged in, to add a flag or similar to BackOfficeSignInManager would be a breaking change identityUser.LastLoginDateUtc = previousLastLoginDate; + identityUser.SecurityStamp = securityStamp; await _userManager.UpdateAsync(identityUser); return RedirectToLogin(new { flow = "invite-user", invite = "1" }); @@ -400,6 +402,11 @@ public async Task ValidatePasswordResetCode([Bind(Prefix = "u")] var result = await _userManager.VerifyUserTokenAsync(user, "Default", "ResetPassword", resetCode); + if (result) + { + HttpContext.StartPasswordResetFlowSession(userId); + } + return result ? // Redirect to login with userId and resetCode diff --git a/src/Umbraco.Web.BackOffice/Controllers/DataTypeController.cs b/src/Umbraco.Web.BackOffice/Controllers/DataTypeController.cs index 0b12e92d66ed..0d43a8bdbd32 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/DataTypeController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/DataTypeController.cs @@ -1,20 +1,17 @@ -using System; -using System.Collections.Generic; using System.Data; -using System.Linq; using System.Net.Mime; using System.Text; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; -using NPoco; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Mapping; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.ContentEditing; +using Umbraco.Cms.Core.Models.Entities; using Umbraco.Cms.Core.PropertyEditors; using Umbraco.Cms.Core.Security; using Umbraco.Cms.Core.Serialization; @@ -37,6 +34,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers [PluginController(Constants.Web.Mvc.BackOfficeApiArea)] [Authorize(Policy = AuthorizationPolicies.TreeAccessDocumentsOrDocumentTypes)] [ParameterSwapControllerActionSelector(nameof(GetById), "id", typeof(int), typeof(Guid), typeof(Udi))] + [ParameterSwapControllerActionSelector(nameof(GetReferences), "id", typeof(int), typeof(Guid))] public class DataTypeController : BackOfficeNotificationsController { private readonly PropertyEditorCollection _propertyEditors; @@ -51,6 +49,7 @@ public class DataTypeController : BackOfficeNotificationsController private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor; private readonly IConfigurationEditorJsonSerializer _serializer; private readonly IDataTypeUsageService _dataTypeUsageService; + private readonly IIdKeyMap _idKeyMap; [Obsolete("Use constructor that takes IDataTypeUsageService, scheduled for removal in V12")] public DataTypeController( @@ -77,11 +76,12 @@ public DataTypeController( localizedTextService, backOfficeSecurityAccessor, serializer, - StaticServiceProvider.Instance.GetRequiredService()) + StaticServiceProvider.Instance.GetRequiredService(), + StaticServiceProvider.Instance.GetRequiredService()) { } - [ActivatorUtilitiesConstructor] + [Obsolete("Use constructor that takes IDataTypeUsageService, scheduled for removal in V17")] public DataTypeController( PropertyEditorCollection propertyEditors, IDataTypeService dataTypeService, @@ -95,6 +95,38 @@ public DataTypeController( IBackOfficeSecurityAccessor backOfficeSecurityAccessor, IConfigurationEditorJsonSerializer serializer, IDataTypeUsageService dataTypeUsageService) + : this( + propertyEditors, + dataTypeService, + contentSettings, + umbracoMapper, + propertyEditorCollection, + contentTypeService, + mediaTypeService, + memberTypeService, + localizedTextService, + backOfficeSecurityAccessor, + serializer, + dataTypeUsageService, + StaticServiceProvider.Instance.GetRequiredService()) + { + } + + [ActivatorUtilitiesConstructor] + public DataTypeController( + PropertyEditorCollection propertyEditors, + IDataTypeService dataTypeService, + IOptionsSnapshot contentSettings, + IUmbracoMapper umbracoMapper, + PropertyEditorCollection propertyEditorCollection, + IContentTypeService contentTypeService, + IMediaTypeService mediaTypeService, + IMemberTypeService memberTypeService, + ILocalizedTextService localizedTextService, + IBackOfficeSecurityAccessor backOfficeSecurityAccessor, + IConfigurationEditorJsonSerializer serializer, + IDataTypeUsageService dataTypeUsageService, + IIdKeyMap entityService) { _propertyEditors = propertyEditors ?? throw new ArgumentNullException(nameof(propertyEditors)); _dataTypeService = dataTypeService ?? throw new ArgumentNullException(nameof(dataTypeService)); @@ -108,6 +140,7 @@ public DataTypeController( _backOfficeSecurityAccessor = backOfficeSecurityAccessor ?? throw new ArgumentNullException(nameof(backOfficeSecurityAccessor)); _serializer = serializer ?? throw new ArgumentNullException(nameof(serializer)); _dataTypeUsageService = dataTypeUsageService ?? throw new ArgumentNullException(nameof(dataTypeUsageService)); + _idKeyMap = entityService ?? throw new ArgumentNullException(nameof(entityService)); } /// @@ -421,10 +454,10 @@ public IActionResult PostRenameContainer(int id, string name) } /// - /// Returns the references (usages) for the data type + /// Returns the references (usages) for the data type. /// - /// - /// + /// Data type's integer Id. + [HttpGet] public DataTypeReferences GetReferences(int id) { var result = new DataTypeReferences(); @@ -462,6 +495,19 @@ public DataTypeReferences GetReferences(int id) return result; } + /// + /// Returns the references (usages) for the data type. + /// + /// Data type's key. + [HttpGet] + public DataTypeReferences GetReferences(Guid id) + { + Attempt dataType = _idKeyMap.GetIdForKey(id, UmbracoObjectTypes.DataType); + return dataType.Success + ? GetReferences(dataType.Result) + : new DataTypeReferences(); + } + [HttpGet] public ActionResult HasValues(int id) { diff --git a/src/Umbraco.Web.BackOffice/Controllers/ExamineManagementController.cs b/src/Umbraco.Web.BackOffice/Controllers/ExamineManagementController.cs index a2cd557a1c34..0b1fdf5cb698 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/ExamineManagementController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/ExamineManagementController.cs @@ -3,9 +3,11 @@ using Lucene.Net.QueryParsers.Classic; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Cache; +using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Models.ContentEditing; using Umbraco.Cms.Infrastructure.Examine; using Umbraco.Cms.Web.Common.Attributes; @@ -22,23 +24,32 @@ public class ExamineManagementController : UmbracoAuthorizedJsonController private readonly IExamineManager _examineManager; private readonly IIndexDiagnosticsFactory _indexDiagnosticsFactory; private readonly IIndexRebuilder _indexRebuilder; + private readonly IIndexRebuildStatusManager _rebuildStatusManager; private readonly ILogger _logger; private readonly IAppPolicyCache _runtimeCache; - + [Obsolete("Use the ctor with IOptionsMonitor>")] public ExamineManagementController( IExamineManager examineManager, ILogger logger, IIndexDiagnosticsFactory indexDiagnosticsFactory, AppCaches appCaches, - IIndexRebuilder indexRebuilder) + IIndexRebuilder indexRebuilder) : this(examineManager, logger, indexDiagnosticsFactory, appCaches, indexRebuilder, StaticServiceProvider.Instance.GetRequiredService()) + { + } + public ExamineManagementController( + IExamineManager examineManager, + ILogger logger, + IIndexDiagnosticsFactory indexDiagnosticsFactory, + AppCaches appCaches, + IIndexRebuilder indexRebuilder, IIndexRebuildStatusManager rebuildStatusManager) { _examineManager = examineManager; _logger = logger; _indexDiagnosticsFactory = indexDiagnosticsFactory; _runtimeCache = appCaches.RuntimeCache; _indexRebuilder = indexRebuilder; + _rebuildStatusManager = rebuildStatusManager; } - /// /// Get the details for indexers /// @@ -129,12 +140,10 @@ public ActionResult GetSearchResults(string searcherName, string? return validate; } - var cacheKey = "temp_indexing_op_" + indexName; - var found = _runtimeCache.Get(cacheKey); - + var status = _rebuildStatusManager.GetRebuildingIndexStatus(indexName); //if its still there then it's not done - return found != null - ? null + return status?.IsRebuilding == true + ? CreateModel(index!, status) : CreateModel(index!); } @@ -167,12 +176,7 @@ public IActionResult PostRebuildIndex(string indexName) try { - var cacheKey = "temp_indexing_op_" + index.Name; - //put temp val in cache which is used as a rudimentary way to know when the indexing is done - _runtimeCache.Insert(cacheKey, () => "tempValue", TimeSpan.FromMinutes(5)); - _indexRebuilder.RebuildIndex(indexName); - return new OkResult(); } catch (Exception ex) @@ -188,10 +192,11 @@ public IActionResult PostRebuildIndex(string indexName) } } - private ExamineIndexModel CreateModel(IIndex index) + private ExamineIndexModel CreateModel(IIndex index, IndexStatus? status = null) { var indexName = index.Name; IIndexDiagnostics indexDiag = _indexDiagnosticsFactory.Create(index); + Attempt isHealth = indexDiag.IsHealthy(); var healthResult = isHealth.Result; @@ -227,7 +232,11 @@ private ExamineIndexModel CreateModel(IIndex index) ["DocumentCount"] = documentCount, ["FieldCount"] = fieldCount }; - + if(status is not null) + { + properties["IsRebuilding"] = status.IsRebuilding; + properties["PopulatorStatuses"] = status.PopulatorStatuses; + } foreach (KeyValuePair p in indexDiag.Metadata) { properties[p.Key] = p.Value; @@ -307,8 +316,5 @@ private void Indexer_IndexOperationComplete(object? sender, EventArgs e) } _logger.LogInformation("Rebuilding index '{indexerName}' done.", indexer?.Name); - - var cacheKey = "temp_indexing_op_" + indexer?.Name; - _runtimeCache.Clear(cacheKey); } } diff --git a/src/Umbraco.Web.BackOffice/Controllers/MacroRenderingController.cs b/src/Umbraco.Web.BackOffice/Controllers/MacroRenderingController.cs index efa322a88ccc..1d2fb7eb9b67 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/MacroRenderingController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/MacroRenderingController.cs @@ -87,7 +87,7 @@ public ActionResult> GetMacroParameters(int macroId) [HttpGet] public async Task GetMacroResultAsHtmlForEditor(string macroAlias, int pageId, [FromQuery] IDictionary macroParams) => - await GetMacroResultAsHtml(macroAlias, pageId, macroParams); + await GetMacroResultAsHtml(macroAlias, pageId.ToString(), macroParams); /// /// Gets a rendered macro as HTML for rendering in the rich text editor. @@ -98,11 +98,24 @@ public async Task GetMacroResultAsHtmlForEditor(string macroAlias /// /// [HttpPost] + [NonAction] + [Obsolete("This endpoint is no longer used.")] public async Task GetMacroResultAsHtmlForEditor(MacroParameterModel model) => + await GetMacroResultAsHtml(model.MacroAlias, model.PageId.ToString(), model.MacroParams); + + /// + /// Gets a rendered macro as HTML for rendering in the rich text editor. + /// Using HTTP POST instead of GET allows for more parameters to be passed as it's not dependent on URL-length + /// limitations like GET. + /// The method using GET is kept to maintain backwards compatibility + /// + /// + /// + [HttpPost] + public async Task GetMacroResultAsHtmlForEditor(MacroParameterModel2 model) => await GetMacroResultAsHtml(model.MacroAlias, model.PageId, model.MacroParams); - private async Task GetMacroResultAsHtml(string? macroAlias, int pageId, - IDictionary? macroParams) + private async Task GetMacroResultAsHtml(string? macroAlias, string pageId, IDictionary? macroParams) { IMacro? m = macroAlias is null ? null : _macroService.GetByAlias(macroAlias); if (m == null) @@ -111,11 +124,11 @@ private async Task GetMacroResultAsHtml(string? macroAlias, int p } IUmbracoContext umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext(); - IPublishedContent? publishedContent = umbracoContext.Content?.GetById(true, pageId); + IPublishedContent? publishedContent = GetPagePublishedContent(pageId, umbracoContext); //if it isn't supposed to be rendered in the editor then return an empty string //currently we cannot render a macro if the page doesn't yet exist - if (pageId == -1 || publishedContent == null || m.DontRender) + if (publishedContent == null || m.DontRender) { //need to create a specific content result formatted as HTML since this controller has been configured //with only json formatters. @@ -149,6 +162,21 @@ private async Task GetMacroResultAsHtml(string? macroAlias, int p } } + private static IPublishedContent? GetPagePublishedContent(string pageId, IUmbracoContext umbracoContext) + { + if (int.TryParse(pageId, NumberStyles.Integer, CultureInfo.InvariantCulture, out int pageIdAsInt)) + { + return umbracoContext.Content?.GetById(true, pageIdAsInt); + } + + if (Guid.TryParse(pageId, out Guid pageIdAsGuid)) + { + return umbracoContext.Content?.GetById(true, pageIdAsGuid); + } + + return null; + } + [HttpPost] public IActionResult CreatePartialViewMacroWithFile(CreatePartialViewMacroWithFileModel model) { @@ -180,6 +208,7 @@ public IActionResult CreatePartialViewMacroWithFile(CreatePartialViewMacroWithFi return Ok(); } + [Obsolete("This model is no longer used and has been replaced with MacroParameterModel2 that changes the type of the PageId property.")] public class MacroParameterModel { public string? MacroAlias { get; set; } @@ -187,6 +216,13 @@ public class MacroParameterModel public IDictionary? MacroParams { get; set; } } + public class MacroParameterModel2 + { + public string? MacroAlias { get; set; } + public string PageId { get; set; } = string.Empty; + public IDictionary? MacroParams { get; set; } + } + public class CreatePartialViewMacroWithFileModel { public string? Filename { get; set; } diff --git a/src/Umbraco.Web.BackOffice/Controllers/PreviewController.cs b/src/Umbraco.Web.BackOffice/Controllers/PreviewController.cs index 676e18a58905..3610b02adcce 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/PreviewController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/PreviewController.cs @@ -178,26 +178,31 @@ public ActionResult Frame(int id, string culture) return RedirectPermanent($"../../{id}{query}"); } - private static bool ValidateProvidedCulture(string culture) + /// + /// Validates the provided culture code. + /// + /// + /// Marked as internal to expose for unit tests. + /// + internal static bool ValidateProvidedCulture(string culture) { if (string.IsNullOrEmpty(culture)) { return true; } - // We can be confident the backoffice will have provided a valid culture in linking to the - // preview, so we don't need to check that the culture matches an Umbraco language. - // We are only concerned here with protecting against XSS attacks from a fiddled preview - // URL, so we can just confirm we have a valid culture. - try - { - CultureInfo.GetCultureInfo(culture, true); - return true; - } - catch (CultureNotFoundException) + // Culture codes are expected to match this pattern. + if (CultureCodeRegex().IsMatch(culture) is false) { return false; } + + // We can be confident the backoffice will have provided a valid culture in linking to the + // preview, so we don't need to check that the culture matches an Umbraco language (or is even a + // valid culture code). + // We are only concerned here with protecting against XSS attacks from a fiddled preview + // URL, so we can proceed if the the regex is matched. + return true; } public ActionResult? EnterPreview(int id) @@ -217,10 +222,8 @@ public ActionResult End(string? redir = null) // are we attempting a redirect to the default route (by ID with optional culture)? Match match = DefaultPreviewRedirectRegex().Match(redir ?? string.Empty); - if (match.Success) + if (match.Success && int.TryParse(match.Groups["id"].Value, out int id)) { - var id = int.Parse(match.Groups["id"].Value); - // first try to resolve the published URL if (_umbracoContextAccessor.TryGetUmbracoContext(out IUmbracoContext? umbracoContext) && umbracoContext.Content is not null) @@ -263,4 +266,7 @@ public ActionResult End(string? redir = null) [GeneratedRegex("^\\/(?\\d*)(\\?culture=(?[\\w-]*))?$")] private static partial Regex DefaultPreviewRedirectRegex(); + + [GeneratedRegex(@"^[a-z]{2,3}[-0-9a-z]*$", RegexOptions.IgnoreCase)] + private static partial Regex CultureCodeRegex(); } diff --git a/src/Umbraco.Web.BackOffice/Controllers/PublishedSnapshotCacheStatusController.cs b/src/Umbraco.Web.BackOffice/Controllers/PublishedSnapshotCacheStatusController.cs index 91cd16a0f6ba..33750d434bfe 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/PublishedSnapshotCacheStatusController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/PublishedSnapshotCacheStatusController.cs @@ -37,13 +37,32 @@ public PublishedSnapshotCacheStatusController( [HttpPost] public string RebuildDbCache() { - //Rebuild All + if (_publishedSnapshotService.IsRebuilding()) + { + return "Rebuild already in progress."; + } + _publishedSnapshotService.RebuildAll(); return _publishedSnapshotStatus.GetStatus(); } /// - /// Gets a status report + /// Rebuilds the Database cache on a background thread. + /// + [HttpPost] + public IActionResult RebuildDbCacheInBackground() + { + if (_publishedSnapshotService.IsRebuilding()) + { + return BadRequest("Rebuild already in progress."); + } + + _publishedSnapshotService.RebuildAll(true); + return Ok(); + } + + /// + /// Gets a status report. /// [HttpGet] public string GetStatus() => _publishedSnapshotStatus.GetStatus(); diff --git a/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilder.BackOfficeAuth.cs b/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilder.BackOfficeAuth.cs index fd3bfe71bc31..dfa707146d1e 100644 --- a/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilder.BackOfficeAuth.cs +++ b/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilder.BackOfficeAuth.cs @@ -7,6 +7,7 @@ using Umbraco.Cms.Core.Security; using Umbraco.Cms.Web.BackOffice.Authorization; using Umbraco.Cms.Web.BackOffice.Middleware; +using Umbraco.Cms.Web.BackOffice.NotificationHandlers; using Umbraco.Cms.Web.BackOffice.Security; using Umbraco.Cms.Web.Common.Authorization; using Umbraco.Cms.Web.Common.Security; @@ -65,6 +66,8 @@ public static IUmbracoBuilder AddBackOfficeAuthentication(this IUmbracoBuilder b builder.AddNotificationHandler(); builder.AddNotificationHandler(); + builder.AddNotificationHandler(); + return builder; } diff --git a/src/Umbraco.Web.BackOffice/Extensions/HttpContextExtensions.cs b/src/Umbraco.Web.BackOffice/Extensions/HttpContextExtensions.cs index 38a681917abe..33644df543d7 100644 --- a/src/Umbraco.Web.BackOffice/Extensions/HttpContextExtensions.cs +++ b/src/Umbraco.Web.BackOffice/Extensions/HttpContextExtensions.cs @@ -5,9 +5,20 @@ namespace Umbraco.Extensions; public static class HttpContextExtensions { + private const string PasswordResetFlowSessionKey = nameof(PasswordResetFlowSessionKey); + public static void SetExternalLoginProviderErrors(this HttpContext httpContext, BackOfficeExternalLoginProviderErrors errors) => httpContext.Items[nameof(BackOfficeExternalLoginProviderErrors)] = errors; public static BackOfficeExternalLoginProviderErrors? GetExternalLoginProviderErrors(this HttpContext httpContext) => httpContext.Items[nameof(BackOfficeExternalLoginProviderErrors)] as BackOfficeExternalLoginProviderErrors; + + internal static void StartPasswordResetFlowSession(this HttpContext httpContext, int userId) + => httpContext.Session.SetInt32(PasswordResetFlowSessionKey, userId); + + internal static void EndPasswordResetFlowSession(this HttpContext httpContext) + => httpContext.Session.Remove(PasswordResetFlowSessionKey); + + internal static bool HasActivePasswordResetFlowSession(this HttpContext httpContext, int userId) + => httpContext.Session.GetInt32(PasswordResetFlowSessionKey) == userId; } diff --git a/src/Umbraco.Web.BackOffice/NotificationHandlers/ExternalLoginProviderStartupHandler.cs b/src/Umbraco.Web.BackOffice/NotificationHandlers/ExternalLoginProviderStartupHandler.cs new file mode 100644 index 000000000000..22013d2bbc0c --- /dev/null +++ b/src/Umbraco.Web.BackOffice/NotificationHandlers/ExternalLoginProviderStartupHandler.cs @@ -0,0 +1,40 @@ +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; +using Umbraco.Cms.Web.BackOffice.Security; + +namespace Umbraco.Cms.Web.BackOffice.NotificationHandlers; + +/// +/// Invalidates backoffice sessions and clears external logins for removed providers if the external login +/// provider setup has changed. +/// +internal sealed class ExternalLoginProviderStartupHandler : INotificationHandler +{ + private readonly IBackOfficeExternalLoginProviders _backOfficeExternalLoginProviders; + private readonly IRuntimeState _runtimeState; + private readonly IServerRoleAccessor _serverRoleAccessor; + + public ExternalLoginProviderStartupHandler( + IBackOfficeExternalLoginProviders backOfficeExternalLoginProviders, + IRuntimeState runtimeState, + IServerRoleAccessor serverRoleAccessor) + { + _backOfficeExternalLoginProviders = backOfficeExternalLoginProviders; + _runtimeState = runtimeState; + _serverRoleAccessor = serverRoleAccessor; + } + + public void Handle(UmbracoApplicationStartingNotification notification) + { + if (_runtimeState.Level != RuntimeLevel.Run || + _serverRoleAccessor.CurrentServerRole == ServerRole.Subscriber) + { + return; + } + + _backOfficeExternalLoginProviders.InvalidateSessionsIfExternalLoginProvidersChanged(); + } +} diff --git a/src/Umbraco.Web.BackOffice/Security/BackOfficeExternalLoginProviders.cs b/src/Umbraco.Web.BackOffice/Security/BackOfficeExternalLoginProviders.cs index 277fd06c6bb1..d0140133f595 100644 --- a/src/Umbraco.Web.BackOffice/Security/BackOfficeExternalLoginProviders.cs +++ b/src/Umbraco.Web.BackOffice/Security/BackOfficeExternalLoginProviders.cs @@ -1,4 +1,8 @@ using Microsoft.AspNetCore.Authentication; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Umbraco.Cms.Core.DependencyInjection; +using Umbraco.Cms.Core.Services; namespace Umbraco.Cms.Web.BackOffice.Security; @@ -7,13 +11,41 @@ public class BackOfficeExternalLoginProviders : IBackOfficeExternalLoginProvider { private readonly IAuthenticationSchemeProvider _authenticationSchemeProvider; private readonly Dictionary _externalLogins; + private readonly IKeyValueService _keyValueService; + private readonly IExternalLoginWithKeyService _externalLoginWithKeyService; + private readonly IUserService _userService; + private readonly ILogger _logger; + private const string ExternalLoginProvidersKey = "Umbraco.Cms.Web.BackOffice.Security.BackOfficeExternalLoginProviders"; + + [Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 17.")] public BackOfficeExternalLoginProviders( IEnumerable externalLogins, IAuthenticationSchemeProvider authenticationSchemeProvider) + : this( + externalLogins, + authenticationSchemeProvider, + StaticServiceProvider.Instance.GetRequiredService(), + StaticServiceProvider.Instance.GetRequiredService(), + StaticServiceProvider.Instance.GetRequiredService(), + StaticServiceProvider.Instance.GetRequiredService>()) + { + } + + public BackOfficeExternalLoginProviders( + IEnumerable externalLogins, + IAuthenticationSchemeProvider authenticationSchemeProvider, + IKeyValueService keyValueService, + IExternalLoginWithKeyService externalLoginWithKeyService, + IUserService userService, + ILogger logger) { _externalLogins = externalLogins.ToDictionary(x => x.AuthenticationType); _authenticationSchemeProvider = authenticationSchemeProvider; + _keyValueService = keyValueService; + _externalLoginWithKeyService = externalLoginWithKeyService; + _userService = userService; + _logger = logger; } /// @@ -66,4 +98,26 @@ public bool HasDenyLocalLogin() var found = _externalLogins.Values.Where(x => x.Options.DenyLocalLogin).ToList(); return found.Count > 0; } + + /// + public void InvalidateSessionsIfExternalLoginProvidersChanged() + { + var previousExternalLoginProvidersValue = _keyValueService.GetValue(ExternalLoginProvidersKey); + var currentExternalLoginProvidersValue = string.Join("|", _externalLogins.Keys.OrderBy(key => key)); + + if ((previousExternalLoginProvidersValue ?? string.Empty) != currentExternalLoginProvidersValue) + { + _logger.LogWarning( + "The configured external login providers have changed. Existing backoffice sessions using the removed providers will be invalidated and external login data removed."); + + _userService.InvalidateSessionsForRemovedProviders(_externalLogins.Keys); + _externalLoginWithKeyService.DeleteUserLoginsForRemovedProviders(_externalLogins.Keys); + + _keyValueService.SetValue(ExternalLoginProvidersKey, currentExternalLoginProvidersValue); + } + else if (previousExternalLoginProvidersValue is null) + { + _keyValueService.SetValue(ExternalLoginProvidersKey, string.Empty); + } + } } diff --git a/src/Umbraco.Web.BackOffice/Security/IBackOfficeExternalLoginProviders.cs b/src/Umbraco.Web.BackOffice/Security/IBackOfficeExternalLoginProviders.cs index 6d0a699f9a54..1b1f86186edd 100644 --- a/src/Umbraco.Web.BackOffice/Security/IBackOfficeExternalLoginProviders.cs +++ b/src/Umbraco.Web.BackOffice/Security/IBackOfficeExternalLoginProviders.cs @@ -30,4 +30,11 @@ public interface IBackOfficeExternalLoginProviders /// /// bool HasDenyLocalLogin(); + + /// + /// Used during startup to see if the configured external login providers is different from the persisted information. + /// If they are different, this will invalidate backoffice sessions and clear external logins for removed providers + /// if the external login provider setup has changed. + /// + void InvalidateSessionsIfExternalLoginProvidersChanged() { } } diff --git a/src/Umbraco.Web.BackOffice/Trees/ContentTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/ContentTreeController.cs index 0ef895e2072a..d5df0dd3689d 100644 --- a/src/Umbraco.Web.BackOffice/Trees/ContentTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/ContentTreeController.cs @@ -100,7 +100,13 @@ protected override int[] UserStartNodes public async Task SearchAsync(string query, int pageSize, long pageIndex, string? searchFrom = null) { - IEnumerable results = _treeSearcher.ExamineSearch(query, UmbracoEntityTypes.Document, pageSize, pageIndex, out var totalFound, searchFrom); + IEnumerable results = _treeSearcher.ExamineSearch( + query, + UmbracoEntityTypes.Document, + pageSize, + pageIndex, + out var totalFound, + searchFrom: searchFrom); return new EntitySearchResults(results, totalFound); } @@ -399,7 +405,14 @@ private void AddActionNode(IUmbracoEntity item, MenuItemCollection menu public async Task SearchAsync(string query, int pageSize, long pageIndex, string? searchFrom = null, string? culture = null) { - var results = _treeSearcher.ExamineSearch(query, UmbracoEntityTypes.Document, pageSize, pageIndex, out long totalFound, culture: culture, searchFrom: searchFrom); + var results = _treeSearcher.ExamineSearch( + query, + UmbracoEntityTypes.Document, + pageSize, + pageIndex, + out long totalFound, + culture: culture, + searchFrom: searchFrom); return new EntitySearchResults(results, totalFound); } } diff --git a/src/Umbraco.Web.BackOffice/Trees/MediaTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/MediaTreeController.cs index 0fbf606db85f..1c36cb0debc9 100644 --- a/src/Umbraco.Web.BackOffice/Trees/MediaTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/MediaTreeController.cs @@ -76,8 +76,13 @@ protected override int[] UserStartNodes public async Task SearchAsync(string query, int pageSize, long pageIndex, string? searchFrom = null) { - IEnumerable results = _treeSearcher.ExamineSearch(query, UmbracoEntityTypes.Media, pageSize, - pageIndex, out var totalFound, searchFrom); + IEnumerable results = _treeSearcher.ExamineSearch( + query, + UmbracoEntityTypes.Media, + pageSize, + pageIndex, + out var totalFound, + searchFrom: searchFrom); return new EntitySearchResults(results, totalFound); } diff --git a/src/Umbraco.Web.BackOffice/Trees/MemberTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/MemberTreeController.cs index 22cef9c08571..61a3ead6ceea 100644 --- a/src/Umbraco.Web.BackOffice/Trees/MemberTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/MemberTreeController.cs @@ -51,7 +51,13 @@ public MemberTreeController( public async Task SearchAsync(string query, int pageSize, long pageIndex, string? searchFrom = null) { - IEnumerable results = _treeSearcher.ExamineSearch(query, UmbracoEntityTypes.Member, pageSize, pageIndex, out var totalFound, searchFrom); + IEnumerable results = _treeSearcher.ExamineSearch( + query, + UmbracoEntityTypes.Member, + pageSize, + pageIndex, + out var totalFound, + searchFrom: searchFrom); return new EntitySearchResults(results, totalFound); } diff --git a/src/Umbraco.Web.BackOffice/Trees/StaticFilesTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/StaticFilesTreeController.cs index 92931f14ca44..1daefc0bc54a 100644 --- a/src/Umbraco.Web.BackOffice/Trees/StaticFilesTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/StaticFilesTreeController.cs @@ -98,16 +98,6 @@ private void AddRootFolder(string directory, FormCollection queryStrings, TreeNo private void AddPhysicalFiles(string path, FormCollection queryStrings, TreeNodeCollection nodes) { - IEnumerable files = _fileSystem.GetFiles(path) - .Where(x => x.StartsWith(AppPlugins) || x.StartsWith(Webroot)); - - foreach (var file in files) - { - var name = Path.GetFileName(file); - TreeNode node = CreateTreeNode(WebUtility.UrlEncode(file), path, queryStrings, name, Constants.Icons.DefaultIcon, false); - nodes.Add(node); - } - IEnumerable directories = _fileSystem.GetDirectories(path); foreach (var directory in directories) @@ -117,6 +107,16 @@ private void AddPhysicalFiles(string path, FormCollection queryStrings, TreeNode TreeNode node = CreateTreeNode(WebUtility.UrlEncode(directory), path, queryStrings, name, Constants.Icons.Folder, hasChildren); nodes.Add(node); } + + IEnumerable files = _fileSystem.GetFiles(path) + .Where(x => x.StartsWith(AppPlugins) || x.StartsWith(Webroot)); + + foreach (var file in files) + { + var name = Path.GetFileName(file); + TreeNode node = CreateTreeNode(WebUtility.UrlEncode(file), path, queryStrings, name, Constants.Icons.DefaultIcon, false); + nodes.Add(node); + } } private void AddWebRootFiles(string path, FormCollection queryStrings, TreeNodeCollection nodes) diff --git a/src/Umbraco.Web.Common/Security/ConfigureSecurityStampOptions.cs b/src/Umbraco.Web.Common/Security/ConfigureSecurityStampOptions.cs index 71d644d489a2..0cf4bd2a1fac 100644 --- a/src/Umbraco.Web.Common/Security/ConfigureSecurityStampOptions.cs +++ b/src/Umbraco.Web.Common/Security/ConfigureSecurityStampOptions.cs @@ -35,7 +35,7 @@ public static void ConfigureOptions(SecurityStampValidatorOptions options, Secur // Adjust the security stamp validation interval to a shorter duration // when concurrent logins are not allowed and the duration has the default interval value // (currently defaults to 30 minutes), ensuring quicker re-validation. - if (securitySettings.AllowConcurrentLogins is false && options.ValidationInterval == TimeSpan.FromMinutes(30)) + if (securitySettings.AllowConcurrentLogins is false && options.ValidationInterval == new SecurityStampValidatorOptions().ValidationInterval) { options.ValidationInterval = TimeSpan.FromSeconds(30); } diff --git a/src/Umbraco.Web.UI.Client/package-lock.json b/src/Umbraco.Web.UI.Client/package-lock.json index 7fbff656922f..ee76ed227443 100644 --- a/src/Umbraco.Web.UI.Client/package-lock.json +++ b/src/Umbraco.Web.UI.Client/package-lock.json @@ -7,8 +7,8 @@ "name": "ui", "dependencies": { "@microsoft/signalr": "8.0.7", - "@umbraco-ui/uui": "1.12.2", - "@umbraco-ui/uui-css": "1.12.1", + "@umbraco-ui/uui": "1.13.0", + "@umbraco-ui/uui-css": "1.13.0", "ace-builds": "1.31.1", "angular": "1.8.3", "angular-animate": "1.8.3", @@ -2257,897 +2257,908 @@ "peer": true }, "node_modules/@umbraco-ui/uui": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui/-/uui-1.12.2.tgz", - "integrity": "sha512-oEqt0ysOpqlpMk7AOX+88aV0dgnHfSXxE6imJw0KQKNMnZNOKv7EpndGliLJW/N2hgXQoVPESeYAfbLLt8J0MQ==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui/-/uui-1.13.0.tgz", + "integrity": "sha512-O/RvFeW+Mjn24ckmWJeTzMZKYbVrnaHscl9zKGKkMSva3j3mnJs/Q9N6BfihQy3qdZP5ED+2lGomezxfoLjZ7g==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-action-bar": "1.12.2", - "@umbraco-ui/uui-avatar": "1.12.2", - "@umbraco-ui/uui-avatar-group": "1.12.2", - "@umbraco-ui/uui-badge": "1.12.2", - "@umbraco-ui/uui-base": "1.12.2", - "@umbraco-ui/uui-boolean-input": "1.12.2", - "@umbraco-ui/uui-box": "1.12.2", - "@umbraco-ui/uui-breadcrumbs": "1.12.2", - "@umbraco-ui/uui-button": "1.12.2", - "@umbraco-ui/uui-button-group": "1.12.2", - "@umbraco-ui/uui-button-inline-create": "1.12.2", - "@umbraco-ui/uui-card": "1.12.2", - "@umbraco-ui/uui-card-block-type": "1.12.2", - "@umbraco-ui/uui-card-content-node": "1.12.2", - "@umbraco-ui/uui-card-media": "1.12.2", - "@umbraco-ui/uui-card-user": "1.12.2", - "@umbraco-ui/uui-caret": "1.12.2", - "@umbraco-ui/uui-checkbox": "1.12.2", - "@umbraco-ui/uui-color-area": "1.12.2", - "@umbraco-ui/uui-color-picker": "1.12.2", - "@umbraco-ui/uui-color-slider": "1.12.2", - "@umbraco-ui/uui-color-swatch": "1.12.2", - "@umbraco-ui/uui-color-swatches": "1.12.2", - "@umbraco-ui/uui-combobox": "1.12.2", - "@umbraco-ui/uui-combobox-list": "1.12.2", - "@umbraco-ui/uui-css": "1.12.1", - "@umbraco-ui/uui-dialog": "1.12.2", - "@umbraco-ui/uui-dialog-layout": "1.12.2", - "@umbraco-ui/uui-file-dropzone": "1.12.2", - "@umbraco-ui/uui-file-preview": "1.12.2", - "@umbraco-ui/uui-form": "1.12.2", - "@umbraco-ui/uui-form-layout-item": "1.12.2", - "@umbraco-ui/uui-form-validation-message": "1.12.2", - "@umbraco-ui/uui-icon": "1.12.2", - "@umbraco-ui/uui-icon-registry": "1.12.2", - "@umbraco-ui/uui-icon-registry-essential": "1.12.2", - "@umbraco-ui/uui-input": "1.12.2", - "@umbraco-ui/uui-input-file": "1.12.2", - "@umbraco-ui/uui-input-lock": "1.12.2", - "@umbraco-ui/uui-input-password": "1.12.2", - "@umbraco-ui/uui-keyboard-shortcut": "1.12.2", - "@umbraco-ui/uui-label": "1.12.2", - "@umbraco-ui/uui-loader": "1.12.2", - "@umbraco-ui/uui-loader-bar": "1.12.2", - "@umbraco-ui/uui-loader-circle": "1.12.2", - "@umbraco-ui/uui-menu-item": "1.12.2", - "@umbraco-ui/uui-modal": "1.12.2", - "@umbraco-ui/uui-pagination": "1.12.2", - "@umbraco-ui/uui-popover": "1.12.2", - "@umbraco-ui/uui-popover-container": "1.12.2", - "@umbraco-ui/uui-progress-bar": "1.12.2", - "@umbraco-ui/uui-radio": "1.12.2", - "@umbraco-ui/uui-range-slider": "1.12.2", - "@umbraco-ui/uui-ref": "1.12.2", - "@umbraco-ui/uui-ref-list": "1.12.2", - "@umbraco-ui/uui-ref-node": "1.12.2", - "@umbraco-ui/uui-ref-node-data-type": "1.12.2", - "@umbraco-ui/uui-ref-node-document-type": "1.12.2", - "@umbraco-ui/uui-ref-node-form": "1.12.2", - "@umbraco-ui/uui-ref-node-member": "1.12.2", - "@umbraco-ui/uui-ref-node-package": "1.12.2", - "@umbraco-ui/uui-ref-node-user": "1.12.2", - "@umbraco-ui/uui-scroll-container": "1.12.2", - "@umbraco-ui/uui-select": "1.12.2", - "@umbraco-ui/uui-slider": "1.12.2", - "@umbraco-ui/uui-symbol-expand": "1.12.2", - "@umbraco-ui/uui-symbol-file": "1.12.2", - "@umbraco-ui/uui-symbol-file-dropzone": "1.12.2", - "@umbraco-ui/uui-symbol-file-thumbnail": "1.12.2", - "@umbraco-ui/uui-symbol-folder": "1.12.2", - "@umbraco-ui/uui-symbol-lock": "1.12.2", - "@umbraco-ui/uui-symbol-more": "1.12.2", - "@umbraco-ui/uui-symbol-sort": "1.12.2", - "@umbraco-ui/uui-table": "1.12.2", - "@umbraco-ui/uui-tabs": "1.12.2", - "@umbraco-ui/uui-tag": "1.12.2", - "@umbraco-ui/uui-textarea": "1.12.2", - "@umbraco-ui/uui-toast-notification": "1.12.2", - "@umbraco-ui/uui-toast-notification-container": "1.12.2", - "@umbraco-ui/uui-toast-notification-layout": "1.12.2", - "@umbraco-ui/uui-toggle": "1.12.2", - "@umbraco-ui/uui-visually-hidden": "1.12.2" + "@umbraco-ui/uui-action-bar": "1.13.0", + "@umbraco-ui/uui-avatar": "1.13.0", + "@umbraco-ui/uui-avatar-group": "1.13.0", + "@umbraco-ui/uui-badge": "1.13.0", + "@umbraco-ui/uui-base": "1.13.0", + "@umbraco-ui/uui-boolean-input": "1.13.0", + "@umbraco-ui/uui-box": "1.13.0", + "@umbraco-ui/uui-breadcrumbs": "1.13.0", + "@umbraco-ui/uui-button": "1.13.0", + "@umbraco-ui/uui-button-copy-text": "1.13.0", + "@umbraco-ui/uui-button-group": "1.13.0", + "@umbraco-ui/uui-button-inline-create": "1.13.0", + "@umbraco-ui/uui-card": "1.13.0", + "@umbraco-ui/uui-card-block-type": "1.13.0", + "@umbraco-ui/uui-card-content-node": "1.13.0", + "@umbraco-ui/uui-card-media": "1.13.0", + "@umbraco-ui/uui-card-user": "1.13.0", + "@umbraco-ui/uui-caret": "1.13.0", + "@umbraco-ui/uui-checkbox": "1.13.0", + "@umbraco-ui/uui-color-area": "1.13.0", + "@umbraco-ui/uui-color-picker": "1.13.0", + "@umbraco-ui/uui-color-slider": "1.13.0", + "@umbraco-ui/uui-color-swatch": "1.13.0", + "@umbraco-ui/uui-color-swatches": "1.13.0", + "@umbraco-ui/uui-combobox": "1.13.0", + "@umbraco-ui/uui-combobox-list": "1.13.0", + "@umbraco-ui/uui-css": "1.13.0", + "@umbraco-ui/uui-dialog": "1.13.0", + "@umbraco-ui/uui-dialog-layout": "1.13.0", + "@umbraco-ui/uui-file-dropzone": "1.13.0", + "@umbraco-ui/uui-file-preview": "1.13.0", + "@umbraco-ui/uui-form": "1.13.0", + "@umbraco-ui/uui-form-layout-item": "1.13.0", + "@umbraco-ui/uui-form-validation-message": "1.13.0", + "@umbraco-ui/uui-icon": "1.13.0", + "@umbraco-ui/uui-icon-registry": "1.13.0", + "@umbraco-ui/uui-icon-registry-essential": "1.13.0", + "@umbraco-ui/uui-input": "1.13.0", + "@umbraco-ui/uui-input-file": "1.13.0", + "@umbraco-ui/uui-input-lock": "1.13.0", + "@umbraco-ui/uui-input-password": "1.13.0", + "@umbraco-ui/uui-keyboard-shortcut": "1.13.0", + "@umbraco-ui/uui-label": "1.13.0", + "@umbraco-ui/uui-loader": "1.13.0", + "@umbraco-ui/uui-loader-bar": "1.13.0", + "@umbraco-ui/uui-loader-circle": "1.13.0", + "@umbraco-ui/uui-menu-item": "1.13.0", + "@umbraco-ui/uui-modal": "1.13.0", + "@umbraco-ui/uui-pagination": "1.13.0", + "@umbraco-ui/uui-popover": "1.13.0", + "@umbraco-ui/uui-popover-container": "1.13.0", + "@umbraco-ui/uui-progress-bar": "1.13.0", + "@umbraco-ui/uui-radio": "1.13.0", + "@umbraco-ui/uui-range-slider": "1.13.0", + "@umbraco-ui/uui-ref": "1.13.0", + "@umbraco-ui/uui-ref-list": "1.13.0", + "@umbraco-ui/uui-ref-node": "1.13.0", + "@umbraco-ui/uui-ref-node-data-type": "1.13.0", + "@umbraco-ui/uui-ref-node-document-type": "1.13.0", + "@umbraco-ui/uui-ref-node-form": "1.13.0", + "@umbraco-ui/uui-ref-node-member": "1.13.0", + "@umbraco-ui/uui-ref-node-package": "1.13.0", + "@umbraco-ui/uui-ref-node-user": "1.13.0", + "@umbraco-ui/uui-scroll-container": "1.13.0", + "@umbraco-ui/uui-select": "1.13.0", + "@umbraco-ui/uui-slider": "1.13.0", + "@umbraco-ui/uui-symbol-expand": "1.13.0", + "@umbraco-ui/uui-symbol-file": "1.13.0", + "@umbraco-ui/uui-symbol-file-dropzone": "1.13.0", + "@umbraco-ui/uui-symbol-file-thumbnail": "1.13.0", + "@umbraco-ui/uui-symbol-folder": "1.13.0", + "@umbraco-ui/uui-symbol-lock": "1.13.0", + "@umbraco-ui/uui-symbol-more": "1.13.0", + "@umbraco-ui/uui-symbol-sort": "1.13.0", + "@umbraco-ui/uui-table": "1.13.0", + "@umbraco-ui/uui-tabs": "1.13.0", + "@umbraco-ui/uui-tag": "1.13.0", + "@umbraco-ui/uui-textarea": "1.13.0", + "@umbraco-ui/uui-toast-notification": "1.13.0", + "@umbraco-ui/uui-toast-notification-container": "1.13.0", + "@umbraco-ui/uui-toast-notification-layout": "1.13.0", + "@umbraco-ui/uui-toggle": "1.13.0", + "@umbraco-ui/uui-visually-hidden": "1.13.0" } }, "node_modules/@umbraco-ui/uui-action-bar": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-action-bar/-/uui-action-bar-1.12.2.tgz", - "integrity": "sha512-ZWTO7//oKxo5vpA+RypyxpfVMPi5f8f1uevbJ8PMdizDi67VxN1kxYA4geMzG8OQ+x5IGp01DCTtVeAx3qoJbg==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-action-bar/-/uui-action-bar-1.13.0.tgz", + "integrity": "sha512-0AGQ1zsUZT1wHKx+01JkRKLNtpjCS/SqEy/NVHUyYIGPimr6NQDM9Ok00LZKpZVwxcvArdy38XaAz6SijlaTqg==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2", - "@umbraco-ui/uui-button-group": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0", + "@umbraco-ui/uui-button-group": "1.13.0" } }, "node_modules/@umbraco-ui/uui-avatar": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-avatar/-/uui-avatar-1.12.2.tgz", - "integrity": "sha512-b/TkEIGJoouqCZLIBl/c0veJg8imImd35Ed+R1VPlcHFXrgpO8C54Fr0AEwsM5x5OeTtkfvs/18pveLPucraww==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-avatar/-/uui-avatar-1.13.0.tgz", + "integrity": "sha512-w+DwB9PUcnR0y0CzeNQA2638PjF2Dswiyuoxa2ryggcy38ihypj0Fj8FpzRSe5rax2JMtpJnuoDPwUpqVwGfOQ==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0" } }, "node_modules/@umbraco-ui/uui-avatar-group": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-avatar-group/-/uui-avatar-group-1.12.2.tgz", - "integrity": "sha512-QdymxxxC6qCRAu8vAM7Owgbe/ubZ+BL+wu0qk8RXz77CVORgLpiFeUM4YwOapOXvtogXR6haxf8m3/7nxedqdg==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-avatar-group/-/uui-avatar-group-1.13.0.tgz", + "integrity": "sha512-G8lIknUuhy+swW9Xz7qN3fp0L5Xhx4d5C2Q9WbW316GeseLYCm2eRhXDLpiEzIMxoVYtA9P0gbkuxLFDkznc+Q==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-avatar": "1.12.2", - "@umbraco-ui/uui-base": "1.12.2" + "@umbraco-ui/uui-avatar": "1.13.0", + "@umbraco-ui/uui-base": "1.13.0" } }, "node_modules/@umbraco-ui/uui-badge": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-badge/-/uui-badge-1.12.2.tgz", - "integrity": "sha512-jkD8rHvunbUDNZfDCekuP5DI23ufBZD+8Y3FHv5aLOAbRm9XrbJ0B4QHyKQoglQ2Yao6iKeYq+nxzG2x88Z7Dw==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-badge/-/uui-badge-1.13.0.tgz", + "integrity": "sha512-z7Z5IZwcfFJDFIPnBDfuCv+YkBHafn15oi4rNmNVynaM/iFJ+W3NAE7EmdWMDZzuDeQngbFpoRx1Ub7I4mqsng==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0" } }, "node_modules/@umbraco-ui/uui-base": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-base/-/uui-base-1.12.2.tgz", - "integrity": "sha512-EyPrP28teYlGeeTZvmq+4wzP8Gh9A963HbZ1nQ3oyGj+twN6QjEKUF7W4VVZ8RvFoyS1/6bWkRODuZAzAwX31g==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-base/-/uui-base-1.13.0.tgz", + "integrity": "sha512-VG0xlVzuq74qKaWn+eaTcTblR6HCi9YyrNohLLhHVAJuelmcgoPwHdNzkjoaWXlq16XKnB5Kmb6BgEtVmSQZ5w==", "license": "MIT", "peerDependencies": { "lit": ">=2.8.0" } }, "node_modules/@umbraco-ui/uui-boolean-input": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-boolean-input/-/uui-boolean-input-1.12.2.tgz", - "integrity": "sha512-/NGwAPgXLiaDIMwunTDth21jQ0+5ajH3gJ5JJH6IGIq+N2g7babAEKybkZybYq+mxH//7ljH/uKDHI9IztW58g==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-boolean-input/-/uui-boolean-input-1.13.0.tgz", + "integrity": "sha512-WsP+W5/4Fcp9sg0gFlfh8FyIzaczRC4kc2LxT3haljflgQTMVwV4MGGadOYg89hVpD0C4dZaqp69sskLWc6fWQ==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0" } }, "node_modules/@umbraco-ui/uui-box": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-box/-/uui-box-1.12.2.tgz", - "integrity": "sha512-JUxqsRjqUbZ5NM5S1w40NUlHUHPIcMFqYTeCq+nLHE9WSLchym3NN+0NZjS2+qpO70kYPGlKf39mahy+rbGP9Q==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-box/-/uui-box-1.13.0.tgz", + "integrity": "sha512-msIz5NerPKx7bnTyEyMjihnfxSTlslU+FyE4DsUUwZT6vtFxT2Dt74UbO8cg0ut9GoBjR1wpn4qNTW3xRxfdiA==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2", - "@umbraco-ui/uui-css": "1.12.1" + "@umbraco-ui/uui-base": "1.13.0", + "@umbraco-ui/uui-css": "1.13.0" } }, "node_modules/@umbraco-ui/uui-breadcrumbs": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-breadcrumbs/-/uui-breadcrumbs-1.12.2.tgz", - "integrity": "sha512-P/L4q5whw1/HVMMUmzgq5CYOu3ZoLmtlTUoOnTXj+g5R0ziX5ikjJWF1JnLa6M7ES43aB/7su9GeyvOMkcxMpA==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-breadcrumbs/-/uui-breadcrumbs-1.13.0.tgz", + "integrity": "sha512-DG/m4bZ3bPTryrN6mDQMmabXPmvKcVlsfjuhJ/UDazq6T/4DVfB6YrXk6q+4N6X4njg88CO/V6ObnyB7RE+flQ==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0" } }, "node_modules/@umbraco-ui/uui-button": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-button/-/uui-button-1.12.2.tgz", - "integrity": "sha512-x3zF+GLwfpc6W2vB3xLRX6g+hdKdEWMKLXtfl+WPOkocu8+EYzodrUHQg24/lO43j7ovy8c3t+zN8OhjnZMu2Q==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-button/-/uui-button-1.13.0.tgz", + "integrity": "sha512-dJWr9fKQFB4CRMJ23oRmkuzN45ESuxDn1nwgLw0TLhJrAWo5uoyTL1k/IuNdg0C3+BqNIzC5B8m5YC2S+BpPlA==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2", - "@umbraco-ui/uui-icon-registry-essential": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0", + "@umbraco-ui/uui-icon-registry-essential": "1.13.0" + } + }, + "node_modules/@umbraco-ui/uui-button-copy-text": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-button-copy-text/-/uui-button-copy-text-1.13.0.tgz", + "integrity": "sha512-/4j4PnxNRAyeD2LYA+dyyCZurOPnGioQfl2iFIE/B2znBvJR2JHrnCLwcAqawI+YhHxGgyKNck7BCKihYQxkow==", + "license": "MIT", + "dependencies": { + "@umbraco-ui/uui-base": "1.13.0", + "@umbraco-ui/uui-button": "1.13.0" } }, "node_modules/@umbraco-ui/uui-button-group": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-button-group/-/uui-button-group-1.12.2.tgz", - "integrity": "sha512-VxWICU4hmYCORmo8JzXgSyzpa82/M3OyTxfn/kX+jHg0rk9vMg4JArQJp4NF9qhgOWsHx0ED5yURTTOtbNqFTQ==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-button-group/-/uui-button-group-1.13.0.tgz", + "integrity": "sha512-Pksx35rtKriOUO9IP1ETnQDoBnoiRzwheM8fmqeo44jSPsr7emaQrI3BOwqeOuD7KfPRIVnzwLdm14K4Zw6tZA==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0" } }, "node_modules/@umbraco-ui/uui-button-inline-create": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-button-inline-create/-/uui-button-inline-create-1.12.2.tgz", - "integrity": "sha512-YvJTwlA2ZUhepHsmc/WwP3OqG7lkrlVmAcgG7uBbasNMwDYtLWcudMrv/NSHFrCpQe0VePyr7U4YtJqyQrbDTg==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-button-inline-create/-/uui-button-inline-create-1.13.0.tgz", + "integrity": "sha512-6XtJ/nZpVDkYFiWEqbr5uz5CJ2Yqled4W7zAsh53QuCCYFgyU6yU9AFtrhPRwC9I27XzmBTQRZgCkQFWuEuL5A==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0" } }, "node_modules/@umbraco-ui/uui-card": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-card/-/uui-card-1.12.2.tgz", - "integrity": "sha512-/FqFYrQxKu38+s3y7XpiO8wW7Z2T7cyst2LvMajG+3U9KPi4A0pwxaRBlli4ay79/9V9uFEGTc4dKjB+jFKl6w==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-card/-/uui-card-1.13.0.tgz", + "integrity": "sha512-fBskLWqFoquKfgFK6bJ4lM0V30XZCZcJjjwTUmSjRFvklyF3csL7W9bKB9hs+aFu0/GDQlVqOBa5tA4RLpcj0w==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0" } }, "node_modules/@umbraco-ui/uui-card-block-type": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-card-block-type/-/uui-card-block-type-1.12.2.tgz", - "integrity": "sha512-aydgrznHaIUrJpHrwftjPtnaXVQOLe+r6VWrtyWNSPM4ivUeT5WaH/FVMc90Q6yWfIF3y2a3yCIQAGEqAXghhQ==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-card-block-type/-/uui-card-block-type-1.13.0.tgz", + "integrity": "sha512-YBDHZ+76oz27+P9v8YSr3OwWs3eftqy2d3Gg/sxh3Y6n9gI2TdXtJgev9GVL2FpifZXM2A1ySzh8MscC2HLJIA==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2", - "@umbraco-ui/uui-card": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0", + "@umbraco-ui/uui-card": "1.13.0" } }, "node_modules/@umbraco-ui/uui-card-content-node": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-card-content-node/-/uui-card-content-node-1.12.2.tgz", - "integrity": "sha512-yuNlbrjwphzMPv2xMHca8YUr+NH7FqeP0EjVjhhDSsOJVUZ8uj8Udoq4YIkypOAGAyG+N63jCzLvVTTR71LxGA==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-card-content-node/-/uui-card-content-node-1.13.0.tgz", + "integrity": "sha512-IYe/AUaJ7Pspd+zSQlJMRIUzzF7+dLnq6ApezC9b93mEEhB4WwX+BbzfHbbhyNxMv9Za9gBKZljIH3RluPWnog==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2", - "@umbraco-ui/uui-card": "1.12.2", - "@umbraco-ui/uui-icon": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0", + "@umbraco-ui/uui-card": "1.13.0", + "@umbraco-ui/uui-icon": "1.13.0" } }, "node_modules/@umbraco-ui/uui-card-media": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-card-media/-/uui-card-media-1.12.2.tgz", - "integrity": "sha512-37Zful2c9UhDxw7qYWR2F2wdt5Qs5yMjcE0Q5R1ZRA5SFba7qgY0W4YW2iAAPMk2xvDyueaTnbVy1v6gG/jtYw==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-card-media/-/uui-card-media-1.13.0.tgz", + "integrity": "sha512-ohRFE1FqmYNJ7VXKjzMqjhCfzfabL9bLOpJet0+VXCMHUomHZv9UHQTI1ibp71BMp934vWT3kqGgco6RYqujSQ==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2", - "@umbraco-ui/uui-card": "1.12.2", - "@umbraco-ui/uui-symbol-file": "1.12.2", - "@umbraco-ui/uui-symbol-folder": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0", + "@umbraco-ui/uui-card": "1.13.0", + "@umbraco-ui/uui-symbol-file": "1.13.0", + "@umbraco-ui/uui-symbol-folder": "1.13.0" } }, "node_modules/@umbraco-ui/uui-card-user": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-card-user/-/uui-card-user-1.12.2.tgz", - "integrity": "sha512-fwuYQvXjjiLTv0ykDpg+GpcoG3af3ZHUPTRbDa5W8ygAYlTRUvENSXc2qOUocy9XmXOa0p+P0NhenVSqOJpSIw==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-card-user/-/uui-card-user-1.13.0.tgz", + "integrity": "sha512-lAB2IuXvNK8l/n+D9s9cNNUUvBdZE2Uy3UDc0QJla3qo2RLsyM4pSgVeS0Ve+GOI1A4vyK8Sfx68cDptW04Vaw==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-avatar": "1.12.2", - "@umbraco-ui/uui-base": "1.12.2", - "@umbraco-ui/uui-card": "1.12.2" + "@umbraco-ui/uui-avatar": "1.13.0", + "@umbraco-ui/uui-base": "1.13.0", + "@umbraco-ui/uui-card": "1.13.0" } }, "node_modules/@umbraco-ui/uui-caret": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-caret/-/uui-caret-1.12.2.tgz", - "integrity": "sha512-7zVDVzvLszVld9E/pGSGFRgpp+rIipB1sY/r4xDYQ70g+ljlegOfMc3bvGs/topcMM+IlcQO8EOotlps4P44Jw==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-caret/-/uui-caret-1.13.0.tgz", + "integrity": "sha512-OCrJISFcRvB6V1+xPS+AjGEp+ue3vyegTRJsLEOVbyCHbrzFwNUKLc2EFYz2rxOGjcFs7Z9M8I6eoLUuMxDQAQ==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0" } }, "node_modules/@umbraco-ui/uui-checkbox": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-checkbox/-/uui-checkbox-1.12.2.tgz", - "integrity": "sha512-C6SSAUq9JfHHWCz9LLlOOmwET1vDsLKKiYv94LIqn8Zj4H3f1bRgUnSfVPVCfy1+p//Ut8SLw2vTFcTz0F21EA==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-checkbox/-/uui-checkbox-1.13.0.tgz", + "integrity": "sha512-9ywXUZgC8kMLEgsx1JFH0iftSeI8zzVDVECYq36+dVuz0iWtXfUjb5ygSoUX0guiACVY5gNba/H+t9R+3FbUgw==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2", - "@umbraco-ui/uui-boolean-input": "1.12.2", - "@umbraco-ui/uui-icon-registry-essential": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0", + "@umbraco-ui/uui-boolean-input": "1.13.0", + "@umbraco-ui/uui-icon-registry-essential": "1.13.0" } }, "node_modules/@umbraco-ui/uui-color-area": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-color-area/-/uui-color-area-1.12.2.tgz", - "integrity": "sha512-W5qOBIvTiHGxFJcc1h3H+CdLHLY4K6QRIXU7I2BEII296PbUMwKaA8WFXAvwSq1KzmCkOJP2hPa4yxQ/qKBzJQ==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-color-area/-/uui-color-area-1.13.0.tgz", + "integrity": "sha512-X7CyxicQYE5VR5iXSY0IsPym0pSYWFLQ9KDgzVIDM3fvoM+KpiGYrDhGTgrHrTpJ3EE8JO06fPrx/mJ2NyxOyQ==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2", + "@umbraco-ui/uui-base": "1.13.0", "colord": "^2.9.3" } }, "node_modules/@umbraco-ui/uui-color-picker": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-color-picker/-/uui-color-picker-1.12.2.tgz", - "integrity": "sha512-t/FB6h1rdNzPa94dIfjGG50yRNmk/7wMjrktKjkZHt+wGWKvjM+I1RjatArZbCAmSV4EQH/7hqyvP6R1OoLIog==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-color-picker/-/uui-color-picker-1.13.0.tgz", + "integrity": "sha512-ROkz+5+ecZ9GbctpaynL9CeMdXhamY2wPfwjVHyp/QERLXsvhlXIojD0n11Fp4i9FzQsiHb328T5aTnBZ3tqcw==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2", - "@umbraco-ui/uui-popover-container": "1.12.2", + "@umbraco-ui/uui-base": "1.13.0", + "@umbraco-ui/uui-popover-container": "1.13.0", "colord": "^2.9.3" } }, "node_modules/@umbraco-ui/uui-color-slider": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-color-slider/-/uui-color-slider-1.12.2.tgz", - "integrity": "sha512-00LxQigqY+04eG0IzHY//Uf010u50DeCQ88ZvCV1MjPNH7T4auEC2/H/O7FYoHhwQB6Ez+ZpYA9ds/NbmTCuVg==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-color-slider/-/uui-color-slider-1.13.0.tgz", + "integrity": "sha512-om/OwXDVDNsy0HZIuIv6VXoi5aFBU7KtHfiq7/OLnnWtO5MQREwBCTVthhSFfe7LaZSZnFhFn89hrmw7hfhljQ==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0" } }, "node_modules/@umbraco-ui/uui-color-swatch": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-color-swatch/-/uui-color-swatch-1.12.2.tgz", - "integrity": "sha512-fDODPeuKirwSyIOhEY46J7Ml5RJcuaeMyLBshWT9bl8pNts9zIlKSvn3oSlZ9mZ7N/Ym/3R2c+33i5avoA+rIA==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-color-swatch/-/uui-color-swatch-1.13.0.tgz", + "integrity": "sha512-tiT274ldYjDMFeBQBx3yGu7HgYaNrxjNIrcktlsddfWxxjJ3UNu08YdoP4DqJOi6limQhadBllCBa9oyz4iOig==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2", - "@umbraco-ui/uui-icon-registry-essential": "1.12.2", + "@umbraco-ui/uui-base": "1.13.0", + "@umbraco-ui/uui-icon-registry-essential": "1.13.0", "colord": "^2.9.3" } }, "node_modules/@umbraco-ui/uui-color-swatches": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-color-swatches/-/uui-color-swatches-1.12.2.tgz", - "integrity": "sha512-kr9gYjYFQR8mavmDJS+I2t/n5wC6kWbCaZHnJzcs3unOX2jzKHnOqJ8N05y8vc2NZP1pOKSOzoIN1Y6N3qxU+g==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-color-swatches/-/uui-color-swatches-1.13.0.tgz", + "integrity": "sha512-DAZ9cAxIp+kGFeGteDCgt+Om0vcuacmjtT98N1meP/EkPgJf6y21o3y4oySeQMAhWXznr3DBxyHHKN1Jt3do8Q==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2", - "@umbraco-ui/uui-color-swatch": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0", + "@umbraco-ui/uui-color-swatch": "1.13.0" } }, "node_modules/@umbraco-ui/uui-combobox": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-combobox/-/uui-combobox-1.12.2.tgz", - "integrity": "sha512-ln7IoQQJ65zknIl5k44E61S0DgW1e7fo/IEuMlgbrmkPnEbkLqV5HVYXIR3377VvfwqbZ44npxegOZBUuuWGlw==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-combobox/-/uui-combobox-1.13.0.tgz", + "integrity": "sha512-8lKmqQMQkh+yMA4iFonDLwpNf6EZ+pYXhJ2Xcum14WT6UI6BgiQvuM2nmRrkWhqA7Wx0tTAAdP5ILPAl5lENRQ==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2", - "@umbraco-ui/uui-button": "1.12.2", - "@umbraco-ui/uui-combobox-list": "1.12.2", - "@umbraco-ui/uui-icon": "1.12.2", - "@umbraco-ui/uui-popover-container": "1.12.2", - "@umbraco-ui/uui-scroll-container": "1.12.2", - "@umbraco-ui/uui-symbol-expand": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0", + "@umbraco-ui/uui-button": "1.13.0", + "@umbraco-ui/uui-combobox-list": "1.13.0", + "@umbraco-ui/uui-icon": "1.13.0", + "@umbraco-ui/uui-popover-container": "1.13.0", + "@umbraco-ui/uui-scroll-container": "1.13.0", + "@umbraco-ui/uui-symbol-expand": "1.13.0" } }, "node_modules/@umbraco-ui/uui-combobox-list": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-combobox-list/-/uui-combobox-list-1.12.2.tgz", - "integrity": "sha512-tBtQgQKB6kgPwRSkXM9kShNfC4Zed7V1hstCjVFy1wkRU+IinVYiN28NMNdSvDWmmxkRcIVOt7lY70T0fgPPMw==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-combobox-list/-/uui-combobox-list-1.13.0.tgz", + "integrity": "sha512-ZVRouGMb7VH5wD8a0kE1t71oUMD1gUvFdACPWTjunpgM0ZXk1wOGGtS3vsEaTAkbQ8gABPpsYnCaWBt0MR+RpA==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0" } }, "node_modules/@umbraco-ui/uui-css": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-css/-/uui-css-1.12.1.tgz", - "integrity": "sha512-cWdoJw3OjdZ5QUoXhUufp/8mdGkVJ4DiI7/NgPaU2GrMbo+c1Q2cx4ST2/K0Q7nY6qa4P4WCSLMoFGyFoOwLKQ==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-css/-/uui-css-1.13.0.tgz", + "integrity": "sha512-6crDukueGm9t5CBU+d/icouGELQQIQsfi/qT7J6qISRZTvBjoT0FxUxUtpXsIQs1H0qgULhwx8PTKnfQ/AMZFA==", "license": "MIT", "peerDependencies": { "lit": ">=2.8.0" } }, "node_modules/@umbraco-ui/uui-dialog": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-dialog/-/uui-dialog-1.12.2.tgz", - "integrity": "sha512-YfHE4RTRKJiSi/ZCnZMJs+eImXx64JrZmu39bEb6FBAnMpqAMxeq70Nll4Nk43nL6liARv1bXP8OKZd2b7CPgQ==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-dialog/-/uui-dialog-1.13.0.tgz", + "integrity": "sha512-RzePOwJrhBEYBZAwgvNkIro+cVirLxgaIGNFUgnvoWIovHJNOuSso65MtcGON3nvuQ4JxE8SIOTE/hwT04R7Ag==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2", - "@umbraco-ui/uui-css": "1.12.1" + "@umbraco-ui/uui-base": "1.13.0", + "@umbraco-ui/uui-css": "1.13.0" } }, "node_modules/@umbraco-ui/uui-dialog-layout": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-dialog-layout/-/uui-dialog-layout-1.12.2.tgz", - "integrity": "sha512-Xy+Ocwia0xRcpUUARTdXgSgf5NIG2mlneDkiz6dsrIsFZ1IysXCnfh/4dXw57fneO+PyHI86bDwb9aFlWvve7Q==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-dialog-layout/-/uui-dialog-layout-1.13.0.tgz", + "integrity": "sha512-m8eoCEz0dugWmqrmRw2vHae3k7oYjr53JiOkb8viCMh7veQo4EM0zqZgdCwADs1wES8woOX5zdttp9JtqYenRw==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0" } }, "node_modules/@umbraco-ui/uui-file-dropzone": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-file-dropzone/-/uui-file-dropzone-1.12.2.tgz", - "integrity": "sha512-5B/1umH72IrxwlQ+4ivKDSIXXcGbfFuhvo98v1nuIF5MGl6wmoiG/lDilhny08RJMHwlcRkdYCtCChtuWEyVUg==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-file-dropzone/-/uui-file-dropzone-1.13.0.tgz", + "integrity": "sha512-1TFkyeNB3qWWhgc7NYudxXOc3v0cBRuRpVYPA3xocfVkqCG2PgEc7ePW18CtUuuGntGwv0E0Oni2bfSLrqVmuQ==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2", - "@umbraco-ui/uui-symbol-file-dropzone": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0", + "@umbraco-ui/uui-symbol-file-dropzone": "1.13.0" } }, "node_modules/@umbraco-ui/uui-file-preview": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-file-preview/-/uui-file-preview-1.12.2.tgz", - "integrity": "sha512-Oxkm7x3V/aCHPQDNh8loMESWswYCyDJeZazbhGig7mU6zbms7Vl3Vm46CIKEBva6IMy1p1AsNOgSjY4wmIvXsw==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-file-preview/-/uui-file-preview-1.13.0.tgz", + "integrity": "sha512-ZEW2q6If0+3WWHnQp9UPdL+rcI4zUKlyvELDU1JDzx/IVDFJb8f7fI5qhzQjl4kXCVI54Ch4WkBie6RDpNSqVg==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2", - "@umbraco-ui/uui-symbol-file": "1.12.2", - "@umbraco-ui/uui-symbol-file-thumbnail": "1.12.2", - "@umbraco-ui/uui-symbol-folder": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0", + "@umbraco-ui/uui-symbol-file": "1.13.0", + "@umbraco-ui/uui-symbol-file-thumbnail": "1.13.0", + "@umbraco-ui/uui-symbol-folder": "1.13.0" } }, "node_modules/@umbraco-ui/uui-form": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-form/-/uui-form-1.12.2.tgz", - "integrity": "sha512-35CEeSCODTMaJi7JlvBl988tB0MIbocNg5ewCLeqm2CLVvW1UQi4V+835CY1fjgiR6D8co6Kz6KCR/9aibX5Gg==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-form/-/uui-form-1.13.0.tgz", + "integrity": "sha512-Y5Wgl3AcixLbPHJJK2yqdga5NMHx5Jv3YvG69+AdPkgzyNmCtdbDitV8ex2ysNYMO3WbBRdYIjbI5pYRl3xn5Q==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0" } }, "node_modules/@umbraco-ui/uui-form-layout-item": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-form-layout-item/-/uui-form-layout-item-1.12.2.tgz", - "integrity": "sha512-qc4JJhhtM7HsVT1DBtw2xRbayLEWvFDwXROXgmwTUMOVZJ9qGFpSN6EWymm9fr+gBYcbwii6ZKg0ujIeHDILTw==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-form-layout-item/-/uui-form-layout-item-1.13.0.tgz", + "integrity": "sha512-GKNjsvUBbl2ba9e7b88Vk7HuMO9exnGDRpmQ94PSH/rOUF44ri4mPTPFU2k9DCvIkSs7lxDvotXE7kQ5IPQYBw==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2", - "@umbraco-ui/uui-form-validation-message": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0", + "@umbraco-ui/uui-form-validation-message": "1.13.0" } }, "node_modules/@umbraco-ui/uui-form-validation-message": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-form-validation-message/-/uui-form-validation-message-1.12.2.tgz", - "integrity": "sha512-MQ0nNQcNpawQUZA+JGYPbGW8Go9b9nj4loK26Op0qvInQpbe9mHbHAhWOdbPTBLoJSYnXpo90/3E9ycU9p9PEQ==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-form-validation-message/-/uui-form-validation-message-1.13.0.tgz", + "integrity": "sha512-x1E84q6L8DbpBkoS+ykdvmoEUcObXYhym6nhh2lK2TAn7vZu+XD+Osd5rgy5ycZ4YtYnCqetlaPwQwAFqFiSHA==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0" } }, "node_modules/@umbraco-ui/uui-icon": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-icon/-/uui-icon-1.12.2.tgz", - "integrity": "sha512-sAz08736Jt1y6pPZSBafNT04w9YCnck46whCZUhx7FX7kiKctJX0Xr9GVZH99YAGxnbXnNx0YsN6PqFfz92FzA==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-icon/-/uui-icon-1.13.0.tgz", + "integrity": "sha512-TKmQi4n8ZV6v228U6zi9f38g/Nu4ok1cbvoIiSfSvmSYNXD1weuWK5y7Ph7EGr6jL5T5vKbDhjcZUIjzBOVWAA==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0" } }, "node_modules/@umbraco-ui/uui-icon-registry": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-icon-registry/-/uui-icon-registry-1.12.2.tgz", - "integrity": "sha512-CXinq7uwca8QzIMCMBkUNkHoq9KV5ioxJSY4+2b5s7lpS8zK+Zoe+zzt5QL/bOCET6TTGZifpCiZRIiRy1Mffg==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-icon-registry/-/uui-icon-registry-1.13.0.tgz", + "integrity": "sha512-/w7EN7Exi7ST0olPuxFLFm8rw3Mt2QhZsqQWQXXYnb4hTC+ot27IDahQmlLOx85+h/KH3Qz+Tn2NgM3BEdQQ5w==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2", - "@umbraco-ui/uui-icon": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0", + "@umbraco-ui/uui-icon": "1.13.0" } }, "node_modules/@umbraco-ui/uui-icon-registry-essential": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-icon-registry-essential/-/uui-icon-registry-essential-1.12.2.tgz", - "integrity": "sha512-s53QmcXVzrLDwpVP3WZW1pekG95kVrjgHDyTo2T3a2J4ovvEEYpZ8/Jmf/3lJVj5CpvQV+I1l/Wx3zFtniT91g==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-icon-registry-essential/-/uui-icon-registry-essential-1.13.0.tgz", + "integrity": "sha512-CcuBNg06ewGM6OhwjXCQKm5QDYXySCcc7TQajJ14kfMXtdcO8ls6eI2D8t+Hkc4YN7TQaUeGgzMF746f4TiiNQ==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2", - "@umbraco-ui/uui-icon-registry": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0", + "@umbraco-ui/uui-icon-registry": "1.13.0" } }, "node_modules/@umbraco-ui/uui-input": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-input/-/uui-input-1.12.2.tgz", - "integrity": "sha512-t/QsptHm9jMH8A0iWBvRZ2s/qeKaO5vp1Zf5oBG9RtgZoS7cNowdMQPVp6mXzc1gICc217lNFsxt+MUGVCud2w==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-input/-/uui-input-1.13.0.tgz", + "integrity": "sha512-2GwLio1SDBofYLZjds47X6Fxq29ORgQBZaD9xwowFaoWCsG+WIFsE7VaE4KgPASUOQYoMMzFZX3F2TdvbjPEAg==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0" } }, "node_modules/@umbraco-ui/uui-input-file": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-input-file/-/uui-input-file-1.12.2.tgz", - "integrity": "sha512-X/AeocW+1XLroIqsuxB4OBTmFy1n7ZzfxNrtwEsaqM1rbrA3RGY2EIjnt311eoxk9DvFWeG50/gICV85sWWNmQ==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-input-file/-/uui-input-file-1.13.0.tgz", + "integrity": "sha512-wKmFAzThstZPeCOtNtdAX8SZ09T0mJQEA1g+l6EaCV5ikPLSO0kiqmv3P0p0IDf6WSX29+UhcSp2hOVzR+cELg==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-action-bar": "1.12.2", - "@umbraco-ui/uui-base": "1.12.2", - "@umbraco-ui/uui-button": "1.12.2", - "@umbraco-ui/uui-file-dropzone": "1.12.2", - "@umbraco-ui/uui-icon": "1.12.2", - "@umbraco-ui/uui-icon-registry-essential": "1.12.2" + "@umbraco-ui/uui-action-bar": "1.13.0", + "@umbraco-ui/uui-base": "1.13.0", + "@umbraco-ui/uui-button": "1.13.0", + "@umbraco-ui/uui-file-dropzone": "1.13.0", + "@umbraco-ui/uui-icon": "1.13.0", + "@umbraco-ui/uui-icon-registry-essential": "1.13.0" } }, "node_modules/@umbraco-ui/uui-input-lock": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-input-lock/-/uui-input-lock-1.12.2.tgz", - "integrity": "sha512-EAjzK0xZbjEEyIqHjMdDPmBQMSay/vbYj65YHb8aJBtYyL17qIqVRMEB9D/tV7cGBp5FbpkpZtb5qWmNVFQtcg==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-input-lock/-/uui-input-lock-1.13.0.tgz", + "integrity": "sha512-qChhwO5NsA8es9X41HJ73sXtmvKUF90WBBL8PYgESLkL7zQdvhe9wfJhVjZ1WMJkOc6F7uTAJbawuEVXSX0uKA==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2", - "@umbraco-ui/uui-button": "1.12.2", - "@umbraco-ui/uui-icon": "1.12.2", - "@umbraco-ui/uui-input": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0", + "@umbraco-ui/uui-button": "1.13.0", + "@umbraco-ui/uui-icon": "1.13.0", + "@umbraco-ui/uui-input": "1.13.0" } }, "node_modules/@umbraco-ui/uui-input-password": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-input-password/-/uui-input-password-1.12.2.tgz", - "integrity": "sha512-CYNHiaDmaBDXUYE6XFpO3lpmClwjC6aCgtlYFe8SqFlcyU1KABal36PopxpnIMuKrmMv3LFHw1Jpg5dnjk/hNA==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-input-password/-/uui-input-password-1.13.0.tgz", + "integrity": "sha512-1ljgXM1Ux2J73J2mNd01Xh1Bk7Por0MXk6fQ4TP/qp4A5ohF6CmiBVNWSBkWLfEY7TNHfvOIIIiDGfc0Ko0UFw==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2", - "@umbraco-ui/uui-icon-registry-essential": "1.12.2", - "@umbraco-ui/uui-input": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0", + "@umbraco-ui/uui-icon-registry-essential": "1.13.0", + "@umbraco-ui/uui-input": "1.13.0" } }, "node_modules/@umbraco-ui/uui-keyboard-shortcut": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-keyboard-shortcut/-/uui-keyboard-shortcut-1.12.2.tgz", - "integrity": "sha512-X4ZpIP6AQbx5d3zLVVGqHKIDBli4HwkOsTnepHYFPTykTTiCVBxRiVQ5TRgAM4GjeEaUe/mOyPOCYkVBJ0bKmA==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-keyboard-shortcut/-/uui-keyboard-shortcut-1.13.0.tgz", + "integrity": "sha512-zKh674a19swyaZiLI/vCws56H4P+lUCIQxu+/U3280zGQqp35vCj0RhvbU2zA4QCCvTEWSrOOQwyu019zEEz5w==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0" } }, "node_modules/@umbraco-ui/uui-label": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-label/-/uui-label-1.12.2.tgz", - "integrity": "sha512-D4j2XBwtYq2tK/pP+QJuLSxg5NtD+jGEy5DO2qhoRm2VPzGjCWw3irdykVoTIgMRjJiWOQMvE8tpgqPBsBygHw==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-label/-/uui-label-1.13.0.tgz", + "integrity": "sha512-BcfvqdFybY0Vb7TVinfHDLrAyhmtayz5ZGXwnTZpwyg7IP+pPZrFunrhcPPTPIrvJEw/j7qgpfC2AKMsiaZq7A==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0" } }, "node_modules/@umbraco-ui/uui-loader": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-loader/-/uui-loader-1.12.2.tgz", - "integrity": "sha512-vbAds+57/wFelt+F4YdCdZ9dyR9DjBtEEPhcJDbd5yLwbgKnl+ITL6pDtu2kT45cVMacaxxZAdP5SzcwVSnR7g==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-loader/-/uui-loader-1.13.0.tgz", + "integrity": "sha512-AmNcIX7XNtW9dc29TdlMgTcTJBxU7MCae9iivNLLfFrg3VblltCPeeCOcLx77rw/k9o/IWrhLOsN81Q0HPfs7g==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0" } }, "node_modules/@umbraco-ui/uui-loader-bar": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-loader-bar/-/uui-loader-bar-1.12.2.tgz", - "integrity": "sha512-nC678xqAJFH8vKqhewfFi1CEZ8dR5r/s88REILZOwQM8S0c2z9J4bxesmjpr2ZIQ4KQ2l7BCzBdWbyqs+GUHUA==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-loader-bar/-/uui-loader-bar-1.13.0.tgz", + "integrity": "sha512-wRJl2n6VesXV5z7EOz3W8DKDo2XLbpb4a9HZHauDtnGl9aNQggcFYBXTrLAvqg2Nuir2g52kQT9mDcQiTDxJLQ==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0" } }, "node_modules/@umbraco-ui/uui-loader-circle": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-loader-circle/-/uui-loader-circle-1.12.2.tgz", - "integrity": "sha512-CmjdLDdUM1pRp3dE+WKVEc9dTIQlvYtPtJIjCyNwP403YcKvreGMW6wKMxV/+69IEPjRtTjyaKyprNGnRVRpwg==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-loader-circle/-/uui-loader-circle-1.13.0.tgz", + "integrity": "sha512-efDzwo7uC7bs60boAvJNtqI7CFTe/4R5xdyi9khSF9w/0WnMRgIsY9h7xQLWCycjC/Nvoe/UwBZQ6P5khkdfaw==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0" } }, "node_modules/@umbraco-ui/uui-menu-item": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-menu-item/-/uui-menu-item-1.12.2.tgz", - "integrity": "sha512-CvrkPWvfRLGSWFNDq+SCLKUm08DjWzw/nYtGLSmQL9QsXa/SMJMtmmcw2H+OYzk4d/9ME+r0GRralZgDlx08iA==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-menu-item/-/uui-menu-item-1.13.0.tgz", + "integrity": "sha512-Rl3y+gXyN4ZLdzhhmCW1dWWC53erFVnVV1OTm2paYk1w13du/T4S+X7J0uyobrc1E3iFTjAFNh0UuvHxRsxtcQ==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2", - "@umbraco-ui/uui-loader-bar": "1.12.2", - "@umbraco-ui/uui-symbol-expand": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0", + "@umbraco-ui/uui-loader-bar": "1.13.0", + "@umbraco-ui/uui-symbol-expand": "1.13.0" } }, "node_modules/@umbraco-ui/uui-modal": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-modal/-/uui-modal-1.12.2.tgz", - "integrity": "sha512-0ZJuMwdpIFDD+vi59gakhL4jsEb+/f/sMIH4yE/np8ccbZNnGSIT0RJPe94lv6b2wPKrjVIQ1VGGrqzY2znh2A==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-modal/-/uui-modal-1.13.0.tgz", + "integrity": "sha512-uiBmQg4gE3S9lreABkLbr4kSRdZAbkxzavBZ27DTDWjeez5Zn+sqy+FckPGct+HZheTdXgGF+M4YRypZj7gOhg==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0" } }, "node_modules/@umbraco-ui/uui-pagination": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-pagination/-/uui-pagination-1.12.2.tgz", - "integrity": "sha512-TvP0GKewUZndpO7rHlPqbsw5dPqmKBJXs33berhn/crIE2pGnPVEBey3NYLIHBd5CZI5ufn+gGn4NPNVGF+Q9A==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-pagination/-/uui-pagination-1.13.0.tgz", + "integrity": "sha512-RtD+szI8P+7y2tKBLLPJyCOlfS504LgQqD4pUOZbxemsQmMe37OZ1CiiqfrNJVEv4TbMHP0WvoRiLFDawICXnw==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2", - "@umbraco-ui/uui-button": "1.12.2", - "@umbraco-ui/uui-button-group": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0", + "@umbraco-ui/uui-button": "1.13.0", + "@umbraco-ui/uui-button-group": "1.13.0" } }, "node_modules/@umbraco-ui/uui-popover": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-popover/-/uui-popover-1.12.2.tgz", - "integrity": "sha512-gvSUe7wox0VY/wEm8LLUV//aLVwz7twswWQd9QniR6MdahvwhjWhQ90hTVpir3VAj5GFBaTfSitqeFBElyT1og==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-popover/-/uui-popover-1.13.0.tgz", + "integrity": "sha512-fpN0x+9p7cxrKiBYzEbpAYuIFYxWlUDrv4jYw3+oEI1ZP2wlS4dKphxhNtLreGrbaYsSUXe8Vgx9wy3eFawfug==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0" } }, "node_modules/@umbraco-ui/uui-popover-container": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-popover-container/-/uui-popover-container-1.12.2.tgz", - "integrity": "sha512-2z//P49B1zyoN/tWdVZp6Q+8qRnbbtGb4CBveXZeuuirzNxhMOA/E77Y0aJmzjn8yTRoooMGmYzRYd+4zJGNJQ==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-popover-container/-/uui-popover-container-1.13.0.tgz", + "integrity": "sha512-pNvfRLjFzRY7j8bJ1LDGROBZ1+h4HbKqr7O4bs8z8ZfdrxqHb1k/BssbSNt25JFmoHDSRZbFs3yBL9jhVEr/Xg==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0" } }, "node_modules/@umbraco-ui/uui-progress-bar": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-progress-bar/-/uui-progress-bar-1.12.2.tgz", - "integrity": "sha512-PW5TKeg58Lv3WfX6Sp/EPWCsl9oYqQovvl/7y0pxy7xFnSYma5tFQ+XX0mD1rKw7ed3Unlek/Ma9u79Z9GVhDQ==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-progress-bar/-/uui-progress-bar-1.13.0.tgz", + "integrity": "sha512-QRgydD21AJfmv89WWiim8O/7XR0BTsWP73lga2Tbj3OU/8jjw0vcqmjzf0uhOir5SL1Y0Y1CT/SPUjgxc0VC0g==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0" } }, "node_modules/@umbraco-ui/uui-radio": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-radio/-/uui-radio-1.12.2.tgz", - "integrity": "sha512-KfXA6+YtueMsxQTjzjp8gVgGJAk17BW9d4Da4h7kYhZGekfWK996ohEgGWF7vj/Q4Ai229OuX7zNJdufCGZIfw==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-radio/-/uui-radio-1.13.0.tgz", + "integrity": "sha512-SsSqyte7n2KEqEjmtI2ajUX6m0AL6nreSZ53IGViMBim8bTcW4oBq5Wbp3dll+s88WvExppozE2iA1kLgjijOw==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0" } }, "node_modules/@umbraco-ui/uui-range-slider": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-range-slider/-/uui-range-slider-1.12.2.tgz", - "integrity": "sha512-m4ATwJYdasF4jfLLHxfFw+2n0uQmZdOha4vxzHbTreyO/gnwn8hLfICA1h9zjoZIqUGMtQ9KlhIaUezvgMpGFw==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-range-slider/-/uui-range-slider-1.13.0.tgz", + "integrity": "sha512-gplKOzASnz2spVVkwgj1kYAPFRqp4KRuDFlWyr2IY5luvTajUZC6JOB4aQDs5+OMbgYvF4G+PKFEapuYnR7gNg==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0" } }, "node_modules/@umbraco-ui/uui-ref": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref/-/uui-ref-1.12.2.tgz", - "integrity": "sha512-uwQmaiuwphD1ereZLBhcUDMUaUosO0sV6NrBOh9KLWhkmeqYjuFFG2+CRxdhQrKb1ltZfLzAmzYfGp6AoFkvmw==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref/-/uui-ref-1.13.0.tgz", + "integrity": "sha512-jgVgHFa7/5zcg86Rtkecp7XO9FENQUQ7uMZyCAUHYCPur/n0CKNBrVjwQ/PEI0o1VR+eRGUG5iByZgQW1yWTtQ==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0" } }, "node_modules/@umbraco-ui/uui-ref-list": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-list/-/uui-ref-list-1.12.2.tgz", - "integrity": "sha512-b7reEiwfGy17Ns3qFQoO0TnngxAUclhj0jR7gLIk7dHNJZw45r37crPMkVs2CnRj657nn4DmghjQgCLDSCre9w==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-list/-/uui-ref-list-1.13.0.tgz", + "integrity": "sha512-9Nw03JwymtGnkqvnEeRhmSS+F7Xlzp7yef4R/WdZEIYASV42vwCIAj5Wdj2JI+Apc0ZVZ4hCQhbfNVsr+e7ddQ==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0" } }, "node_modules/@umbraco-ui/uui-ref-node": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node/-/uui-ref-node-1.12.2.tgz", - "integrity": "sha512-RFma47ixyYNdcMwel1+dte5fGnULczWZpzh1CvAiI9JNKzy9ItUFi70UiFKMrkOY0gT+910xgeWhk4jPTJJgpQ==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node/-/uui-ref-node-1.13.0.tgz", + "integrity": "sha512-otjHMgmaekLl3iPwDjLgJ6H7HIWF3QqNLNAiHnGZ1pdShJyLfajvHxnDULeUuI6zRDdjavzz3fhPUnjzyraYpQ==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2", - "@umbraco-ui/uui-icon": "1.12.2", - "@umbraco-ui/uui-ref": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0", + "@umbraco-ui/uui-icon": "1.13.0", + "@umbraco-ui/uui-ref": "1.13.0" } }, "node_modules/@umbraco-ui/uui-ref-node-data-type": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-data-type/-/uui-ref-node-data-type-1.12.2.tgz", - "integrity": "sha512-s8eviANQTHaNXSVa4U61wJcPCAwzUj6YrIvw7T3Ioe4HgIQvTotIWaCkek+p4ttl3irnnBsRXfGdW+yWuaEnEg==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-data-type/-/uui-ref-node-data-type-1.13.0.tgz", + "integrity": "sha512-M5+7ekzpoNJmjD8YvH5TQPb1ENbIwnjyXyUv7IiXV2AtTF/H/g0t4pEU+SYhlrAe61VyW5TedtAMWK+oDKrWUg==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2", - "@umbraco-ui/uui-ref-node": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0", + "@umbraco-ui/uui-ref-node": "1.13.0" } }, "node_modules/@umbraco-ui/uui-ref-node-document-type": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-document-type/-/uui-ref-node-document-type-1.12.2.tgz", - "integrity": "sha512-Dg+SAAcMSqr0EvX6IY2jjGk9I8bbgo1Pe6L5c9g0CBPmQ8H+0qOKDdSojWzn/qohtfdAIvN+21Q0AvCovVA9rA==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-document-type/-/uui-ref-node-document-type-1.13.0.tgz", + "integrity": "sha512-AsTjfuAKak/cdaZaJCjuu19YyYwC2FunPP2fz2PuXPr7ULcmo78oYIZY6YJPUsPlBSMN5PIIr9iox3ob6Dd+Rg==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2", - "@umbraco-ui/uui-ref-node": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0", + "@umbraco-ui/uui-ref-node": "1.13.0" } }, "node_modules/@umbraco-ui/uui-ref-node-form": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-form/-/uui-ref-node-form-1.12.2.tgz", - "integrity": "sha512-jnPNmLK8LvZenH2MY9Ea8R+4JkuDNMoBfUFVnhaLg+dHp7tsrg9opIONDNOIJJTYHryHdZ+/ksvQGW6ZWlACgQ==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-form/-/uui-ref-node-form-1.13.0.tgz", + "integrity": "sha512-ySHrq0xH4l69NH12pXzfPrrMG9fRnHF7ul+iKSrPvqUdWnsNpEzYakGvt6XXji1x3ogZEKnKMlzAXrHZDL8LoA==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2", - "@umbraco-ui/uui-ref-node": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0", + "@umbraco-ui/uui-ref-node": "1.13.0" } }, "node_modules/@umbraco-ui/uui-ref-node-member": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-member/-/uui-ref-node-member-1.12.2.tgz", - "integrity": "sha512-ft0SRlDZ49eRbV3Xk7JtDfR5UraULoeTfYe/MHZkmAzhrDKeTtnd9oVYUQ27qsYs6EVneQ8byydwXrmSMloc8A==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-member/-/uui-ref-node-member-1.13.0.tgz", + "integrity": "sha512-UiSAxsPjpivbI0Hm1fZ1O7nTgkSjPigi9z5RWT4P0viiYetrc8ggJpZ5cDFEQH2xBe40qfBQQGr8vJ4Gsz3opg==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2", - "@umbraco-ui/uui-ref-node": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0", + "@umbraco-ui/uui-ref-node": "1.13.0" } }, "node_modules/@umbraco-ui/uui-ref-node-package": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-package/-/uui-ref-node-package-1.12.2.tgz", - "integrity": "sha512-TX9PCPpeOWpl5vK8o/QjXgEWXOt7z0lQK8wlUHYSz+a3/wcmDZD0J/OXkmpvVyS2lXe6pqR8HJ/+FwcnrOm/9w==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-package/-/uui-ref-node-package-1.13.0.tgz", + "integrity": "sha512-iANsUZDFjLQdalKVI007LuNDlEsruh88peWiBrDN47HtRZlZ/tLV67Ljd5oRjZhAFZLjjFQ1jl0DOkkD3lKIFw==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2", - "@umbraco-ui/uui-ref-node": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0", + "@umbraco-ui/uui-ref-node": "1.13.0" } }, "node_modules/@umbraco-ui/uui-ref-node-user": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-user/-/uui-ref-node-user-1.12.2.tgz", - "integrity": "sha512-sBMICX3vxJd9WjJPWqVnhUhJL+JMuzGzZVUfHlzIjrdpANZZ6FrhnvYkHXhW83KsrfwLsY5/3CXr22xZSsVajA==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-user/-/uui-ref-node-user-1.13.0.tgz", + "integrity": "sha512-XckwV6nrJjWeeBZX7j28fJdJZlzugyhfXIacp6+AnFHrL5NXjsb3Hi4ZLt00CurcxmhVVta+J5uvmOLSVi7Myw==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2", - "@umbraco-ui/uui-ref-node": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0", + "@umbraco-ui/uui-ref-node": "1.13.0" } }, "node_modules/@umbraco-ui/uui-scroll-container": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-scroll-container/-/uui-scroll-container-1.12.2.tgz", - "integrity": "sha512-MI5lpiUeLg1Scf2xHaFzBADAW8CAwcU2yEKOOfOgONuaP6PiUA80YqtE2hCm5BmoldbOYBufCJlFFi2cyuq7HQ==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-scroll-container/-/uui-scroll-container-1.13.0.tgz", + "integrity": "sha512-3Qnl6aGxRs2FYvZzskZYFXnDsej5vBHalu/0b7VKfTPdUMJuAtR+1rz+veLvm9hL5pf9sJbSx4IZe+BubrYmnw==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0" } }, "node_modules/@umbraco-ui/uui-select": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-select/-/uui-select-1.12.2.tgz", - "integrity": "sha512-TOGodRtumlh1cgC9iKxsV/jEGH2w7bKBjIhyQ42sJ3DXyLPcXVEUooZYmh/3dOf7R/7eHSsZOxH/sskbQlNS2A==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-select/-/uui-select-1.13.0.tgz", + "integrity": "sha512-JF4Jtgc/H61tdPVD01kkBRkUofWatrUG9diRMuaGPvQsWEVNvbCJKXJ+Fww9pMQts25EidLhhtqG2hX3ZSsgYA==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0" } }, "node_modules/@umbraco-ui/uui-slider": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-slider/-/uui-slider-1.12.2.tgz", - "integrity": "sha512-Eg0XqIIXwibxq7y4qe0OB9+t7QLetnlBY3i2BSeMPMfarG1NQ6jhWVOv//RKmZ1kqfUh9MCE5tya9T9h68zR1A==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-slider/-/uui-slider-1.13.0.tgz", + "integrity": "sha512-r4QE+V0LTyn1NAyHsLBkHAvu1jzqfpQ9mOIzwt7ekpuKUrlbaB+LWVo+lxiX7ShUVHxL+0squ0/1CNrLquz0AA==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0" } }, "node_modules/@umbraco-ui/uui-symbol-expand": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-expand/-/uui-symbol-expand-1.12.2.tgz", - "integrity": "sha512-zW/ClcJuPCe7ELYHCyoSMm6sGWVPLDbjz8TlE1qambwmFefqTfv69p3nB0YF7QnB+7LR5ePOV63vjZSYWT9/Aw==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-expand/-/uui-symbol-expand-1.13.0.tgz", + "integrity": "sha512-nR6Ld0ZrWQX0Ijk1y+3sRXMaAh87uaQkhcIHgS9Ziz+F1JbCf5WCygla3Xux5t+zpxhPVy6yvZc3iWJxQMp1TA==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0" } }, "node_modules/@umbraco-ui/uui-symbol-file": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-file/-/uui-symbol-file-1.12.2.tgz", - "integrity": "sha512-+af95C4eZOdOpqJrt8br1pic1P/NPrnyC1Q4sKLaCReuBqBdaWLl502kAXjlkkoJZsv4GsyzmjiSbBkbRIZCFQ==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-file/-/uui-symbol-file-1.13.0.tgz", + "integrity": "sha512-F3+MyQaGDUYr+Z0VyBmZaIirPKSkON2Gu6jrb8kX04UuqVPIRvoxjubGTmu6wU5M0ATRt/NIG5CzYJp33R8bGA==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0" } }, "node_modules/@umbraco-ui/uui-symbol-file-dropzone": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-file-dropzone/-/uui-symbol-file-dropzone-1.12.2.tgz", - "integrity": "sha512-8vmHw+nYZdWgeUVNCJhTvJg4iw0zTCxQ6H5tguN1Qepc+XD1NdlRTi8yicnEKSLcq20qzI3KxxwToNLnFKseSQ==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-file-dropzone/-/uui-symbol-file-dropzone-1.13.0.tgz", + "integrity": "sha512-ko3+WSdgd3pG3SI5eUPJ/VbOYTW86AW6EmYDrsALAdRdKhQ5Kusbe7M8Uds8BB3EJ9GT9+xcjocLNMCTxV8soA==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0" } }, "node_modules/@umbraco-ui/uui-symbol-file-thumbnail": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-file-thumbnail/-/uui-symbol-file-thumbnail-1.12.2.tgz", - "integrity": "sha512-tQsQTjgZti4zB327Xd2ql8lz9rj07aVwKfJcV2bClHwyQbRb370KRAS4m6MiaT587+6qVcjRwG3Sya1blpNMfg==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-file-thumbnail/-/uui-symbol-file-thumbnail-1.13.0.tgz", + "integrity": "sha512-fgdJcecVz39MuFTTneD9yI8K/4KZQkHaARfvcWmc2rvRD8S5HzGcp/a+y0zOmzLIpKi3Sjlwc/4d123nE3V0NQ==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0" } }, "node_modules/@umbraco-ui/uui-symbol-folder": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-folder/-/uui-symbol-folder-1.12.2.tgz", - "integrity": "sha512-v3bYEpbomOmt2J+LYuB3HqzzZW+LzK/Ufpvr3Km9Gl4eXjPUnrAzBn3PSdq7w5ZvR3vfEV017coPTSX0wncjKQ==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-folder/-/uui-symbol-folder-1.13.0.tgz", + "integrity": "sha512-TIh45JTtvv6l+/7+UtSQxxyvtIyiv9tVv1QC4SKesW19opUkWiaNd5awaKlshi+Iu9CbXvCVwxTJ6TK5z3hsaA==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0" } }, "node_modules/@umbraco-ui/uui-symbol-lock": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-lock/-/uui-symbol-lock-1.12.2.tgz", - "integrity": "sha512-syW+kTYq7W9coBc7ov1BbDhRTmAMh77GacfQt4XSayHgE/hhO6UvG95uk0POaooQ0UfBW1bDv9r3/wJNZBTfmw==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-lock/-/uui-symbol-lock-1.13.0.tgz", + "integrity": "sha512-/39U8n0DfHNI4I2X1WX8dJv6pSOHeJMvpyS1Cla54Q45gtt7RHMU55aNEGBZoF19oPV2W74gl7vfcHGTtnPKNQ==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0" } }, "node_modules/@umbraco-ui/uui-symbol-more": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-more/-/uui-symbol-more-1.12.2.tgz", - "integrity": "sha512-lxcw/B6zl3TJ7mZDYgXKvX6D/1gYYLmrLvKV7J5iSTGxDNiLji8NAXu2/rgffKMGIFaLfZicEENSLLX/JF8QGQ==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-more/-/uui-symbol-more-1.13.0.tgz", + "integrity": "sha512-mEwbSezTZfG77MUdWrFGXkMaSBHpC899lToDONTnQurkvLBxbBRBlT+xhHo54psAzJX7C6NLRvExTMztGU1OeQ==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0" } }, "node_modules/@umbraco-ui/uui-symbol-sort": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-sort/-/uui-symbol-sort-1.12.2.tgz", - "integrity": "sha512-iDLs6Ph9BGrLRifU6oGZr7UCOsoOKk5NMxnP7eP/sy0geq30kHlI/mcBu6XUrtYiFsy3+l8b8gSFdLxEHQrcgQ==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-sort/-/uui-symbol-sort-1.13.0.tgz", + "integrity": "sha512-BDcvHXrueX3d6sFcQa5jzxlV1C0OdhymN1Q5GzXpby2xLJTjNbeGISdgHGCxjLPsmHUAAZ7XCGR8pqI0F+8Hpg==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0" } }, "node_modules/@umbraco-ui/uui-table": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-table/-/uui-table-1.12.2.tgz", - "integrity": "sha512-aHSArtedBiQBcz++eXomQvTys4Q0P7/SNEUcsG/CbPS7uDWXQZJK/KajtI7rMjU/d63dtavIXq9v0LatKTM/sw==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-table/-/uui-table-1.13.0.tgz", + "integrity": "sha512-CKoPIsqURMtR6PwaSs4UvB56LVLMTop93gArI//yN9Ox1/w7awxnnkRN2skpKIbtDHrbEBI//NJE50jPoS82eg==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0" } }, "node_modules/@umbraco-ui/uui-tabs": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-tabs/-/uui-tabs-1.12.2.tgz", - "integrity": "sha512-20ZmwGiLFtFA5a1CkBo713Ua508d0VwaCWnaKkhoE8Kl/ttlWhlKg+PSB26wkcwB0QonWrH1clMRalwKqRhjvg==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-tabs/-/uui-tabs-1.13.0.tgz", + "integrity": "sha512-b50xJ1Xka8nu51GCR8n2RZtCFjwTYDXV5zQF+s5KXpgC6A8mahCvzmmINHdgGnKm1JNG3c8abhwrueyxxVdI8Q==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2", - "@umbraco-ui/uui-button": "1.12.2", - "@umbraco-ui/uui-popover-container": "1.12.2", - "@umbraco-ui/uui-symbol-more": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0", + "@umbraco-ui/uui-button": "1.13.0", + "@umbraco-ui/uui-popover-container": "1.13.0", + "@umbraco-ui/uui-symbol-more": "1.13.0" } }, "node_modules/@umbraco-ui/uui-tag": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-tag/-/uui-tag-1.12.2.tgz", - "integrity": "sha512-15omQCZmBeW3U6E0kCoFQs3ckUsNqWOCjslGfDMe+0x0a+r5hntam05OrUlF523plD/SG6utXGI/tRYdTidh1g==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-tag/-/uui-tag-1.13.0.tgz", + "integrity": "sha512-SBY9Mi9A89jIag7URKQqXW3omDk5Eczw2LuNF7VnkXmQCuvsiRP6/BzSBCh9m0RrD4QOLSXpYGgSoLSpS7MitA==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0" } }, "node_modules/@umbraco-ui/uui-textarea": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-textarea/-/uui-textarea-1.12.2.tgz", - "integrity": "sha512-dlT0fZ0zjdj4BouWhjqA4UBBj4YRFGxWZkMhbP/+g2lAnsl11GN2yMzOvfv7R6Zo3pmV6/qavtEk+XRKBaAihg==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-textarea/-/uui-textarea-1.13.0.tgz", + "integrity": "sha512-H4XChy1m5gq43eySQ3Zp/AsBvh35Gk0VLijFxdhCfV+HHpuyrt0fJsYnjq1W1xoqhyt7h84YRpNIJMyAIm2WHQ==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0" } }, "node_modules/@umbraco-ui/uui-toast-notification": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-toast-notification/-/uui-toast-notification-1.12.2.tgz", - "integrity": "sha512-gtVAoGPd4G0VWVdSyyhaDQupzuLLfFzuaVTVai0970hLAZAzcbodG3W382iPhPIbHwQX7T8LMV02ScPfGuhjbA==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-toast-notification/-/uui-toast-notification-1.13.0.tgz", + "integrity": "sha512-o45G8hWXgqcfGaJM+nhCTDSpevREJd+gPKT5XhTkD2wA99/kevdedmlYIgKS+9wONLk5A0j8qnsbWntinbb+rw==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2", - "@umbraco-ui/uui-button": "1.12.2", - "@umbraco-ui/uui-css": "1.12.1", - "@umbraco-ui/uui-icon": "1.12.2", - "@umbraco-ui/uui-icon-registry-essential": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0", + "@umbraco-ui/uui-button": "1.13.0", + "@umbraco-ui/uui-css": "1.13.0", + "@umbraco-ui/uui-icon": "1.13.0", + "@umbraco-ui/uui-icon-registry-essential": "1.13.0" } }, "node_modules/@umbraco-ui/uui-toast-notification-container": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-toast-notification-container/-/uui-toast-notification-container-1.12.2.tgz", - "integrity": "sha512-Zu70rQzYV+QegV2kwNmpUDGU75z6u9B3ujFzVN2u+oi1y0kkR6wgXIczExQ4PeqEBZM252ZWbCIDQ66gX1+thw==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-toast-notification-container/-/uui-toast-notification-container-1.13.0.tgz", + "integrity": "sha512-9O0t73v7qkb3+VE8i0pD1vo33tNt1U7t3L6699jNMZZr+7R6a5YOAVrFt+gs+kQcQXWt0HCfQxhKJ8opLoBOyw==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2", - "@umbraco-ui/uui-toast-notification": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0", + "@umbraco-ui/uui-toast-notification": "1.13.0" } }, "node_modules/@umbraco-ui/uui-toast-notification-layout": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-toast-notification-layout/-/uui-toast-notification-layout-1.12.2.tgz", - "integrity": "sha512-b0kgRwc744RpBjJW5URKRwGXzbGWU12OuFqIXq6BSl8LuFci9uh62V2J7Jj5xnx6v1jqZi/RRRKRwiqQOa3AWw==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-toast-notification-layout/-/uui-toast-notification-layout-1.13.0.tgz", + "integrity": "sha512-yhz8msOc1ngA//oBDefrR8pagTbvAenBiyk/fPuEwGQriM43e8bbVCJvhmrsTuAzAL8nn/ilKhAU5lrkn2rAmg==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2", - "@umbraco-ui/uui-css": "1.12.1" + "@umbraco-ui/uui-base": "1.13.0", + "@umbraco-ui/uui-css": "1.13.0" } }, "node_modules/@umbraco-ui/uui-toggle": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-toggle/-/uui-toggle-1.12.2.tgz", - "integrity": "sha512-hQCQJUEYjNL/2a/vldTlkFhTLiAF+P1UKxhPDqxCQlO/GsOihefcRhchOPmx4ptvjadvSc7J/MJPhAYC2RB0gw==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-toggle/-/uui-toggle-1.13.0.tgz", + "integrity": "sha512-tHzG/Lh9vRLjPu7EhFupaD7jkpVenyEM3iIsA24wBVKmqJGxacpuuuOwpTv6vGGiIYSKfRDXTDk07Q6MHDSy4g==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2", - "@umbraco-ui/uui-boolean-input": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0", + "@umbraco-ui/uui-boolean-input": "1.13.0" } }, "node_modules/@umbraco-ui/uui-visually-hidden": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-visually-hidden/-/uui-visually-hidden-1.12.2.tgz", - "integrity": "sha512-3VC4UUcalOl93pkwVWxbSxnIEyN9e5Soy+V3HKQDifWZ536NjBRvMzw+jib5BFLBzrfmRjX68lxNbE2t/EDydA==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-visually-hidden/-/uui-visually-hidden-1.13.0.tgz", + "integrity": "sha512-1ayTJylWnpAl0VQE7X2PBJCKLZ15R+xfZ3yy4ygT751k4wML26nvdWscp/tYfl4MteqrHtNJKTRTFoQ1Dn/r/g==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" + "@umbraco-ui/uui-base": "1.13.0" } }, "node_modules/@ungap/structured-clone": { diff --git a/src/Umbraco.Web.UI.Client/package.json b/src/Umbraco.Web.UI.Client/package.json index a5f314d1986d..27414554225c 100644 --- a/src/Umbraco.Web.UI.Client/package.json +++ b/src/Umbraco.Web.UI.Client/package.json @@ -19,8 +19,8 @@ }, "dependencies": { "@microsoft/signalr": "8.0.7", - "@umbraco-ui/uui": "1.12.2", - "@umbraco-ui/uui-css": "1.12.1", + "@umbraco-ui/uui": "1.13.0", + "@umbraco-ui/uui-css": "1.13.0", "ace-builds": "1.31.1", "angular": "1.8.3", "angular-animate": "1.8.3", 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 72c5f3fec1e5..7e293a94f2a3 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 @@ -1033,9 +1033,12 @@ $scope.content.variants.forEach(variant => variant.save = false); //ensure the save flag is set for the active variant selectedVariant.save = true; + $scope.page.previewButtonState = "busy"; performSave({ saveMethod: $scope.saveMethod(), action: "save" }).then(function (data) { + $scope.page.previewButtonState = "success"; openPreviewWindow(url, urlTarget); }, function (err) { + $scope.page.previewButtonState = "error"; //validation issues .... }); } diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbmediagrid.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbmediagrid.directive.js index 01de877b799f..dfb33bd50ad5 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbmediagrid.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbmediagrid.directive.js @@ -351,14 +351,17 @@ Use this directive to generate a thumbnail grid of media items. // sort function scope.sortBy = function (item) { if (scope.sortColumn === "updateDate") { - return [-item['isFolder'],item['updateDate']]; + return [-item['isFolder'],parseUpdateDate(item['updateDate'])]; } else { return [-item['isFolder'],item['name']]; } }; - + function parseUpdateDate(date) { + var parsedDate = Date.parse(date); + return isNaN(parsedDate) ? date : parsedDate; + } } var directive = { diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js index dab8d8629ba9..9261b40c4c2b 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js @@ -852,7 +852,7 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { orderBy: options.orderBy, orderDirection: options.orderDirection, orderBySystemField: toBool(options.orderBySystemField), - filter: options.filter, + filter: encodeURIComponent(options.filter), cultureName: options.cultureName })), 'Failed to retrieve children for content item ' + parentId); diff --git a/src/Umbraco.Web.UI.Client/src/views/components/umb-locked-field.html b/src/Umbraco.Web.UI.Client/src/views/components/umb-locked-field.html index 9c4e694335af..b60bc0c13c5c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/umb-locked-field.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-locked-field.html @@ -25,7 +25,8 @@ title="{{ngModel}}" focus-when="{{!locked}}" umb-select-when="{{!locked}}" - ng-blur="lock()" /> + ng-blur="lock()" + ng-maxlength="255" /> @@ -46,6 +47,11 @@ ng-if="serverValidationField.length > 0" ng-message="valServerField">{{lockedFieldForm.lockedField.errorMsg}} +
    + Invalid alias +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/publishedsnapshotcache.controller.js b/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/publishedsnapshotcache.controller.js index 5270892fa59a..264e24d5641d 100644 --- a/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/publishedsnapshotcache.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/publishedsnapshotcache.controller.js @@ -1,4 +1,4 @@ -function publishedSnapshotCacheController($scope, $http, umbRequestHelper, localizationService, overlayService) { +function publishedSnapshotCacheController($scope, $http, umbRequestHelper, localizationService, overlayService) { var vm = this; @@ -94,12 +94,23 @@ vm.working = true; umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper.getApiUrl("publishedSnapshotCacheStatusBaseUrl", "RebuildDbCache")), - 'Failed to rebuild the cache.') - .then(function (result) { - vm.working = false; - vm.status = result; - }); + $http.post(umbRequestHelper.getApiUrl("publishedSnapshotCacheStatusBaseUrl", "RebuildDbCacheInBackground")), "Failed to queue the rebuild task.") + .then(function () { + const interval = setInterval(function () { + $http.get(umbRequestHelper.getApiUrl("publishedSnapshotCacheStatusBaseUrl", "GetStatus")) + .then(function (result) { + if (!result.data.toString().startsWith("Rebuild in progress")) { + vm.working = false; + vm.status = result.data; + clearInterval(interval); + } + }, function () { + vm.working = false; + vm.status = "Could not retrieve rebuild cache status"; + }); + + }, 2000); + }); } function init() { diff --git a/src/Umbraco.Web.UI.Client/src/views/member/member.delete.controller.js b/src/Umbraco.Web.UI.Client/src/views/member/member.delete.controller.js index 6ce8f86ce21b..a77eaa70c878 100644 --- a/src/Umbraco.Web.UI.Client/src/views/member/member.delete.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/member/member.delete.controller.js @@ -19,7 +19,7 @@ function MemberDeleteController($scope, memberResource, treeService, navigationS treeService.removeNode($scope.currentNode); //if the current edited item is the same one as we're deleting, we need to navigate elsewhere - if (editorState.current && editorState.current.key == $scope.currentNode.id) { + if (editorState.current && editorState.current.key.replace(/-/g, "") == $scope.currentNode.id) { $location.path("/member/member/list/" + ($routeParams.listName ? $routeParams.listName : 'all-members')); } diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/blockgridentryeditors/gridblock/gridblock.editor.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/blockgridentryeditors/gridblock/gridblock.editor.html index 31d759188cb3..abc2dffde9b8 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/blockgridentryeditors/gridblock/gridblock.editor.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/blockgridentryeditors/gridblock/gridblock.editor.html @@ -22,6 +22,8 @@ user-select: none; border: none; transition: border-color 120ms, background-color 120ms; + font-size:15px; + font-family: Lato, Helvetica Neue, Helvetica, Arial, sans-serif; } .blockelement-gridblock-editor > button:hover { color: #2152A3; diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/umbBlockListPropertyEditor.component.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/umbBlockListPropertyEditor.component.js index 4ebb3ea2a9c8..62c300d7902a 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/umbBlockListPropertyEditor.component.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/umbBlockListPropertyEditor.component.js @@ -37,6 +37,9 @@ let copyAllBlocksAction = null; let deleteAllBlocksAction = null; let pasteSingleBlockAction = null; + let resetSingleBlock = null; + + let scopeOfExistence = null; var inlineEditing = false; var liveEditing = true; @@ -124,12 +127,12 @@ vm.listWrapperStyles['max-width'] = vm.model.config.maxPropertyWidth; } - // We need to ensure that the property model value is an object, this is needed for modelObject to recive a reference and keep that updated. + // We need to ensure that the property model value is an object, this is needed for modelObject to receive a reference and keep that updated. if (typeof vm.model.value !== 'object' || vm.model.value === null) {// testing if we have null or undefined value or if the value is set to another type than Object. vm.model.value = {}; } - var scopeOfExistence = $scope; + scopeOfExistence = $scope; if (vm.umbVariantContentEditors && vm.umbVariantContentEditors.getScope) { scopeOfExistence = vm.umbVariantContentEditors.getScope(); } else if(vm.umbElementEditorContent && vm.umbElementEditorContent.getScope) { @@ -179,9 +182,18 @@ useLegacyIcon: false }; + resetSingleBlock = { + labelKey: "content_removeItem", + labelTokens: [], + icon: "icon-trash", + method: requestResetSingleBlock, + isDisabled: false, + useLegacyIcon: false + }; + var propertyActions = [copyAllBlocksAction, deleteAllBlocksAction]; - var propertyActionsForSingleBlockMode = [pasteSingleBlockAction]; + var propertyActionsForSingleBlockMode = [pasteSingleBlockAction, resetSingleBlock]; if (vm.umbProperty) { if (vm.singleBlockMode) { @@ -844,6 +856,24 @@ }); } + function requestResetSingleBlock() { + localizationService.localizeMany(["content_nestedContentDeleteItem", "general_delete"]).then(function (data) { + overlayService.confirmDelete({ + title: data[1], + content: data[0], + close: function () { + overlayService.close(); + }, + submit: function () { + deleteAllBlocks(); + modelObject = blockEditorService.createModelObject(vm.model.value, vm.model.editor, vm.model.config.blocks, scopeOfExistence, $scope); + modelObject.load().then(onLoaded); + overlayService.close(); + } + }); + }); + } + function openSettingsForBlock(block, blockIndex, parentForm) { editBlock(block, true, blockIndex, parentForm); } diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/datepicker/datepicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/datepicker/datepicker.controller.js index e210e94aa1c9..5d1375a27f47 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/datepicker/datepicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/datepicker/datepicker.controller.js @@ -111,7 +111,7 @@ function dateTimePickerController($scope, angularHelper, dateHelper, validationM // $scope.hasDatetimePickerValue indicates that we had a value before the input was changed, // but now the input is empty. $scope.clearDate(); - } else if ($scope.model.datetimePickerValue) { + } else if ($scope.model.datetimePickerInputValue) { var momentDate = moment($scope.model.datetimePickerInputValue, $scope.model.config.format, true); if (!momentDate || !momentDate.isValid()) { momentDate = moment(new Date($scope.model.datetimePickerInputValue)); @@ -120,7 +120,7 @@ function dateTimePickerController($scope, angularHelper, dateHelper, validationM setDate(momentDate); } setDatePickerVal(); - flatPickr.setDate($scope.model.datetimePickerValue, false); + flatPickr.setDate($scope.model.datetimePickerInputValue, false); } } diff --git a/src/Umbraco.Web.UI.Login/package-lock.json b/src/Umbraco.Web.UI.Login/package-lock.json index c1fac838dce2..14bde36c5064 100644 --- a/src/Umbraco.Web.UI.Login/package-lock.json +++ b/src/Umbraco.Web.UI.Login/package-lock.json @@ -1,2367 +1,2379 @@ { - "name": "login", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "login", - "dependencies": { - "lit": "^3.1.2", - "msw": "^2.2.0", - "rxjs": "^7.8.1" - }, - "devDependencies": { - "@umbraco-ui/uui": "1.12.2", - "@umbraco-ui/uui-css": "1.12.1", - "typescript": "^5.3.3", - "vite": "^5.1.7" - }, - "engines": { - "node": ">=20.8", - "npm": ">=10.1" - } - }, - "node_modules/@bundled-es-modules/cookie": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@bundled-es-modules/cookie/-/cookie-2.0.0.tgz", - "integrity": "sha512-Or6YHg/kamKHpxULAdSqhGqnWFneIXu1NKvvfBBzKGwpVsYuFIQ5aBPHDnnoR3ghW1nvSkALd+EF9iMtY7Vjxw==", - "dependencies": { - "cookie": "^0.5.0" - } - }, - "node_modules/@bundled-es-modules/statuses": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@bundled-es-modules/statuses/-/statuses-1.0.1.tgz", - "integrity": "sha512-yn7BklA5acgcBr+7w064fGV+SGIFySjCKpqjcWgBAIfrAkY+4GQTJJHQMeT3V/sgz23VTEVV8TtOmkvJAhFVfg==", - "dependencies": { - "statuses": "^2.0.1" - } - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz", - "integrity": "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.12.tgz", - "integrity": "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.12.tgz", - "integrity": "sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.12.tgz", - "integrity": "sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.12.tgz", - "integrity": "sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.12.tgz", - "integrity": "sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.12.tgz", - "integrity": "sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.12.tgz", - "integrity": "sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.12.tgz", - "integrity": "sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.12.tgz", - "integrity": "sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.12.tgz", - "integrity": "sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.12.tgz", - "integrity": "sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==", - "cpu": [ - "loong64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.12.tgz", - "integrity": "sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==", - "cpu": [ - "mips64el" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.12.tgz", - "integrity": "sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==", - "cpu": [ - "ppc64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.12.tgz", - "integrity": "sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==", - "cpu": [ - "riscv64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.12.tgz", - "integrity": "sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==", - "cpu": [ - "s390x" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.12.tgz", - "integrity": "sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.12.tgz", - "integrity": "sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.12.tgz", - "integrity": "sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.12.tgz", - "integrity": "sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.12.tgz", - "integrity": "sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.12.tgz", - "integrity": "sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.12.tgz", - "integrity": "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@inquirer/confirm": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-3.0.0.tgz", - "integrity": "sha512-LHeuYP1D8NmQra1eR4UqvZMXwxEdDXyElJmmZfU44xdNLL6+GcQBS0uE16vyfZVjH8c22p9e+DStROfE/hyHrg==", - "dependencies": { - "@inquirer/core": "^7.0.0", - "@inquirer/type": "^1.2.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@inquirer/core": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-7.0.0.tgz", - "integrity": "sha512-g13W5yEt9r1sEVVriffJqQ8GWy94OnfxLCreNSOTw0HPVcszmc/If1KIf7YBmlwtX4klmvwpZHnQpl3N7VX2xA==", - "dependencies": { - "@inquirer/type": "^1.2.0", - "@types/mute-stream": "^0.0.4", - "@types/node": "^20.11.16", - "@types/wrap-ansi": "^3.0.0", - "ansi-escapes": "^4.3.2", - "chalk": "^4.1.2", - "cli-spinners": "^2.9.2", - "cli-width": "^4.1.0", - "figures": "^3.2.0", - "mute-stream": "^1.0.0", - "run-async": "^3.0.0", - "signal-exit": "^4.1.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^6.2.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@inquirer/core/node_modules/wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@inquirer/type": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-1.2.0.tgz", - "integrity": "sha512-/vvkUkYhrjbm+RolU7V1aUFDydZVKNKqKHR5TsE+j5DXgXFwrsOPcoGUJ02K0O7q7O53CU2DOTMYCHeGZ25WHA==", - "engines": { - "node": ">=18" - } - }, - "node_modules/@lit-labs/ssr-dom-shim": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.2.0.tgz", - "integrity": "sha512-yWJKmpGE6lUURKAaIltoPIE/wrbY3TEkqQt+X0m+7fQNnAv0keydnYvbiJFP1PnMhizmIWRWOG5KLhYyc/xl+g==" - }, - "node_modules/@lit/reactive-element": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-2.0.4.tgz", - "integrity": "sha512-GFn91inaUa2oHLak8awSIigYz0cU0Payr1rcFsrkf5OJ5eSPxElyZfKh0f2p9FsTiZWXQdWGJeXZICEfXXYSXQ==", - "dependencies": { - "@lit-labs/ssr-dom-shim": "^1.2.0" - } - }, - "node_modules/@mswjs/cookies": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@mswjs/cookies/-/cookies-1.1.0.tgz", - "integrity": "sha512-0ZcCVQxifZmhwNBoQIrystCb+2sWBY2Zw8lpfJBPCHGCA/HWqehITeCRVIv4VMy8MPlaHo2w2pTHFV2pFfqKPw==", - "engines": { - "node": ">=18" - } - }, - "node_modules/@mswjs/interceptors": { - "version": "0.25.16", - "resolved": "https://registry.npmjs.org/@mswjs/interceptors/-/interceptors-0.25.16.tgz", - "integrity": "sha512-8QC8JyKztvoGAdPgyZy49c9vSHHAZjHagwl4RY9E8carULk8ym3iTaiawrT1YoLF/qb449h48f71XDPgkUSOUg==", - "dependencies": { - "@open-draft/deferred-promise": "^2.2.0", - "@open-draft/logger": "^0.3.0", - "@open-draft/until": "^2.0.0", - "is-node-process": "^1.2.0", - "outvariant": "^1.2.1", - "strict-event-emitter": "^0.5.1" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@open-draft/deferred-promise": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@open-draft/deferred-promise/-/deferred-promise-2.2.0.tgz", - "integrity": "sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==" - }, - "node_modules/@open-draft/logger": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@open-draft/logger/-/logger-0.3.0.tgz", - "integrity": "sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ==", - "dependencies": { - "is-node-process": "^1.2.0", - "outvariant": "^1.4.0" - } - }, - "node_modules/@open-draft/until": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@open-draft/until/-/until-2.1.0.tgz", - "integrity": "sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==" - }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.10.0.tgz", - "integrity": "sha512-/MeDQmcD96nVoRumKUljsYOLqfv1YFJps+0pTrb2Z9Nl/w5qNUysMaWQsrd1mvAlNT4yza1iVyIu4Q4AgF6V3A==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.10.0.tgz", - "integrity": "sha512-lvu0jK97mZDJdpZKDnZI93I0Om8lSDaiPx3OiCk0RXn3E8CMPJNS/wxjAvSJJzhhZpfjXsjLWL8LnS6qET4VNQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.10.0.tgz", - "integrity": "sha512-uFpayx8I8tyOvDkD7X6n0PriDRWxcqEjqgtlxnUA/G9oS93ur9aZ8c8BEpzFmsed1TH5WZNG5IONB8IiW90TQg==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.10.0.tgz", - "integrity": "sha512-nIdCX03qFKoR/MwQegQBK+qZoSpO3LESurVAC6s6jazLA1Mpmgzo3Nj3H1vydXp/JM29bkCiuF7tDuToj4+U9Q==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.10.0.tgz", - "integrity": "sha512-Fz7a+y5sYhYZMQFRkOyCs4PLhICAnxRX/GnWYReaAoruUzuRtcf+Qnw+T0CoAWbHCuz2gBUwmWnUgQ67fb3FYw==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.10.0.tgz", - "integrity": "sha512-yPtF9jIix88orwfTi0lJiqINnlWo6p93MtZEoaehZnmCzEmLL0eqjA3eGVeyQhMtxdV+Mlsgfwhh0+M/k1/V7Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.10.0.tgz", - "integrity": "sha512-9GW9yA30ib+vfFiwjX+N7PnjTnCMiUffhWj4vkG4ukYv1kJ4T9gHNg8zw+ChsOccM27G9yXrEtMScf1LaCuoWQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.10.0.tgz", - "integrity": "sha512-X1ES+V4bMq2ws5fF4zHornxebNxMXye0ZZjUrzOrf7UMx1d6wMQtfcchZ8SqUnQPPHdOyOLW6fTcUiFgHFadRA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.10.0.tgz", - "integrity": "sha512-w/5OpT2EnI/Xvypw4FIhV34jmNqU5PZjZue2l2Y3ty1Ootm3SqhI+AmfhlUYGBTd9JnpneZCDnt3uNOiOBkMyw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.10.0.tgz", - "integrity": "sha512-q/meftEe3QlwQiGYxD9rWwB21DoKQ9Q8wA40of/of6yGHhZuGfZO0c3WYkN9dNlopHlNT3mf5BPsUSxoPuVQaw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.10.0.tgz", - "integrity": "sha512-NrR6667wlUfP0BHaEIKgYM/2va+Oj+RjZSASbBMnszM9k+1AmliRjHc3lJIiOehtSSjqYiO7R6KLNrWOX+YNSQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.10.0.tgz", - "integrity": "sha512-FV0Tpt84LPYDduIDcXvEC7HKtyXxdvhdAOvOeWMWbQNulxViH2O07QXkT/FffX4FqEI02jEbCJbr+YcuKdyyMg==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.10.0.tgz", - "integrity": "sha512-OZoJd+o5TaTSQeFFQ6WjFCiltiYVjIdsXxwu/XZ8qRpsvMQr4UsVrE5UyT9RIvsnuF47DqkJKhhVZ2Q9YW9IpQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@types/cookie": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==" - }, - "node_modules/@types/estree": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", - "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", - "dev": true - }, - "node_modules/@types/mute-stream": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/@types/mute-stream/-/mute-stream-0.0.4.tgz", - "integrity": "sha512-CPM9nzrCPPJHQNA9keH9CVkVI+WR5kMa+7XEs5jcGQ0VoAGnLv242w8lIVgwAEfmE4oufJRaTc9PNLQl0ioAow==", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/node": { - "version": "20.11.17", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.17.tgz", - "integrity": "sha512-QmgQZGWu1Yw9TDyAP9ZzpFJKynYNeOvwMJmaxABfieQoVoiVOS6MN1WSpqpRcbeA5+RW82kraAVxCCJg+780Qw==", - "dependencies": { - "undici-types": "~5.26.4" - } - }, - "node_modules/@types/statuses": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@types/statuses/-/statuses-2.0.4.tgz", - "integrity": "sha512-eqNDvZsCNY49OAXB0Firg/Sc2BgoWsntsLUdybGFOhAfCD6QJ2n9HXUIHGqt5qjrxmMv4wS8WLAw43ZkKcJ8Pw==" - }, - "node_modules/@types/trusted-types": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", - "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==" - }, - "node_modules/@types/wrap-ansi": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/wrap-ansi/-/wrap-ansi-3.0.0.tgz", - "integrity": "sha512-ltIpx+kM7g/MLRZfkbL7EsCEjfzCcScLpkg37eXEtx5kmrAKBkTJwd1GIAjDSL8wTpM6Hzn5YO4pSb91BEwu1g==" - }, - "node_modules/@umbraco-ui/uui": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui/-/uui-1.12.2.tgz", - "integrity": "sha512-oEqt0ysOpqlpMk7AOX+88aV0dgnHfSXxE6imJw0KQKNMnZNOKv7EpndGliLJW/N2hgXQoVPESeYAfbLLt8J0MQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@umbraco-ui/uui-action-bar": "1.12.2", - "@umbraco-ui/uui-avatar": "1.12.2", - "@umbraco-ui/uui-avatar-group": "1.12.2", - "@umbraco-ui/uui-badge": "1.12.2", - "@umbraco-ui/uui-base": "1.12.2", - "@umbraco-ui/uui-boolean-input": "1.12.2", - "@umbraco-ui/uui-box": "1.12.2", - "@umbraco-ui/uui-breadcrumbs": "1.12.2", - "@umbraco-ui/uui-button": "1.12.2", - "@umbraco-ui/uui-button-group": "1.12.2", - "@umbraco-ui/uui-button-inline-create": "1.12.2", - "@umbraco-ui/uui-card": "1.12.2", - "@umbraco-ui/uui-card-block-type": "1.12.2", - "@umbraco-ui/uui-card-content-node": "1.12.2", - "@umbraco-ui/uui-card-media": "1.12.2", - "@umbraco-ui/uui-card-user": "1.12.2", - "@umbraco-ui/uui-caret": "1.12.2", - "@umbraco-ui/uui-checkbox": "1.12.2", - "@umbraco-ui/uui-color-area": "1.12.2", - "@umbraco-ui/uui-color-picker": "1.12.2", - "@umbraco-ui/uui-color-slider": "1.12.2", - "@umbraco-ui/uui-color-swatch": "1.12.2", - "@umbraco-ui/uui-color-swatches": "1.12.2", - "@umbraco-ui/uui-combobox": "1.12.2", - "@umbraco-ui/uui-combobox-list": "1.12.2", - "@umbraco-ui/uui-css": "1.12.1", - "@umbraco-ui/uui-dialog": "1.12.2", - "@umbraco-ui/uui-dialog-layout": "1.12.2", - "@umbraco-ui/uui-file-dropzone": "1.12.2", - "@umbraco-ui/uui-file-preview": "1.12.2", - "@umbraco-ui/uui-form": "1.12.2", - "@umbraco-ui/uui-form-layout-item": "1.12.2", - "@umbraco-ui/uui-form-validation-message": "1.12.2", - "@umbraco-ui/uui-icon": "1.12.2", - "@umbraco-ui/uui-icon-registry": "1.12.2", - "@umbraco-ui/uui-icon-registry-essential": "1.12.2", - "@umbraco-ui/uui-input": "1.12.2", - "@umbraco-ui/uui-input-file": "1.12.2", - "@umbraco-ui/uui-input-lock": "1.12.2", - "@umbraco-ui/uui-input-password": "1.12.2", - "@umbraco-ui/uui-keyboard-shortcut": "1.12.2", - "@umbraco-ui/uui-label": "1.12.2", - "@umbraco-ui/uui-loader": "1.12.2", - "@umbraco-ui/uui-loader-bar": "1.12.2", - "@umbraco-ui/uui-loader-circle": "1.12.2", - "@umbraco-ui/uui-menu-item": "1.12.2", - "@umbraco-ui/uui-modal": "1.12.2", - "@umbraco-ui/uui-pagination": "1.12.2", - "@umbraco-ui/uui-popover": "1.12.2", - "@umbraco-ui/uui-popover-container": "1.12.2", - "@umbraco-ui/uui-progress-bar": "1.12.2", - "@umbraco-ui/uui-radio": "1.12.2", - "@umbraco-ui/uui-range-slider": "1.12.2", - "@umbraco-ui/uui-ref": "1.12.2", - "@umbraco-ui/uui-ref-list": "1.12.2", - "@umbraco-ui/uui-ref-node": "1.12.2", - "@umbraco-ui/uui-ref-node-data-type": "1.12.2", - "@umbraco-ui/uui-ref-node-document-type": "1.12.2", - "@umbraco-ui/uui-ref-node-form": "1.12.2", - "@umbraco-ui/uui-ref-node-member": "1.12.2", - "@umbraco-ui/uui-ref-node-package": "1.12.2", - "@umbraco-ui/uui-ref-node-user": "1.12.2", - "@umbraco-ui/uui-scroll-container": "1.12.2", - "@umbraco-ui/uui-select": "1.12.2", - "@umbraco-ui/uui-slider": "1.12.2", - "@umbraco-ui/uui-symbol-expand": "1.12.2", - "@umbraco-ui/uui-symbol-file": "1.12.2", - "@umbraco-ui/uui-symbol-file-dropzone": "1.12.2", - "@umbraco-ui/uui-symbol-file-thumbnail": "1.12.2", - "@umbraco-ui/uui-symbol-folder": "1.12.2", - "@umbraco-ui/uui-symbol-lock": "1.12.2", - "@umbraco-ui/uui-symbol-more": "1.12.2", - "@umbraco-ui/uui-symbol-sort": "1.12.2", - "@umbraco-ui/uui-table": "1.12.2", - "@umbraco-ui/uui-tabs": "1.12.2", - "@umbraco-ui/uui-tag": "1.12.2", - "@umbraco-ui/uui-textarea": "1.12.2", - "@umbraco-ui/uui-toast-notification": "1.12.2", - "@umbraco-ui/uui-toast-notification-container": "1.12.2", - "@umbraco-ui/uui-toast-notification-layout": "1.12.2", - "@umbraco-ui/uui-toggle": "1.12.2", - "@umbraco-ui/uui-visually-hidden": "1.12.2" - } - }, - "node_modules/@umbraco-ui/uui-action-bar": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-action-bar/-/uui-action-bar-1.12.2.tgz", - "integrity": "sha512-ZWTO7//oKxo5vpA+RypyxpfVMPi5f8f1uevbJ8PMdizDi67VxN1kxYA4geMzG8OQ+x5IGp01DCTtVeAx3qoJbg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@umbraco-ui/uui-base": "1.12.2", - "@umbraco-ui/uui-button-group": "1.12.2" - } - }, - "node_modules/@umbraco-ui/uui-avatar": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-avatar/-/uui-avatar-1.12.2.tgz", - "integrity": "sha512-b/TkEIGJoouqCZLIBl/c0veJg8imImd35Ed+R1VPlcHFXrgpO8C54Fr0AEwsM5x5OeTtkfvs/18pveLPucraww==", - "dev": true, - "license": "MIT", - "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" - } - }, - "node_modules/@umbraco-ui/uui-avatar-group": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-avatar-group/-/uui-avatar-group-1.12.2.tgz", - "integrity": "sha512-QdymxxxC6qCRAu8vAM7Owgbe/ubZ+BL+wu0qk8RXz77CVORgLpiFeUM4YwOapOXvtogXR6haxf8m3/7nxedqdg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@umbraco-ui/uui-avatar": "1.12.2", - "@umbraco-ui/uui-base": "1.12.2" - } - }, - "node_modules/@umbraco-ui/uui-badge": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-badge/-/uui-badge-1.12.2.tgz", - "integrity": "sha512-jkD8rHvunbUDNZfDCekuP5DI23ufBZD+8Y3FHv5aLOAbRm9XrbJ0B4QHyKQoglQ2Yao6iKeYq+nxzG2x88Z7Dw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" - } - }, - "node_modules/@umbraco-ui/uui-base": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-base/-/uui-base-1.12.2.tgz", - "integrity": "sha512-EyPrP28teYlGeeTZvmq+4wzP8Gh9A963HbZ1nQ3oyGj+twN6QjEKUF7W4VVZ8RvFoyS1/6bWkRODuZAzAwX31g==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "lit": ">=2.8.0" - } - }, - "node_modules/@umbraco-ui/uui-boolean-input": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-boolean-input/-/uui-boolean-input-1.12.2.tgz", - "integrity": "sha512-/NGwAPgXLiaDIMwunTDth21jQ0+5ajH3gJ5JJH6IGIq+N2g7babAEKybkZybYq+mxH//7ljH/uKDHI9IztW58g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" - } - }, - "node_modules/@umbraco-ui/uui-box": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-box/-/uui-box-1.12.2.tgz", - "integrity": "sha512-JUxqsRjqUbZ5NM5S1w40NUlHUHPIcMFqYTeCq+nLHE9WSLchym3NN+0NZjS2+qpO70kYPGlKf39mahy+rbGP9Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@umbraco-ui/uui-base": "1.12.2", - "@umbraco-ui/uui-css": "1.12.1" - } - }, - "node_modules/@umbraco-ui/uui-breadcrumbs": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-breadcrumbs/-/uui-breadcrumbs-1.12.2.tgz", - "integrity": "sha512-P/L4q5whw1/HVMMUmzgq5CYOu3ZoLmtlTUoOnTXj+g5R0ziX5ikjJWF1JnLa6M7ES43aB/7su9GeyvOMkcxMpA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" - } - }, - "node_modules/@umbraco-ui/uui-button": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-button/-/uui-button-1.12.2.tgz", - "integrity": "sha512-x3zF+GLwfpc6W2vB3xLRX6g+hdKdEWMKLXtfl+WPOkocu8+EYzodrUHQg24/lO43j7ovy8c3t+zN8OhjnZMu2Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@umbraco-ui/uui-base": "1.12.2", - "@umbraco-ui/uui-icon-registry-essential": "1.12.2" - } - }, - "node_modules/@umbraco-ui/uui-button-group": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-button-group/-/uui-button-group-1.12.2.tgz", - "integrity": "sha512-VxWICU4hmYCORmo8JzXgSyzpa82/M3OyTxfn/kX+jHg0rk9vMg4JArQJp4NF9qhgOWsHx0ED5yURTTOtbNqFTQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" - } - }, - "node_modules/@umbraco-ui/uui-button-inline-create": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-button-inline-create/-/uui-button-inline-create-1.12.2.tgz", - "integrity": "sha512-YvJTwlA2ZUhepHsmc/WwP3OqG7lkrlVmAcgG7uBbasNMwDYtLWcudMrv/NSHFrCpQe0VePyr7U4YtJqyQrbDTg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" - } - }, - "node_modules/@umbraco-ui/uui-card": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-card/-/uui-card-1.12.2.tgz", - "integrity": "sha512-/FqFYrQxKu38+s3y7XpiO8wW7Z2T7cyst2LvMajG+3U9KPi4A0pwxaRBlli4ay79/9V9uFEGTc4dKjB+jFKl6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" - } - }, - "node_modules/@umbraco-ui/uui-card-block-type": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-card-block-type/-/uui-card-block-type-1.12.2.tgz", - "integrity": "sha512-aydgrznHaIUrJpHrwftjPtnaXVQOLe+r6VWrtyWNSPM4ivUeT5WaH/FVMc90Q6yWfIF3y2a3yCIQAGEqAXghhQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@umbraco-ui/uui-base": "1.12.2", - "@umbraco-ui/uui-card": "1.12.2" - } - }, - "node_modules/@umbraco-ui/uui-card-content-node": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-card-content-node/-/uui-card-content-node-1.12.2.tgz", - "integrity": "sha512-yuNlbrjwphzMPv2xMHca8YUr+NH7FqeP0EjVjhhDSsOJVUZ8uj8Udoq4YIkypOAGAyG+N63jCzLvVTTR71LxGA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@umbraco-ui/uui-base": "1.12.2", - "@umbraco-ui/uui-card": "1.12.2", - "@umbraco-ui/uui-icon": "1.12.2" - } - }, - "node_modules/@umbraco-ui/uui-card-media": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-card-media/-/uui-card-media-1.12.2.tgz", - "integrity": "sha512-37Zful2c9UhDxw7qYWR2F2wdt5Qs5yMjcE0Q5R1ZRA5SFba7qgY0W4YW2iAAPMk2xvDyueaTnbVy1v6gG/jtYw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@umbraco-ui/uui-base": "1.12.2", - "@umbraco-ui/uui-card": "1.12.2", - "@umbraco-ui/uui-symbol-file": "1.12.2", - "@umbraco-ui/uui-symbol-folder": "1.12.2" - } - }, - "node_modules/@umbraco-ui/uui-card-user": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-card-user/-/uui-card-user-1.12.2.tgz", - "integrity": "sha512-fwuYQvXjjiLTv0ykDpg+GpcoG3af3ZHUPTRbDa5W8ygAYlTRUvENSXc2qOUocy9XmXOa0p+P0NhenVSqOJpSIw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@umbraco-ui/uui-avatar": "1.12.2", - "@umbraco-ui/uui-base": "1.12.2", - "@umbraco-ui/uui-card": "1.12.2" - } - }, - "node_modules/@umbraco-ui/uui-caret": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-caret/-/uui-caret-1.12.2.tgz", - "integrity": "sha512-7zVDVzvLszVld9E/pGSGFRgpp+rIipB1sY/r4xDYQ70g+ljlegOfMc3bvGs/topcMM+IlcQO8EOotlps4P44Jw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" - } - }, - "node_modules/@umbraco-ui/uui-checkbox": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-checkbox/-/uui-checkbox-1.12.2.tgz", - "integrity": "sha512-C6SSAUq9JfHHWCz9LLlOOmwET1vDsLKKiYv94LIqn8Zj4H3f1bRgUnSfVPVCfy1+p//Ut8SLw2vTFcTz0F21EA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@umbraco-ui/uui-base": "1.12.2", - "@umbraco-ui/uui-boolean-input": "1.12.2", - "@umbraco-ui/uui-icon-registry-essential": "1.12.2" - } - }, - "node_modules/@umbraco-ui/uui-color-area": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-color-area/-/uui-color-area-1.12.2.tgz", - "integrity": "sha512-W5qOBIvTiHGxFJcc1h3H+CdLHLY4K6QRIXU7I2BEII296PbUMwKaA8WFXAvwSq1KzmCkOJP2hPa4yxQ/qKBzJQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@umbraco-ui/uui-base": "1.12.2", - "colord": "^2.9.3" - } - }, - "node_modules/@umbraco-ui/uui-color-picker": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-color-picker/-/uui-color-picker-1.12.2.tgz", - "integrity": "sha512-t/FB6h1rdNzPa94dIfjGG50yRNmk/7wMjrktKjkZHt+wGWKvjM+I1RjatArZbCAmSV4EQH/7hqyvP6R1OoLIog==", - "dev": true, - "license": "MIT", - "dependencies": { - "@umbraco-ui/uui-base": "1.12.2", - "@umbraco-ui/uui-popover-container": "1.12.2", - "colord": "^2.9.3" - } - }, - "node_modules/@umbraco-ui/uui-color-slider": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-color-slider/-/uui-color-slider-1.12.2.tgz", - "integrity": "sha512-00LxQigqY+04eG0IzHY//Uf010u50DeCQ88ZvCV1MjPNH7T4auEC2/H/O7FYoHhwQB6Ez+ZpYA9ds/NbmTCuVg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" - } - }, - "node_modules/@umbraco-ui/uui-color-swatch": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-color-swatch/-/uui-color-swatch-1.12.2.tgz", - "integrity": "sha512-fDODPeuKirwSyIOhEY46J7Ml5RJcuaeMyLBshWT9bl8pNts9zIlKSvn3oSlZ9mZ7N/Ym/3R2c+33i5avoA+rIA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@umbraco-ui/uui-base": "1.12.2", - "@umbraco-ui/uui-icon-registry-essential": "1.12.2", - "colord": "^2.9.3" - } - }, - "node_modules/@umbraco-ui/uui-color-swatches": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-color-swatches/-/uui-color-swatches-1.12.2.tgz", - "integrity": "sha512-kr9gYjYFQR8mavmDJS+I2t/n5wC6kWbCaZHnJzcs3unOX2jzKHnOqJ8N05y8vc2NZP1pOKSOzoIN1Y6N3qxU+g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@umbraco-ui/uui-base": "1.12.2", - "@umbraco-ui/uui-color-swatch": "1.12.2" - } - }, - "node_modules/@umbraco-ui/uui-combobox": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-combobox/-/uui-combobox-1.12.2.tgz", - "integrity": "sha512-ln7IoQQJ65zknIl5k44E61S0DgW1e7fo/IEuMlgbrmkPnEbkLqV5HVYXIR3377VvfwqbZ44npxegOZBUuuWGlw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@umbraco-ui/uui-base": "1.12.2", - "@umbraco-ui/uui-button": "1.12.2", - "@umbraco-ui/uui-combobox-list": "1.12.2", - "@umbraco-ui/uui-icon": "1.12.2", - "@umbraco-ui/uui-popover-container": "1.12.2", - "@umbraco-ui/uui-scroll-container": "1.12.2", - "@umbraco-ui/uui-symbol-expand": "1.12.2" - } - }, - "node_modules/@umbraco-ui/uui-combobox-list": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-combobox-list/-/uui-combobox-list-1.12.2.tgz", - "integrity": "sha512-tBtQgQKB6kgPwRSkXM9kShNfC4Zed7V1hstCjVFy1wkRU+IinVYiN28NMNdSvDWmmxkRcIVOt7lY70T0fgPPMw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" - } - }, - "node_modules/@umbraco-ui/uui-css": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-css/-/uui-css-1.12.1.tgz", - "integrity": "sha512-cWdoJw3OjdZ5QUoXhUufp/8mdGkVJ4DiI7/NgPaU2GrMbo+c1Q2cx4ST2/K0Q7nY6qa4P4WCSLMoFGyFoOwLKQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "lit": ">=2.8.0" - } - }, - "node_modules/@umbraco-ui/uui-dialog": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-dialog/-/uui-dialog-1.12.2.tgz", - "integrity": "sha512-YfHE4RTRKJiSi/ZCnZMJs+eImXx64JrZmu39bEb6FBAnMpqAMxeq70Nll4Nk43nL6liARv1bXP8OKZd2b7CPgQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@umbraco-ui/uui-base": "1.12.2", - "@umbraco-ui/uui-css": "1.12.1" - } - }, - "node_modules/@umbraco-ui/uui-dialog-layout": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-dialog-layout/-/uui-dialog-layout-1.12.2.tgz", - "integrity": "sha512-Xy+Ocwia0xRcpUUARTdXgSgf5NIG2mlneDkiz6dsrIsFZ1IysXCnfh/4dXw57fneO+PyHI86bDwb9aFlWvve7Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" - } - }, - "node_modules/@umbraco-ui/uui-file-dropzone": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-file-dropzone/-/uui-file-dropzone-1.12.2.tgz", - "integrity": "sha512-5B/1umH72IrxwlQ+4ivKDSIXXcGbfFuhvo98v1nuIF5MGl6wmoiG/lDilhny08RJMHwlcRkdYCtCChtuWEyVUg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@umbraco-ui/uui-base": "1.12.2", - "@umbraco-ui/uui-symbol-file-dropzone": "1.12.2" - } - }, - "node_modules/@umbraco-ui/uui-file-preview": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-file-preview/-/uui-file-preview-1.12.2.tgz", - "integrity": "sha512-Oxkm7x3V/aCHPQDNh8loMESWswYCyDJeZazbhGig7mU6zbms7Vl3Vm46CIKEBva6IMy1p1AsNOgSjY4wmIvXsw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@umbraco-ui/uui-base": "1.12.2", - "@umbraco-ui/uui-symbol-file": "1.12.2", - "@umbraco-ui/uui-symbol-file-thumbnail": "1.12.2", - "@umbraco-ui/uui-symbol-folder": "1.12.2" - } - }, - "node_modules/@umbraco-ui/uui-form": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-form/-/uui-form-1.12.2.tgz", - "integrity": "sha512-35CEeSCODTMaJi7JlvBl988tB0MIbocNg5ewCLeqm2CLVvW1UQi4V+835CY1fjgiR6D8co6Kz6KCR/9aibX5Gg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" - } - }, - "node_modules/@umbraco-ui/uui-form-layout-item": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-form-layout-item/-/uui-form-layout-item-1.12.2.tgz", - "integrity": "sha512-qc4JJhhtM7HsVT1DBtw2xRbayLEWvFDwXROXgmwTUMOVZJ9qGFpSN6EWymm9fr+gBYcbwii6ZKg0ujIeHDILTw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@umbraco-ui/uui-base": "1.12.2", - "@umbraco-ui/uui-form-validation-message": "1.12.2" - } - }, - "node_modules/@umbraco-ui/uui-form-validation-message": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-form-validation-message/-/uui-form-validation-message-1.12.2.tgz", - "integrity": "sha512-MQ0nNQcNpawQUZA+JGYPbGW8Go9b9nj4loK26Op0qvInQpbe9mHbHAhWOdbPTBLoJSYnXpo90/3E9ycU9p9PEQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" - } - }, - "node_modules/@umbraco-ui/uui-icon": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-icon/-/uui-icon-1.12.2.tgz", - "integrity": "sha512-sAz08736Jt1y6pPZSBafNT04w9YCnck46whCZUhx7FX7kiKctJX0Xr9GVZH99YAGxnbXnNx0YsN6PqFfz92FzA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" - } - }, - "node_modules/@umbraco-ui/uui-icon-registry": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-icon-registry/-/uui-icon-registry-1.12.2.tgz", - "integrity": "sha512-CXinq7uwca8QzIMCMBkUNkHoq9KV5ioxJSY4+2b5s7lpS8zK+Zoe+zzt5QL/bOCET6TTGZifpCiZRIiRy1Mffg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@umbraco-ui/uui-base": "1.12.2", - "@umbraco-ui/uui-icon": "1.12.2" - } - }, - "node_modules/@umbraco-ui/uui-icon-registry-essential": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-icon-registry-essential/-/uui-icon-registry-essential-1.12.2.tgz", - "integrity": "sha512-s53QmcXVzrLDwpVP3WZW1pekG95kVrjgHDyTo2T3a2J4ovvEEYpZ8/Jmf/3lJVj5CpvQV+I1l/Wx3zFtniT91g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@umbraco-ui/uui-base": "1.12.2", - "@umbraco-ui/uui-icon-registry": "1.12.2" - } - }, - "node_modules/@umbraco-ui/uui-input": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-input/-/uui-input-1.12.2.tgz", - "integrity": "sha512-t/QsptHm9jMH8A0iWBvRZ2s/qeKaO5vp1Zf5oBG9RtgZoS7cNowdMQPVp6mXzc1gICc217lNFsxt+MUGVCud2w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" - } - }, - "node_modules/@umbraco-ui/uui-input-file": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-input-file/-/uui-input-file-1.12.2.tgz", - "integrity": "sha512-X/AeocW+1XLroIqsuxB4OBTmFy1n7ZzfxNrtwEsaqM1rbrA3RGY2EIjnt311eoxk9DvFWeG50/gICV85sWWNmQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@umbraco-ui/uui-action-bar": "1.12.2", - "@umbraco-ui/uui-base": "1.12.2", - "@umbraco-ui/uui-button": "1.12.2", - "@umbraco-ui/uui-file-dropzone": "1.12.2", - "@umbraco-ui/uui-icon": "1.12.2", - "@umbraco-ui/uui-icon-registry-essential": "1.12.2" - } - }, - "node_modules/@umbraco-ui/uui-input-lock": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-input-lock/-/uui-input-lock-1.12.2.tgz", - "integrity": "sha512-EAjzK0xZbjEEyIqHjMdDPmBQMSay/vbYj65YHb8aJBtYyL17qIqVRMEB9D/tV7cGBp5FbpkpZtb5qWmNVFQtcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@umbraco-ui/uui-base": "1.12.2", - "@umbraco-ui/uui-button": "1.12.2", - "@umbraco-ui/uui-icon": "1.12.2", - "@umbraco-ui/uui-input": "1.12.2" - } - }, - "node_modules/@umbraco-ui/uui-input-password": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-input-password/-/uui-input-password-1.12.2.tgz", - "integrity": "sha512-CYNHiaDmaBDXUYE6XFpO3lpmClwjC6aCgtlYFe8SqFlcyU1KABal36PopxpnIMuKrmMv3LFHw1Jpg5dnjk/hNA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@umbraco-ui/uui-base": "1.12.2", - "@umbraco-ui/uui-icon-registry-essential": "1.12.2", - "@umbraco-ui/uui-input": "1.12.2" - } - }, - "node_modules/@umbraco-ui/uui-keyboard-shortcut": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-keyboard-shortcut/-/uui-keyboard-shortcut-1.12.2.tgz", - "integrity": "sha512-X4ZpIP6AQbx5d3zLVVGqHKIDBli4HwkOsTnepHYFPTykTTiCVBxRiVQ5TRgAM4GjeEaUe/mOyPOCYkVBJ0bKmA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" - } - }, - "node_modules/@umbraco-ui/uui-label": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-label/-/uui-label-1.12.2.tgz", - "integrity": "sha512-D4j2XBwtYq2tK/pP+QJuLSxg5NtD+jGEy5DO2qhoRm2VPzGjCWw3irdykVoTIgMRjJiWOQMvE8tpgqPBsBygHw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" - } - }, - "node_modules/@umbraco-ui/uui-loader": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-loader/-/uui-loader-1.12.2.tgz", - "integrity": "sha512-vbAds+57/wFelt+F4YdCdZ9dyR9DjBtEEPhcJDbd5yLwbgKnl+ITL6pDtu2kT45cVMacaxxZAdP5SzcwVSnR7g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" - } - }, - "node_modules/@umbraco-ui/uui-loader-bar": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-loader-bar/-/uui-loader-bar-1.12.2.tgz", - "integrity": "sha512-nC678xqAJFH8vKqhewfFi1CEZ8dR5r/s88REILZOwQM8S0c2z9J4bxesmjpr2ZIQ4KQ2l7BCzBdWbyqs+GUHUA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" - } - }, - "node_modules/@umbraco-ui/uui-loader-circle": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-loader-circle/-/uui-loader-circle-1.12.2.tgz", - "integrity": "sha512-CmjdLDdUM1pRp3dE+WKVEc9dTIQlvYtPtJIjCyNwP403YcKvreGMW6wKMxV/+69IEPjRtTjyaKyprNGnRVRpwg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" - } - }, - "node_modules/@umbraco-ui/uui-menu-item": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-menu-item/-/uui-menu-item-1.12.2.tgz", - "integrity": "sha512-CvrkPWvfRLGSWFNDq+SCLKUm08DjWzw/nYtGLSmQL9QsXa/SMJMtmmcw2H+OYzk4d/9ME+r0GRralZgDlx08iA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@umbraco-ui/uui-base": "1.12.2", - "@umbraco-ui/uui-loader-bar": "1.12.2", - "@umbraco-ui/uui-symbol-expand": "1.12.2" - } - }, - "node_modules/@umbraco-ui/uui-modal": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-modal/-/uui-modal-1.12.2.tgz", - "integrity": "sha512-0ZJuMwdpIFDD+vi59gakhL4jsEb+/f/sMIH4yE/np8ccbZNnGSIT0RJPe94lv6b2wPKrjVIQ1VGGrqzY2znh2A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" - } - }, - "node_modules/@umbraco-ui/uui-pagination": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-pagination/-/uui-pagination-1.12.2.tgz", - "integrity": "sha512-TvP0GKewUZndpO7rHlPqbsw5dPqmKBJXs33berhn/crIE2pGnPVEBey3NYLIHBd5CZI5ufn+gGn4NPNVGF+Q9A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@umbraco-ui/uui-base": "1.12.2", - "@umbraco-ui/uui-button": "1.12.2", - "@umbraco-ui/uui-button-group": "1.12.2" - } - }, - "node_modules/@umbraco-ui/uui-popover": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-popover/-/uui-popover-1.12.2.tgz", - "integrity": "sha512-gvSUe7wox0VY/wEm8LLUV//aLVwz7twswWQd9QniR6MdahvwhjWhQ90hTVpir3VAj5GFBaTfSitqeFBElyT1og==", - "dev": true, - "license": "MIT", - "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" - } - }, - "node_modules/@umbraco-ui/uui-popover-container": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-popover-container/-/uui-popover-container-1.12.2.tgz", - "integrity": "sha512-2z//P49B1zyoN/tWdVZp6Q+8qRnbbtGb4CBveXZeuuirzNxhMOA/E77Y0aJmzjn8yTRoooMGmYzRYd+4zJGNJQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" - } - }, - "node_modules/@umbraco-ui/uui-progress-bar": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-progress-bar/-/uui-progress-bar-1.12.2.tgz", - "integrity": "sha512-PW5TKeg58Lv3WfX6Sp/EPWCsl9oYqQovvl/7y0pxy7xFnSYma5tFQ+XX0mD1rKw7ed3Unlek/Ma9u79Z9GVhDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" - } - }, - "node_modules/@umbraco-ui/uui-radio": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-radio/-/uui-radio-1.12.2.tgz", - "integrity": "sha512-KfXA6+YtueMsxQTjzjp8gVgGJAk17BW9d4Da4h7kYhZGekfWK996ohEgGWF7vj/Q4Ai229OuX7zNJdufCGZIfw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" - } - }, - "node_modules/@umbraco-ui/uui-range-slider": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-range-slider/-/uui-range-slider-1.12.2.tgz", - "integrity": "sha512-m4ATwJYdasF4jfLLHxfFw+2n0uQmZdOha4vxzHbTreyO/gnwn8hLfICA1h9zjoZIqUGMtQ9KlhIaUezvgMpGFw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" - } - }, - "node_modules/@umbraco-ui/uui-ref": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref/-/uui-ref-1.12.2.tgz", - "integrity": "sha512-uwQmaiuwphD1ereZLBhcUDMUaUosO0sV6NrBOh9KLWhkmeqYjuFFG2+CRxdhQrKb1ltZfLzAmzYfGp6AoFkvmw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" - } - }, - "node_modules/@umbraco-ui/uui-ref-list": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-list/-/uui-ref-list-1.12.2.tgz", - "integrity": "sha512-b7reEiwfGy17Ns3qFQoO0TnngxAUclhj0jR7gLIk7dHNJZw45r37crPMkVs2CnRj657nn4DmghjQgCLDSCre9w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" - } - }, - "node_modules/@umbraco-ui/uui-ref-node": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node/-/uui-ref-node-1.12.2.tgz", - "integrity": "sha512-RFma47ixyYNdcMwel1+dte5fGnULczWZpzh1CvAiI9JNKzy9ItUFi70UiFKMrkOY0gT+910xgeWhk4jPTJJgpQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@umbraco-ui/uui-base": "1.12.2", - "@umbraco-ui/uui-icon": "1.12.2", - "@umbraco-ui/uui-ref": "1.12.2" - } - }, - "node_modules/@umbraco-ui/uui-ref-node-data-type": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-data-type/-/uui-ref-node-data-type-1.12.2.tgz", - "integrity": "sha512-s8eviANQTHaNXSVa4U61wJcPCAwzUj6YrIvw7T3Ioe4HgIQvTotIWaCkek+p4ttl3irnnBsRXfGdW+yWuaEnEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@umbraco-ui/uui-base": "1.12.2", - "@umbraco-ui/uui-ref-node": "1.12.2" - } - }, - "node_modules/@umbraco-ui/uui-ref-node-document-type": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-document-type/-/uui-ref-node-document-type-1.12.2.tgz", - "integrity": "sha512-Dg+SAAcMSqr0EvX6IY2jjGk9I8bbgo1Pe6L5c9g0CBPmQ8H+0qOKDdSojWzn/qohtfdAIvN+21Q0AvCovVA9rA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@umbraco-ui/uui-base": "1.12.2", - "@umbraco-ui/uui-ref-node": "1.12.2" - } - }, - "node_modules/@umbraco-ui/uui-ref-node-form": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-form/-/uui-ref-node-form-1.12.2.tgz", - "integrity": "sha512-jnPNmLK8LvZenH2MY9Ea8R+4JkuDNMoBfUFVnhaLg+dHp7tsrg9opIONDNOIJJTYHryHdZ+/ksvQGW6ZWlACgQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@umbraco-ui/uui-base": "1.12.2", - "@umbraco-ui/uui-ref-node": "1.12.2" - } - }, - "node_modules/@umbraco-ui/uui-ref-node-member": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-member/-/uui-ref-node-member-1.12.2.tgz", - "integrity": "sha512-ft0SRlDZ49eRbV3Xk7JtDfR5UraULoeTfYe/MHZkmAzhrDKeTtnd9oVYUQ27qsYs6EVneQ8byydwXrmSMloc8A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@umbraco-ui/uui-base": "1.12.2", - "@umbraco-ui/uui-ref-node": "1.12.2" - } - }, - "node_modules/@umbraco-ui/uui-ref-node-package": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-package/-/uui-ref-node-package-1.12.2.tgz", - "integrity": "sha512-TX9PCPpeOWpl5vK8o/QjXgEWXOt7z0lQK8wlUHYSz+a3/wcmDZD0J/OXkmpvVyS2lXe6pqR8HJ/+FwcnrOm/9w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@umbraco-ui/uui-base": "1.12.2", - "@umbraco-ui/uui-ref-node": "1.12.2" - } - }, - "node_modules/@umbraco-ui/uui-ref-node-user": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-user/-/uui-ref-node-user-1.12.2.tgz", - "integrity": "sha512-sBMICX3vxJd9WjJPWqVnhUhJL+JMuzGzZVUfHlzIjrdpANZZ6FrhnvYkHXhW83KsrfwLsY5/3CXr22xZSsVajA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@umbraco-ui/uui-base": "1.12.2", - "@umbraco-ui/uui-ref-node": "1.12.2" - } - }, - "node_modules/@umbraco-ui/uui-scroll-container": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-scroll-container/-/uui-scroll-container-1.12.2.tgz", - "integrity": "sha512-MI5lpiUeLg1Scf2xHaFzBADAW8CAwcU2yEKOOfOgONuaP6PiUA80YqtE2hCm5BmoldbOYBufCJlFFi2cyuq7HQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" - } - }, - "node_modules/@umbraco-ui/uui-select": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-select/-/uui-select-1.12.2.tgz", - "integrity": "sha512-TOGodRtumlh1cgC9iKxsV/jEGH2w7bKBjIhyQ42sJ3DXyLPcXVEUooZYmh/3dOf7R/7eHSsZOxH/sskbQlNS2A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" - } - }, - "node_modules/@umbraco-ui/uui-slider": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-slider/-/uui-slider-1.12.2.tgz", - "integrity": "sha512-Eg0XqIIXwibxq7y4qe0OB9+t7QLetnlBY3i2BSeMPMfarG1NQ6jhWVOv//RKmZ1kqfUh9MCE5tya9T9h68zR1A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" - } - }, - "node_modules/@umbraco-ui/uui-symbol-expand": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-expand/-/uui-symbol-expand-1.12.2.tgz", - "integrity": "sha512-zW/ClcJuPCe7ELYHCyoSMm6sGWVPLDbjz8TlE1qambwmFefqTfv69p3nB0YF7QnB+7LR5ePOV63vjZSYWT9/Aw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" - } - }, - "node_modules/@umbraco-ui/uui-symbol-file": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-file/-/uui-symbol-file-1.12.2.tgz", - "integrity": "sha512-+af95C4eZOdOpqJrt8br1pic1P/NPrnyC1Q4sKLaCReuBqBdaWLl502kAXjlkkoJZsv4GsyzmjiSbBkbRIZCFQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" - } - }, - "node_modules/@umbraco-ui/uui-symbol-file-dropzone": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-file-dropzone/-/uui-symbol-file-dropzone-1.12.2.tgz", - "integrity": "sha512-8vmHw+nYZdWgeUVNCJhTvJg4iw0zTCxQ6H5tguN1Qepc+XD1NdlRTi8yicnEKSLcq20qzI3KxxwToNLnFKseSQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" - } - }, - "node_modules/@umbraco-ui/uui-symbol-file-thumbnail": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-file-thumbnail/-/uui-symbol-file-thumbnail-1.12.2.tgz", - "integrity": "sha512-tQsQTjgZti4zB327Xd2ql8lz9rj07aVwKfJcV2bClHwyQbRb370KRAS4m6MiaT587+6qVcjRwG3Sya1blpNMfg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" - } - }, - "node_modules/@umbraco-ui/uui-symbol-folder": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-folder/-/uui-symbol-folder-1.12.2.tgz", - "integrity": "sha512-v3bYEpbomOmt2J+LYuB3HqzzZW+LzK/Ufpvr3Km9Gl4eXjPUnrAzBn3PSdq7w5ZvR3vfEV017coPTSX0wncjKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" - } - }, - "node_modules/@umbraco-ui/uui-symbol-lock": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-lock/-/uui-symbol-lock-1.12.2.tgz", - "integrity": "sha512-syW+kTYq7W9coBc7ov1BbDhRTmAMh77GacfQt4XSayHgE/hhO6UvG95uk0POaooQ0UfBW1bDv9r3/wJNZBTfmw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" - } - }, - "node_modules/@umbraco-ui/uui-symbol-more": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-more/-/uui-symbol-more-1.12.2.tgz", - "integrity": "sha512-lxcw/B6zl3TJ7mZDYgXKvX6D/1gYYLmrLvKV7J5iSTGxDNiLji8NAXu2/rgffKMGIFaLfZicEENSLLX/JF8QGQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" - } - }, - "node_modules/@umbraco-ui/uui-symbol-sort": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-sort/-/uui-symbol-sort-1.12.2.tgz", - "integrity": "sha512-iDLs6Ph9BGrLRifU6oGZr7UCOsoOKk5NMxnP7eP/sy0geq30kHlI/mcBu6XUrtYiFsy3+l8b8gSFdLxEHQrcgQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" - } - }, - "node_modules/@umbraco-ui/uui-table": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-table/-/uui-table-1.12.2.tgz", - "integrity": "sha512-aHSArtedBiQBcz++eXomQvTys4Q0P7/SNEUcsG/CbPS7uDWXQZJK/KajtI7rMjU/d63dtavIXq9v0LatKTM/sw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" - } - }, - "node_modules/@umbraco-ui/uui-tabs": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-tabs/-/uui-tabs-1.12.2.tgz", - "integrity": "sha512-20ZmwGiLFtFA5a1CkBo713Ua508d0VwaCWnaKkhoE8Kl/ttlWhlKg+PSB26wkcwB0QonWrH1clMRalwKqRhjvg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@umbraco-ui/uui-base": "1.12.2", - "@umbraco-ui/uui-button": "1.12.2", - "@umbraco-ui/uui-popover-container": "1.12.2", - "@umbraco-ui/uui-symbol-more": "1.12.2" - } - }, - "node_modules/@umbraco-ui/uui-tag": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-tag/-/uui-tag-1.12.2.tgz", - "integrity": "sha512-15omQCZmBeW3U6E0kCoFQs3ckUsNqWOCjslGfDMe+0x0a+r5hntam05OrUlF523plD/SG6utXGI/tRYdTidh1g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" - } - }, - "node_modules/@umbraco-ui/uui-textarea": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-textarea/-/uui-textarea-1.12.2.tgz", - "integrity": "sha512-dlT0fZ0zjdj4BouWhjqA4UBBj4YRFGxWZkMhbP/+g2lAnsl11GN2yMzOvfv7R6Zo3pmV6/qavtEk+XRKBaAihg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" - } - }, - "node_modules/@umbraco-ui/uui-toast-notification": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-toast-notification/-/uui-toast-notification-1.12.2.tgz", - "integrity": "sha512-gtVAoGPd4G0VWVdSyyhaDQupzuLLfFzuaVTVai0970hLAZAzcbodG3W382iPhPIbHwQX7T8LMV02ScPfGuhjbA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@umbraco-ui/uui-base": "1.12.2", - "@umbraco-ui/uui-button": "1.12.2", - "@umbraco-ui/uui-css": "1.12.1", - "@umbraco-ui/uui-icon": "1.12.2", - "@umbraco-ui/uui-icon-registry-essential": "1.12.2" - } - }, - "node_modules/@umbraco-ui/uui-toast-notification-container": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-toast-notification-container/-/uui-toast-notification-container-1.12.2.tgz", - "integrity": "sha512-Zu70rQzYV+QegV2kwNmpUDGU75z6u9B3ujFzVN2u+oi1y0kkR6wgXIczExQ4PeqEBZM252ZWbCIDQ66gX1+thw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@umbraco-ui/uui-base": "1.12.2", - "@umbraco-ui/uui-toast-notification": "1.12.2" - } - }, - "node_modules/@umbraco-ui/uui-toast-notification-layout": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-toast-notification-layout/-/uui-toast-notification-layout-1.12.2.tgz", - "integrity": "sha512-b0kgRwc744RpBjJW5URKRwGXzbGWU12OuFqIXq6BSl8LuFci9uh62V2J7Jj5xnx6v1jqZi/RRRKRwiqQOa3AWw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@umbraco-ui/uui-base": "1.12.2", - "@umbraco-ui/uui-css": "1.12.1" - } - }, - "node_modules/@umbraco-ui/uui-toggle": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-toggle/-/uui-toggle-1.12.2.tgz", - "integrity": "sha512-hQCQJUEYjNL/2a/vldTlkFhTLiAF+P1UKxhPDqxCQlO/GsOihefcRhchOPmx4ptvjadvSc7J/MJPhAYC2RB0gw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@umbraco-ui/uui-base": "1.12.2", - "@umbraco-ui/uui-boolean-input": "1.12.2" - } - }, - "node_modules/@umbraco-ui/uui-visually-hidden": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-visually-hidden/-/uui-visually-hidden-1.12.2.tgz", - "integrity": "sha512-3VC4UUcalOl93pkwVWxbSxnIEyN9e5Soy+V3HKQDifWZ536NjBRvMzw+jib5BFLBzrfmRjX68lxNbE2t/EDydA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@umbraco-ui/uui-base": "1.12.2" - } - }, - "node_modules/ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dependencies": { - "type-fest": "^0.21.3" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ansi-escapes/node_modules/type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/cli-spinners": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", - "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cli-width": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", - "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", - "engines": { - "node": ">= 12" - } - }, - "node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/colord": { - "version": "2.9.3", - "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", - "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==", - "dev": true, - "license": "MIT" - }, - "node_modules/cookie": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", - "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" - }, - "node_modules/esbuild": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.12.tgz", - "integrity": "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==", - "dev": true, - "hasInstallScript": true, - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=12" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.19.12", - "@esbuild/android-arm": "0.19.12", - "@esbuild/android-arm64": "0.19.12", - "@esbuild/android-x64": "0.19.12", - "@esbuild/darwin-arm64": "0.19.12", - "@esbuild/darwin-x64": "0.19.12", - "@esbuild/freebsd-arm64": "0.19.12", - "@esbuild/freebsd-x64": "0.19.12", - "@esbuild/linux-arm": "0.19.12", - "@esbuild/linux-arm64": "0.19.12", - "@esbuild/linux-ia32": "0.19.12", - "@esbuild/linux-loong64": "0.19.12", - "@esbuild/linux-mips64el": "0.19.12", - "@esbuild/linux-ppc64": "0.19.12", - "@esbuild/linux-riscv64": "0.19.12", - "@esbuild/linux-s390x": "0.19.12", - "@esbuild/linux-x64": "0.19.12", - "@esbuild/netbsd-x64": "0.19.12", - "@esbuild/openbsd-x64": "0.19.12", - "@esbuild/sunos-x64": "0.19.12", - "@esbuild/win32-arm64": "0.19.12", - "@esbuild/win32-ia32": "0.19.12", - "@esbuild/win32-x64": "0.19.12" - } - }, - "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/figures": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", - "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", - "dependencies": { - "escape-string-regexp": "^1.0.5" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/graphql": { - "version": "16.8.1", - "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.8.1.tgz", - "integrity": "sha512-59LZHPdGZVh695Ud9lRzPBVTtlX9ZCV150Er2W43ro37wVof0ctenSaskPPjN7lVTIN8mSZt8PHUNKZuNQUuxw==", - "engines": { - "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" - } - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/headers-polyfill": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/headers-polyfill/-/headers-polyfill-4.0.2.tgz", - "integrity": "sha512-EWGTfnTqAO2L/j5HZgoM/3z82L7necsJ0pO9Tp0X1wil3PDLrkypTBRgVO2ExehEEvUycejZD3FuRaXpZZc3kw==" - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-node-process": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/is-node-process/-/is-node-process-1.2.0.tgz", - "integrity": "sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw==" - }, - "node_modules/lit": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/lit/-/lit-3.1.2.tgz", - "integrity": "sha512-VZx5iAyMtX7CV4K8iTLdCkMaYZ7ipjJZ0JcSdJ0zIdGxxyurjIn7yuuSxNBD7QmjvcNJwr0JS4cAdAtsy7gZ6w==", - "dependencies": { - "@lit/reactive-element": "^2.0.4", - "lit-element": "^4.0.4", - "lit-html": "^3.1.2" - } - }, - "node_modules/lit-element": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/lit-element/-/lit-element-4.0.4.tgz", - "integrity": "sha512-98CvgulX6eCPs6TyAIQoJZBCQPo80rgXR+dVBs61cstJXqtI+USQZAbA4gFHh6L/mxBx9MrgPLHLsUgDUHAcCQ==", - "dependencies": { - "@lit-labs/ssr-dom-shim": "^1.2.0", - "@lit/reactive-element": "^2.0.4", - "lit-html": "^3.1.2" - } - }, - "node_modules/lit-html": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-3.1.2.tgz", - "integrity": "sha512-3OBZSUrPnAHoKJ9AMjRL/m01YJxQMf+TMHanNtTHG68ubjnZxK0RFl102DPzsw4mWnHibfZIBJm3LWCZ/LmMvg==", - "dependencies": { - "@types/trusted-types": "^2.0.2" - } - }, - "node_modules/msw": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/msw/-/msw-2.2.0.tgz", - "integrity": "sha512-98cUGcIphhdf3KDbmSxji7XFqLxeSFAmPUNV00N/U76GOkuUKEwp6MHqM6KW70rlpgeJP8qIWueppdnVThzG1g==", - "hasInstallScript": true, - "dependencies": { - "@bundled-es-modules/cookie": "^2.0.0", - "@bundled-es-modules/statuses": "^1.0.1", - "@inquirer/confirm": "^3.0.0", - "@mswjs/cookies": "^1.1.0", - "@mswjs/interceptors": "^0.25.16", - "@open-draft/until": "^2.1.0", - "@types/cookie": "^0.6.0", - "@types/statuses": "^2.0.4", - "chalk": "^4.1.2", - "graphql": "^16.8.1", - "headers-polyfill": "^4.0.2", - "is-node-process": "^1.2.0", - "outvariant": "^1.4.2", - "path-to-regexp": "^6.2.0", - "strict-event-emitter": "^0.5.1", - "type-fest": "^4.9.0", - "yargs": "^17.7.2" - }, - "bin": { - "msw": "cli/index.js" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mswjs" - }, - "peerDependencies": { - "typescript": ">= 4.7.x <= 5.3.x" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/mute-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-1.0.0.tgz", - "integrity": "sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==", - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/nanoid": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", - "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/outvariant": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/outvariant/-/outvariant-1.4.2.tgz", - "integrity": "sha512-Ou3dJ6bA/UJ5GVHxah4LnqDwZRwAmWxrG3wtrHrbGnP4RnLCtA64A4F+ae7Y8ww660JaddSoArUR5HjipWSHAQ==" - }, - "node_modules/path-to-regexp": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.1.tgz", - "integrity": "sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==" - }, - "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true - }, - "node_modules/postcss": { - "version": "8.4.35", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz", - "integrity": "sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "dependencies": { - "nanoid": "^3.3.7", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/rollup": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.10.0.tgz", - "integrity": "sha512-t2v9G2AKxcQ8yrG+WGxctBes1AomT0M4ND7jTFBCVPXQ/WFTvNSefIrNSmLKhIKBrvN8SG+CZslimJcT3W2u2g==", - "dev": true, - "dependencies": { - "@types/estree": "1.0.5" - }, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.10.0", - "@rollup/rollup-android-arm64": "4.10.0", - "@rollup/rollup-darwin-arm64": "4.10.0", - "@rollup/rollup-darwin-x64": "4.10.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.10.0", - "@rollup/rollup-linux-arm64-gnu": "4.10.0", - "@rollup/rollup-linux-arm64-musl": "4.10.0", - "@rollup/rollup-linux-riscv64-gnu": "4.10.0", - "@rollup/rollup-linux-x64-gnu": "4.10.0", - "@rollup/rollup-linux-x64-musl": "4.10.0", - "@rollup/rollup-win32-arm64-msvc": "4.10.0", - "@rollup/rollup-win32-ia32-msvc": "4.10.0", - "@rollup/rollup-win32-x64-msvc": "4.10.0", - "fsevents": "~2.3.2" - } - }, - "node_modules/run-async": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-3.0.0.tgz", - "integrity": "sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/rxjs": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", - "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", - "dependencies": { - "tslib": "^2.1.0" - } - }, - "node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/strict-event-emitter": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/strict-event-emitter/-/strict-event-emitter-0.5.1.tgz", - "integrity": "sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==" - }, - "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==" - }, - "node_modules/type-fest": { - "version": "4.10.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.10.2.tgz", - "integrity": "sha512-anpAG63wSpdEbLwOqH8L84urkL6PiVIov3EMmgIhhThevh9aiMQov+6Btx0wldNcvm4wV+e2/Rt1QdDwKHFbHw==", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/typescript": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", - "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", - "devOptional": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" - }, - "node_modules/vite": { - "version": "5.1.7", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.7.tgz", - "integrity": "sha512-sgnEEFTZYMui/sTlH1/XEnVNHMujOahPLGMxn1+5sIT45Xjng1Ec1K78jRP15dSmVgg5WBin9yO81j3o9OxofA==", - "dev": true, - "dependencies": { - "esbuild": "^0.19.3", - "postcss": "^8.4.35", - "rollup": "^4.2.0" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^18.0.0 || >=20.0.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^18.0.0 || >=20.0.0", - "less": "*", - "lightningcss": "^1.21.0", - "sass": "*", - "stylus": "*", - "sugarss": "*", - "terser": "^5.4.0" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - } - } - }, - "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "engines": { - "node": ">=12" - } - } - } + "name": "login", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "login", + "dependencies": { + "lit": "^3.1.2", + "msw": "^2.2.0", + "rxjs": "^7.8.1" + }, + "devDependencies": { + "@umbraco-ui/uui": "1.13.0", + "@umbraco-ui/uui-css": "1.13.0", + "typescript": "^5.3.3", + "vite": "^5.1.7" + }, + "engines": { + "node": ">=20.8", + "npm": ">=10.1" + } + }, + "node_modules/@bundled-es-modules/cookie": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@bundled-es-modules/cookie/-/cookie-2.0.0.tgz", + "integrity": "sha512-Or6YHg/kamKHpxULAdSqhGqnWFneIXu1NKvvfBBzKGwpVsYuFIQ5aBPHDnnoR3ghW1nvSkALd+EF9iMtY7Vjxw==", + "dependencies": { + "cookie": "^0.5.0" + } + }, + "node_modules/@bundled-es-modules/statuses": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@bundled-es-modules/statuses/-/statuses-1.0.1.tgz", + "integrity": "sha512-yn7BklA5acgcBr+7w064fGV+SGIFySjCKpqjcWgBAIfrAkY+4GQTJJHQMeT3V/sgz23VTEVV8TtOmkvJAhFVfg==", + "dependencies": { + "statuses": "^2.0.1" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz", + "integrity": "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.12.tgz", + "integrity": "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.12.tgz", + "integrity": "sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.12.tgz", + "integrity": "sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.12.tgz", + "integrity": "sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.12.tgz", + "integrity": "sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.12.tgz", + "integrity": "sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.12.tgz", + "integrity": "sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.12.tgz", + "integrity": "sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.12.tgz", + "integrity": "sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.12.tgz", + "integrity": "sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.12.tgz", + "integrity": "sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.12.tgz", + "integrity": "sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.12.tgz", + "integrity": "sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.12.tgz", + "integrity": "sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.12.tgz", + "integrity": "sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.12.tgz", + "integrity": "sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.12.tgz", + "integrity": "sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.12.tgz", + "integrity": "sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.12.tgz", + "integrity": "sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.12.tgz", + "integrity": "sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.12.tgz", + "integrity": "sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.12.tgz", + "integrity": "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@inquirer/confirm": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-3.0.0.tgz", + "integrity": "sha512-LHeuYP1D8NmQra1eR4UqvZMXwxEdDXyElJmmZfU44xdNLL6+GcQBS0uE16vyfZVjH8c22p9e+DStROfE/hyHrg==", + "dependencies": { + "@inquirer/core": "^7.0.0", + "@inquirer/type": "^1.2.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/core": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-7.0.0.tgz", + "integrity": "sha512-g13W5yEt9r1sEVVriffJqQ8GWy94OnfxLCreNSOTw0HPVcszmc/If1KIf7YBmlwtX4klmvwpZHnQpl3N7VX2xA==", + "dependencies": { + "@inquirer/type": "^1.2.0", + "@types/mute-stream": "^0.0.4", + "@types/node": "^20.11.16", + "@types/wrap-ansi": "^3.0.0", + "ansi-escapes": "^4.3.2", + "chalk": "^4.1.2", + "cli-spinners": "^2.9.2", + "cli-width": "^4.1.0", + "figures": "^3.2.0", + "mute-stream": "^1.0.0", + "run-async": "^3.0.0", + "signal-exit": "^4.1.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^6.2.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/core/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@inquirer/type": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-1.2.0.tgz", + "integrity": "sha512-/vvkUkYhrjbm+RolU7V1aUFDydZVKNKqKHR5TsE+j5DXgXFwrsOPcoGUJ02K0O7q7O53CU2DOTMYCHeGZ25WHA==", + "engines": { + "node": ">=18" + } + }, + "node_modules/@lit-labs/ssr-dom-shim": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.2.0.tgz", + "integrity": "sha512-yWJKmpGE6lUURKAaIltoPIE/wrbY3TEkqQt+X0m+7fQNnAv0keydnYvbiJFP1PnMhizmIWRWOG5KLhYyc/xl+g==" + }, + "node_modules/@lit/reactive-element": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-2.0.4.tgz", + "integrity": "sha512-GFn91inaUa2oHLak8awSIigYz0cU0Payr1rcFsrkf5OJ5eSPxElyZfKh0f2p9FsTiZWXQdWGJeXZICEfXXYSXQ==", + "dependencies": { + "@lit-labs/ssr-dom-shim": "^1.2.0" + } + }, + "node_modules/@mswjs/cookies": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@mswjs/cookies/-/cookies-1.1.0.tgz", + "integrity": "sha512-0ZcCVQxifZmhwNBoQIrystCb+2sWBY2Zw8lpfJBPCHGCA/HWqehITeCRVIv4VMy8MPlaHo2w2pTHFV2pFfqKPw==", + "engines": { + "node": ">=18" + } + }, + "node_modules/@mswjs/interceptors": { + "version": "0.25.16", + "resolved": "https://registry.npmjs.org/@mswjs/interceptors/-/interceptors-0.25.16.tgz", + "integrity": "sha512-8QC8JyKztvoGAdPgyZy49c9vSHHAZjHagwl4RY9E8carULk8ym3iTaiawrT1YoLF/qb449h48f71XDPgkUSOUg==", + "dependencies": { + "@open-draft/deferred-promise": "^2.2.0", + "@open-draft/logger": "^0.3.0", + "@open-draft/until": "^2.0.0", + "is-node-process": "^1.2.0", + "outvariant": "^1.2.1", + "strict-event-emitter": "^0.5.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@open-draft/deferred-promise": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@open-draft/deferred-promise/-/deferred-promise-2.2.0.tgz", + "integrity": "sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==" + }, + "node_modules/@open-draft/logger": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@open-draft/logger/-/logger-0.3.0.tgz", + "integrity": "sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ==", + "dependencies": { + "is-node-process": "^1.2.0", + "outvariant": "^1.4.0" + } + }, + "node_modules/@open-draft/until": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@open-draft/until/-/until-2.1.0.tgz", + "integrity": "sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.10.0.tgz", + "integrity": "sha512-/MeDQmcD96nVoRumKUljsYOLqfv1YFJps+0pTrb2Z9Nl/w5qNUysMaWQsrd1mvAlNT4yza1iVyIu4Q4AgF6V3A==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.10.0.tgz", + "integrity": "sha512-lvu0jK97mZDJdpZKDnZI93I0Om8lSDaiPx3OiCk0RXn3E8CMPJNS/wxjAvSJJzhhZpfjXsjLWL8LnS6qET4VNQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.10.0.tgz", + "integrity": "sha512-uFpayx8I8tyOvDkD7X6n0PriDRWxcqEjqgtlxnUA/G9oS93ur9aZ8c8BEpzFmsed1TH5WZNG5IONB8IiW90TQg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.10.0.tgz", + "integrity": "sha512-nIdCX03qFKoR/MwQegQBK+qZoSpO3LESurVAC6s6jazLA1Mpmgzo3Nj3H1vydXp/JM29bkCiuF7tDuToj4+U9Q==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.10.0.tgz", + "integrity": "sha512-Fz7a+y5sYhYZMQFRkOyCs4PLhICAnxRX/GnWYReaAoruUzuRtcf+Qnw+T0CoAWbHCuz2gBUwmWnUgQ67fb3FYw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.10.0.tgz", + "integrity": "sha512-yPtF9jIix88orwfTi0lJiqINnlWo6p93MtZEoaehZnmCzEmLL0eqjA3eGVeyQhMtxdV+Mlsgfwhh0+M/k1/V7Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.10.0.tgz", + "integrity": "sha512-9GW9yA30ib+vfFiwjX+N7PnjTnCMiUffhWj4vkG4ukYv1kJ4T9gHNg8zw+ChsOccM27G9yXrEtMScf1LaCuoWQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.10.0.tgz", + "integrity": "sha512-X1ES+V4bMq2ws5fF4zHornxebNxMXye0ZZjUrzOrf7UMx1d6wMQtfcchZ8SqUnQPPHdOyOLW6fTcUiFgHFadRA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.10.0.tgz", + "integrity": "sha512-w/5OpT2EnI/Xvypw4FIhV34jmNqU5PZjZue2l2Y3ty1Ootm3SqhI+AmfhlUYGBTd9JnpneZCDnt3uNOiOBkMyw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.10.0.tgz", + "integrity": "sha512-q/meftEe3QlwQiGYxD9rWwB21DoKQ9Q8wA40of/of6yGHhZuGfZO0c3WYkN9dNlopHlNT3mf5BPsUSxoPuVQaw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.10.0.tgz", + "integrity": "sha512-NrR6667wlUfP0BHaEIKgYM/2va+Oj+RjZSASbBMnszM9k+1AmliRjHc3lJIiOehtSSjqYiO7R6KLNrWOX+YNSQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.10.0.tgz", + "integrity": "sha512-FV0Tpt84LPYDduIDcXvEC7HKtyXxdvhdAOvOeWMWbQNulxViH2O07QXkT/FffX4FqEI02jEbCJbr+YcuKdyyMg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.10.0.tgz", + "integrity": "sha512-OZoJd+o5TaTSQeFFQ6WjFCiltiYVjIdsXxwu/XZ8qRpsvMQr4UsVrE5UyT9RIvsnuF47DqkJKhhVZ2Q9YW9IpQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==" + }, + "node_modules/@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "dev": true + }, + "node_modules/@types/mute-stream": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/@types/mute-stream/-/mute-stream-0.0.4.tgz", + "integrity": "sha512-CPM9nzrCPPJHQNA9keH9CVkVI+WR5kMa+7XEs5jcGQ0VoAGnLv242w8lIVgwAEfmE4oufJRaTc9PNLQl0ioAow==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/node": { + "version": "20.11.17", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.17.tgz", + "integrity": "sha512-QmgQZGWu1Yw9TDyAP9ZzpFJKynYNeOvwMJmaxABfieQoVoiVOS6MN1WSpqpRcbeA5+RW82kraAVxCCJg+780Qw==", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/statuses": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/statuses/-/statuses-2.0.4.tgz", + "integrity": "sha512-eqNDvZsCNY49OAXB0Firg/Sc2BgoWsntsLUdybGFOhAfCD6QJ2n9HXUIHGqt5qjrxmMv4wS8WLAw43ZkKcJ8Pw==" + }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==" + }, + "node_modules/@types/wrap-ansi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/wrap-ansi/-/wrap-ansi-3.0.0.tgz", + "integrity": "sha512-ltIpx+kM7g/MLRZfkbL7EsCEjfzCcScLpkg37eXEtx5kmrAKBkTJwd1GIAjDSL8wTpM6Hzn5YO4pSb91BEwu1g==" + }, + "node_modules/@umbraco-ui/uui": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui/-/uui-1.13.0.tgz", + "integrity": "sha512-O/RvFeW+Mjn24ckmWJeTzMZKYbVrnaHscl9zKGKkMSva3j3mnJs/Q9N6BfihQy3qdZP5ED+2lGomezxfoLjZ7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@umbraco-ui/uui-action-bar": "1.13.0", + "@umbraco-ui/uui-avatar": "1.13.0", + "@umbraco-ui/uui-avatar-group": "1.13.0", + "@umbraco-ui/uui-badge": "1.13.0", + "@umbraco-ui/uui-base": "1.13.0", + "@umbraco-ui/uui-boolean-input": "1.13.0", + "@umbraco-ui/uui-box": "1.13.0", + "@umbraco-ui/uui-breadcrumbs": "1.13.0", + "@umbraco-ui/uui-button": "1.13.0", + "@umbraco-ui/uui-button-copy-text": "1.13.0", + "@umbraco-ui/uui-button-group": "1.13.0", + "@umbraco-ui/uui-button-inline-create": "1.13.0", + "@umbraco-ui/uui-card": "1.13.0", + "@umbraco-ui/uui-card-block-type": "1.13.0", + "@umbraco-ui/uui-card-content-node": "1.13.0", + "@umbraco-ui/uui-card-media": "1.13.0", + "@umbraco-ui/uui-card-user": "1.13.0", + "@umbraco-ui/uui-caret": "1.13.0", + "@umbraco-ui/uui-checkbox": "1.13.0", + "@umbraco-ui/uui-color-area": "1.13.0", + "@umbraco-ui/uui-color-picker": "1.13.0", + "@umbraco-ui/uui-color-slider": "1.13.0", + "@umbraco-ui/uui-color-swatch": "1.13.0", + "@umbraco-ui/uui-color-swatches": "1.13.0", + "@umbraco-ui/uui-combobox": "1.13.0", + "@umbraco-ui/uui-combobox-list": "1.13.0", + "@umbraco-ui/uui-css": "1.13.0", + "@umbraco-ui/uui-dialog": "1.13.0", + "@umbraco-ui/uui-dialog-layout": "1.13.0", + "@umbraco-ui/uui-file-dropzone": "1.13.0", + "@umbraco-ui/uui-file-preview": "1.13.0", + "@umbraco-ui/uui-form": "1.13.0", + "@umbraco-ui/uui-form-layout-item": "1.13.0", + "@umbraco-ui/uui-form-validation-message": "1.13.0", + "@umbraco-ui/uui-icon": "1.13.0", + "@umbraco-ui/uui-icon-registry": "1.13.0", + "@umbraco-ui/uui-icon-registry-essential": "1.13.0", + "@umbraco-ui/uui-input": "1.13.0", + "@umbraco-ui/uui-input-file": "1.13.0", + "@umbraco-ui/uui-input-lock": "1.13.0", + "@umbraco-ui/uui-input-password": "1.13.0", + "@umbraco-ui/uui-keyboard-shortcut": "1.13.0", + "@umbraco-ui/uui-label": "1.13.0", + "@umbraco-ui/uui-loader": "1.13.0", + "@umbraco-ui/uui-loader-bar": "1.13.0", + "@umbraco-ui/uui-loader-circle": "1.13.0", + "@umbraco-ui/uui-menu-item": "1.13.0", + "@umbraco-ui/uui-modal": "1.13.0", + "@umbraco-ui/uui-pagination": "1.13.0", + "@umbraco-ui/uui-popover": "1.13.0", + "@umbraco-ui/uui-popover-container": "1.13.0", + "@umbraco-ui/uui-progress-bar": "1.13.0", + "@umbraco-ui/uui-radio": "1.13.0", + "@umbraco-ui/uui-range-slider": "1.13.0", + "@umbraco-ui/uui-ref": "1.13.0", + "@umbraco-ui/uui-ref-list": "1.13.0", + "@umbraco-ui/uui-ref-node": "1.13.0", + "@umbraco-ui/uui-ref-node-data-type": "1.13.0", + "@umbraco-ui/uui-ref-node-document-type": "1.13.0", + "@umbraco-ui/uui-ref-node-form": "1.13.0", + "@umbraco-ui/uui-ref-node-member": "1.13.0", + "@umbraco-ui/uui-ref-node-package": "1.13.0", + "@umbraco-ui/uui-ref-node-user": "1.13.0", + "@umbraco-ui/uui-scroll-container": "1.13.0", + "@umbraco-ui/uui-select": "1.13.0", + "@umbraco-ui/uui-slider": "1.13.0", + "@umbraco-ui/uui-symbol-expand": "1.13.0", + "@umbraco-ui/uui-symbol-file": "1.13.0", + "@umbraco-ui/uui-symbol-file-dropzone": "1.13.0", + "@umbraco-ui/uui-symbol-file-thumbnail": "1.13.0", + "@umbraco-ui/uui-symbol-folder": "1.13.0", + "@umbraco-ui/uui-symbol-lock": "1.13.0", + "@umbraco-ui/uui-symbol-more": "1.13.0", + "@umbraco-ui/uui-symbol-sort": "1.13.0", + "@umbraco-ui/uui-table": "1.13.0", + "@umbraco-ui/uui-tabs": "1.13.0", + "@umbraco-ui/uui-tag": "1.13.0", + "@umbraco-ui/uui-textarea": "1.13.0", + "@umbraco-ui/uui-toast-notification": "1.13.0", + "@umbraco-ui/uui-toast-notification-container": "1.13.0", + "@umbraco-ui/uui-toast-notification-layout": "1.13.0", + "@umbraco-ui/uui-toggle": "1.13.0", + "@umbraco-ui/uui-visually-hidden": "1.13.0" + } + }, + "node_modules/@umbraco-ui/uui-action-bar": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-action-bar/-/uui-action-bar-1.13.0.tgz", + "integrity": "sha512-0AGQ1zsUZT1wHKx+01JkRKLNtpjCS/SqEy/NVHUyYIGPimr6NQDM9Ok00LZKpZVwxcvArdy38XaAz6SijlaTqg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@umbraco-ui/uui-base": "1.13.0", + "@umbraco-ui/uui-button-group": "1.13.0" + } + }, + "node_modules/@umbraco-ui/uui-avatar": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-avatar/-/uui-avatar-1.13.0.tgz", + "integrity": "sha512-w+DwB9PUcnR0y0CzeNQA2638PjF2Dswiyuoxa2ryggcy38ihypj0Fj8FpzRSe5rax2JMtpJnuoDPwUpqVwGfOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@umbraco-ui/uui-base": "1.13.0" + } + }, + "node_modules/@umbraco-ui/uui-avatar-group": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-avatar-group/-/uui-avatar-group-1.13.0.tgz", + "integrity": "sha512-G8lIknUuhy+swW9Xz7qN3fp0L5Xhx4d5C2Q9WbW316GeseLYCm2eRhXDLpiEzIMxoVYtA9P0gbkuxLFDkznc+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@umbraco-ui/uui-avatar": "1.13.0", + "@umbraco-ui/uui-base": "1.13.0" + } + }, + "node_modules/@umbraco-ui/uui-badge": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-badge/-/uui-badge-1.13.0.tgz", + "integrity": "sha512-z7Z5IZwcfFJDFIPnBDfuCv+YkBHafn15oi4rNmNVynaM/iFJ+W3NAE7EmdWMDZzuDeQngbFpoRx1Ub7I4mqsng==", + "dev": true, + "license": "MIT", + "dependencies": { + "@umbraco-ui/uui-base": "1.13.0" + } + }, + "node_modules/@umbraco-ui/uui-base": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-base/-/uui-base-1.13.0.tgz", + "integrity": "sha512-VG0xlVzuq74qKaWn+eaTcTblR6HCi9YyrNohLLhHVAJuelmcgoPwHdNzkjoaWXlq16XKnB5Kmb6BgEtVmSQZ5w==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "lit": ">=2.8.0" + } + }, + "node_modules/@umbraco-ui/uui-boolean-input": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-boolean-input/-/uui-boolean-input-1.13.0.tgz", + "integrity": "sha512-WsP+W5/4Fcp9sg0gFlfh8FyIzaczRC4kc2LxT3haljflgQTMVwV4MGGadOYg89hVpD0C4dZaqp69sskLWc6fWQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@umbraco-ui/uui-base": "1.13.0" + } + }, + "node_modules/@umbraco-ui/uui-box": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-box/-/uui-box-1.13.0.tgz", + "integrity": "sha512-msIz5NerPKx7bnTyEyMjihnfxSTlslU+FyE4DsUUwZT6vtFxT2Dt74UbO8cg0ut9GoBjR1wpn4qNTW3xRxfdiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@umbraco-ui/uui-base": "1.13.0", + "@umbraco-ui/uui-css": "1.13.0" + } + }, + "node_modules/@umbraco-ui/uui-breadcrumbs": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-breadcrumbs/-/uui-breadcrumbs-1.13.0.tgz", + "integrity": "sha512-DG/m4bZ3bPTryrN6mDQMmabXPmvKcVlsfjuhJ/UDazq6T/4DVfB6YrXk6q+4N6X4njg88CO/V6ObnyB7RE+flQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@umbraco-ui/uui-base": "1.13.0" + } + }, + "node_modules/@umbraco-ui/uui-button": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-button/-/uui-button-1.13.0.tgz", + "integrity": "sha512-dJWr9fKQFB4CRMJ23oRmkuzN45ESuxDn1nwgLw0TLhJrAWo5uoyTL1k/IuNdg0C3+BqNIzC5B8m5YC2S+BpPlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@umbraco-ui/uui-base": "1.13.0", + "@umbraco-ui/uui-icon-registry-essential": "1.13.0" + } + }, + "node_modules/@umbraco-ui/uui-button-copy-text": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-button-copy-text/-/uui-button-copy-text-1.13.0.tgz", + "integrity": "sha512-/4j4PnxNRAyeD2LYA+dyyCZurOPnGioQfl2iFIE/B2znBvJR2JHrnCLwcAqawI+YhHxGgyKNck7BCKihYQxkow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@umbraco-ui/uui-base": "1.13.0", + "@umbraco-ui/uui-button": "1.13.0" + } + }, + "node_modules/@umbraco-ui/uui-button-group": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-button-group/-/uui-button-group-1.13.0.tgz", + "integrity": "sha512-Pksx35rtKriOUO9IP1ETnQDoBnoiRzwheM8fmqeo44jSPsr7emaQrI3BOwqeOuD7KfPRIVnzwLdm14K4Zw6tZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@umbraco-ui/uui-base": "1.13.0" + } + }, + "node_modules/@umbraco-ui/uui-button-inline-create": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-button-inline-create/-/uui-button-inline-create-1.13.0.tgz", + "integrity": "sha512-6XtJ/nZpVDkYFiWEqbr5uz5CJ2Yqled4W7zAsh53QuCCYFgyU6yU9AFtrhPRwC9I27XzmBTQRZgCkQFWuEuL5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@umbraco-ui/uui-base": "1.13.0" + } + }, + "node_modules/@umbraco-ui/uui-card": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-card/-/uui-card-1.13.0.tgz", + "integrity": "sha512-fBskLWqFoquKfgFK6bJ4lM0V30XZCZcJjjwTUmSjRFvklyF3csL7W9bKB9hs+aFu0/GDQlVqOBa5tA4RLpcj0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@umbraco-ui/uui-base": "1.13.0" + } + }, + "node_modules/@umbraco-ui/uui-card-block-type": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-card-block-type/-/uui-card-block-type-1.13.0.tgz", + "integrity": "sha512-YBDHZ+76oz27+P9v8YSr3OwWs3eftqy2d3Gg/sxh3Y6n9gI2TdXtJgev9GVL2FpifZXM2A1ySzh8MscC2HLJIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@umbraco-ui/uui-base": "1.13.0", + "@umbraco-ui/uui-card": "1.13.0" + } + }, + "node_modules/@umbraco-ui/uui-card-content-node": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-card-content-node/-/uui-card-content-node-1.13.0.tgz", + "integrity": "sha512-IYe/AUaJ7Pspd+zSQlJMRIUzzF7+dLnq6ApezC9b93mEEhB4WwX+BbzfHbbhyNxMv9Za9gBKZljIH3RluPWnog==", + "dev": true, + "license": "MIT", + "dependencies": { + "@umbraco-ui/uui-base": "1.13.0", + "@umbraco-ui/uui-card": "1.13.0", + "@umbraco-ui/uui-icon": "1.13.0" + } + }, + "node_modules/@umbraco-ui/uui-card-media": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-card-media/-/uui-card-media-1.13.0.tgz", + "integrity": "sha512-ohRFE1FqmYNJ7VXKjzMqjhCfzfabL9bLOpJet0+VXCMHUomHZv9UHQTI1ibp71BMp934vWT3kqGgco6RYqujSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@umbraco-ui/uui-base": "1.13.0", + "@umbraco-ui/uui-card": "1.13.0", + "@umbraco-ui/uui-symbol-file": "1.13.0", + "@umbraco-ui/uui-symbol-folder": "1.13.0" + } + }, + "node_modules/@umbraco-ui/uui-card-user": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-card-user/-/uui-card-user-1.13.0.tgz", + "integrity": "sha512-lAB2IuXvNK8l/n+D9s9cNNUUvBdZE2Uy3UDc0QJla3qo2RLsyM4pSgVeS0Ve+GOI1A4vyK8Sfx68cDptW04Vaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@umbraco-ui/uui-avatar": "1.13.0", + "@umbraco-ui/uui-base": "1.13.0", + "@umbraco-ui/uui-card": "1.13.0" + } + }, + "node_modules/@umbraco-ui/uui-caret": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-caret/-/uui-caret-1.13.0.tgz", + "integrity": "sha512-OCrJISFcRvB6V1+xPS+AjGEp+ue3vyegTRJsLEOVbyCHbrzFwNUKLc2EFYz2rxOGjcFs7Z9M8I6eoLUuMxDQAQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@umbraco-ui/uui-base": "1.13.0" + } + }, + "node_modules/@umbraco-ui/uui-checkbox": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-checkbox/-/uui-checkbox-1.13.0.tgz", + "integrity": "sha512-9ywXUZgC8kMLEgsx1JFH0iftSeI8zzVDVECYq36+dVuz0iWtXfUjb5ygSoUX0guiACVY5gNba/H+t9R+3FbUgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@umbraco-ui/uui-base": "1.13.0", + "@umbraco-ui/uui-boolean-input": "1.13.0", + "@umbraco-ui/uui-icon-registry-essential": "1.13.0" + } + }, + "node_modules/@umbraco-ui/uui-color-area": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-color-area/-/uui-color-area-1.13.0.tgz", + "integrity": "sha512-X7CyxicQYE5VR5iXSY0IsPym0pSYWFLQ9KDgzVIDM3fvoM+KpiGYrDhGTgrHrTpJ3EE8JO06fPrx/mJ2NyxOyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@umbraco-ui/uui-base": "1.13.0", + "colord": "^2.9.3" + } + }, + "node_modules/@umbraco-ui/uui-color-picker": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-color-picker/-/uui-color-picker-1.13.0.tgz", + "integrity": "sha512-ROkz+5+ecZ9GbctpaynL9CeMdXhamY2wPfwjVHyp/QERLXsvhlXIojD0n11Fp4i9FzQsiHb328T5aTnBZ3tqcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@umbraco-ui/uui-base": "1.13.0", + "@umbraco-ui/uui-popover-container": "1.13.0", + "colord": "^2.9.3" + } + }, + "node_modules/@umbraco-ui/uui-color-slider": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-color-slider/-/uui-color-slider-1.13.0.tgz", + "integrity": "sha512-om/OwXDVDNsy0HZIuIv6VXoi5aFBU7KtHfiq7/OLnnWtO5MQREwBCTVthhSFfe7LaZSZnFhFn89hrmw7hfhljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@umbraco-ui/uui-base": "1.13.0" + } + }, + "node_modules/@umbraco-ui/uui-color-swatch": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-color-swatch/-/uui-color-swatch-1.13.0.tgz", + "integrity": "sha512-tiT274ldYjDMFeBQBx3yGu7HgYaNrxjNIrcktlsddfWxxjJ3UNu08YdoP4DqJOi6limQhadBllCBa9oyz4iOig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@umbraco-ui/uui-base": "1.13.0", + "@umbraco-ui/uui-icon-registry-essential": "1.13.0", + "colord": "^2.9.3" + } + }, + "node_modules/@umbraco-ui/uui-color-swatches": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-color-swatches/-/uui-color-swatches-1.13.0.tgz", + "integrity": "sha512-DAZ9cAxIp+kGFeGteDCgt+Om0vcuacmjtT98N1meP/EkPgJf6y21o3y4oySeQMAhWXznr3DBxyHHKN1Jt3do8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@umbraco-ui/uui-base": "1.13.0", + "@umbraco-ui/uui-color-swatch": "1.13.0" + } + }, + "node_modules/@umbraco-ui/uui-combobox": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-combobox/-/uui-combobox-1.13.0.tgz", + "integrity": "sha512-8lKmqQMQkh+yMA4iFonDLwpNf6EZ+pYXhJ2Xcum14WT6UI6BgiQvuM2nmRrkWhqA7Wx0tTAAdP5ILPAl5lENRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@umbraco-ui/uui-base": "1.13.0", + "@umbraco-ui/uui-button": "1.13.0", + "@umbraco-ui/uui-combobox-list": "1.13.0", + "@umbraco-ui/uui-icon": "1.13.0", + "@umbraco-ui/uui-popover-container": "1.13.0", + "@umbraco-ui/uui-scroll-container": "1.13.0", + "@umbraco-ui/uui-symbol-expand": "1.13.0" + } + }, + "node_modules/@umbraco-ui/uui-combobox-list": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-combobox-list/-/uui-combobox-list-1.13.0.tgz", + "integrity": "sha512-ZVRouGMb7VH5wD8a0kE1t71oUMD1gUvFdACPWTjunpgM0ZXk1wOGGtS3vsEaTAkbQ8gABPpsYnCaWBt0MR+RpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@umbraco-ui/uui-base": "1.13.0" + } + }, + "node_modules/@umbraco-ui/uui-css": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-css/-/uui-css-1.13.0.tgz", + "integrity": "sha512-6crDukueGm9t5CBU+d/icouGELQQIQsfi/qT7J6qISRZTvBjoT0FxUxUtpXsIQs1H0qgULhwx8PTKnfQ/AMZFA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "lit": ">=2.8.0" + } + }, + "node_modules/@umbraco-ui/uui-dialog": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-dialog/-/uui-dialog-1.13.0.tgz", + "integrity": "sha512-RzePOwJrhBEYBZAwgvNkIro+cVirLxgaIGNFUgnvoWIovHJNOuSso65MtcGON3nvuQ4JxE8SIOTE/hwT04R7Ag==", + "dev": true, + "license": "MIT", + "dependencies": { + "@umbraco-ui/uui-base": "1.13.0", + "@umbraco-ui/uui-css": "1.13.0" + } + }, + "node_modules/@umbraco-ui/uui-dialog-layout": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-dialog-layout/-/uui-dialog-layout-1.13.0.tgz", + "integrity": "sha512-m8eoCEz0dugWmqrmRw2vHae3k7oYjr53JiOkb8viCMh7veQo4EM0zqZgdCwADs1wES8woOX5zdttp9JtqYenRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@umbraco-ui/uui-base": "1.13.0" + } + }, + "node_modules/@umbraco-ui/uui-file-dropzone": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-file-dropzone/-/uui-file-dropzone-1.13.0.tgz", + "integrity": "sha512-1TFkyeNB3qWWhgc7NYudxXOc3v0cBRuRpVYPA3xocfVkqCG2PgEc7ePW18CtUuuGntGwv0E0Oni2bfSLrqVmuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@umbraco-ui/uui-base": "1.13.0", + "@umbraco-ui/uui-symbol-file-dropzone": "1.13.0" + } + }, + "node_modules/@umbraco-ui/uui-file-preview": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-file-preview/-/uui-file-preview-1.13.0.tgz", + "integrity": "sha512-ZEW2q6If0+3WWHnQp9UPdL+rcI4zUKlyvELDU1JDzx/IVDFJb8f7fI5qhzQjl4kXCVI54Ch4WkBie6RDpNSqVg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@umbraco-ui/uui-base": "1.13.0", + "@umbraco-ui/uui-symbol-file": "1.13.0", + "@umbraco-ui/uui-symbol-file-thumbnail": "1.13.0", + "@umbraco-ui/uui-symbol-folder": "1.13.0" + } + }, + "node_modules/@umbraco-ui/uui-form": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-form/-/uui-form-1.13.0.tgz", + "integrity": "sha512-Y5Wgl3AcixLbPHJJK2yqdga5NMHx5Jv3YvG69+AdPkgzyNmCtdbDitV8ex2ysNYMO3WbBRdYIjbI5pYRl3xn5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@umbraco-ui/uui-base": "1.13.0" + } + }, + "node_modules/@umbraco-ui/uui-form-layout-item": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-form-layout-item/-/uui-form-layout-item-1.13.0.tgz", + "integrity": "sha512-GKNjsvUBbl2ba9e7b88Vk7HuMO9exnGDRpmQ94PSH/rOUF44ri4mPTPFU2k9DCvIkSs7lxDvotXE7kQ5IPQYBw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@umbraco-ui/uui-base": "1.13.0", + "@umbraco-ui/uui-form-validation-message": "1.13.0" + } + }, + "node_modules/@umbraco-ui/uui-form-validation-message": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-form-validation-message/-/uui-form-validation-message-1.13.0.tgz", + "integrity": "sha512-x1E84q6L8DbpBkoS+ykdvmoEUcObXYhym6nhh2lK2TAn7vZu+XD+Osd5rgy5ycZ4YtYnCqetlaPwQwAFqFiSHA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@umbraco-ui/uui-base": "1.13.0" + } + }, + "node_modules/@umbraco-ui/uui-icon": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-icon/-/uui-icon-1.13.0.tgz", + "integrity": "sha512-TKmQi4n8ZV6v228U6zi9f38g/Nu4ok1cbvoIiSfSvmSYNXD1weuWK5y7Ph7EGr6jL5T5vKbDhjcZUIjzBOVWAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@umbraco-ui/uui-base": "1.13.0" + } + }, + "node_modules/@umbraco-ui/uui-icon-registry": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-icon-registry/-/uui-icon-registry-1.13.0.tgz", + "integrity": "sha512-/w7EN7Exi7ST0olPuxFLFm8rw3Mt2QhZsqQWQXXYnb4hTC+ot27IDahQmlLOx85+h/KH3Qz+Tn2NgM3BEdQQ5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@umbraco-ui/uui-base": "1.13.0", + "@umbraco-ui/uui-icon": "1.13.0" + } + }, + "node_modules/@umbraco-ui/uui-icon-registry-essential": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-icon-registry-essential/-/uui-icon-registry-essential-1.13.0.tgz", + "integrity": "sha512-CcuBNg06ewGM6OhwjXCQKm5QDYXySCcc7TQajJ14kfMXtdcO8ls6eI2D8t+Hkc4YN7TQaUeGgzMF746f4TiiNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@umbraco-ui/uui-base": "1.13.0", + "@umbraco-ui/uui-icon-registry": "1.13.0" + } + }, + "node_modules/@umbraco-ui/uui-input": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-input/-/uui-input-1.13.0.tgz", + "integrity": "sha512-2GwLio1SDBofYLZjds47X6Fxq29ORgQBZaD9xwowFaoWCsG+WIFsE7VaE4KgPASUOQYoMMzFZX3F2TdvbjPEAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@umbraco-ui/uui-base": "1.13.0" + } + }, + "node_modules/@umbraco-ui/uui-input-file": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-input-file/-/uui-input-file-1.13.0.tgz", + "integrity": "sha512-wKmFAzThstZPeCOtNtdAX8SZ09T0mJQEA1g+l6EaCV5ikPLSO0kiqmv3P0p0IDf6WSX29+UhcSp2hOVzR+cELg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@umbraco-ui/uui-action-bar": "1.13.0", + "@umbraco-ui/uui-base": "1.13.0", + "@umbraco-ui/uui-button": "1.13.0", + "@umbraco-ui/uui-file-dropzone": "1.13.0", + "@umbraco-ui/uui-icon": "1.13.0", + "@umbraco-ui/uui-icon-registry-essential": "1.13.0" + } + }, + "node_modules/@umbraco-ui/uui-input-lock": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-input-lock/-/uui-input-lock-1.13.0.tgz", + "integrity": "sha512-qChhwO5NsA8es9X41HJ73sXtmvKUF90WBBL8PYgESLkL7zQdvhe9wfJhVjZ1WMJkOc6F7uTAJbawuEVXSX0uKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@umbraco-ui/uui-base": "1.13.0", + "@umbraco-ui/uui-button": "1.13.0", + "@umbraco-ui/uui-icon": "1.13.0", + "@umbraco-ui/uui-input": "1.13.0" + } + }, + "node_modules/@umbraco-ui/uui-input-password": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-input-password/-/uui-input-password-1.13.0.tgz", + "integrity": "sha512-1ljgXM1Ux2J73J2mNd01Xh1Bk7Por0MXk6fQ4TP/qp4A5ohF6CmiBVNWSBkWLfEY7TNHfvOIIIiDGfc0Ko0UFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@umbraco-ui/uui-base": "1.13.0", + "@umbraco-ui/uui-icon-registry-essential": "1.13.0", + "@umbraco-ui/uui-input": "1.13.0" + } + }, + "node_modules/@umbraco-ui/uui-keyboard-shortcut": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-keyboard-shortcut/-/uui-keyboard-shortcut-1.13.0.tgz", + "integrity": "sha512-zKh674a19swyaZiLI/vCws56H4P+lUCIQxu+/U3280zGQqp35vCj0RhvbU2zA4QCCvTEWSrOOQwyu019zEEz5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@umbraco-ui/uui-base": "1.13.0" + } + }, + "node_modules/@umbraco-ui/uui-label": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-label/-/uui-label-1.13.0.tgz", + "integrity": "sha512-BcfvqdFybY0Vb7TVinfHDLrAyhmtayz5ZGXwnTZpwyg7IP+pPZrFunrhcPPTPIrvJEw/j7qgpfC2AKMsiaZq7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@umbraco-ui/uui-base": "1.13.0" + } + }, + "node_modules/@umbraco-ui/uui-loader": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-loader/-/uui-loader-1.13.0.tgz", + "integrity": "sha512-AmNcIX7XNtW9dc29TdlMgTcTJBxU7MCae9iivNLLfFrg3VblltCPeeCOcLx77rw/k9o/IWrhLOsN81Q0HPfs7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@umbraco-ui/uui-base": "1.13.0" + } + }, + "node_modules/@umbraco-ui/uui-loader-bar": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-loader-bar/-/uui-loader-bar-1.13.0.tgz", + "integrity": "sha512-wRJl2n6VesXV5z7EOz3W8DKDo2XLbpb4a9HZHauDtnGl9aNQggcFYBXTrLAvqg2Nuir2g52kQT9mDcQiTDxJLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@umbraco-ui/uui-base": "1.13.0" + } + }, + "node_modules/@umbraco-ui/uui-loader-circle": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-loader-circle/-/uui-loader-circle-1.13.0.tgz", + "integrity": "sha512-efDzwo7uC7bs60boAvJNtqI7CFTe/4R5xdyi9khSF9w/0WnMRgIsY9h7xQLWCycjC/Nvoe/UwBZQ6P5khkdfaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@umbraco-ui/uui-base": "1.13.0" + } + }, + "node_modules/@umbraco-ui/uui-menu-item": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-menu-item/-/uui-menu-item-1.13.0.tgz", + "integrity": "sha512-Rl3y+gXyN4ZLdzhhmCW1dWWC53erFVnVV1OTm2paYk1w13du/T4S+X7J0uyobrc1E3iFTjAFNh0UuvHxRsxtcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@umbraco-ui/uui-base": "1.13.0", + "@umbraco-ui/uui-loader-bar": "1.13.0", + "@umbraco-ui/uui-symbol-expand": "1.13.0" + } + }, + "node_modules/@umbraco-ui/uui-modal": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-modal/-/uui-modal-1.13.0.tgz", + "integrity": "sha512-uiBmQg4gE3S9lreABkLbr4kSRdZAbkxzavBZ27DTDWjeez5Zn+sqy+FckPGct+HZheTdXgGF+M4YRypZj7gOhg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@umbraco-ui/uui-base": "1.13.0" + } + }, + "node_modules/@umbraco-ui/uui-pagination": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-pagination/-/uui-pagination-1.13.0.tgz", + "integrity": "sha512-RtD+szI8P+7y2tKBLLPJyCOlfS504LgQqD4pUOZbxemsQmMe37OZ1CiiqfrNJVEv4TbMHP0WvoRiLFDawICXnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@umbraco-ui/uui-base": "1.13.0", + "@umbraco-ui/uui-button": "1.13.0", + "@umbraco-ui/uui-button-group": "1.13.0" + } + }, + "node_modules/@umbraco-ui/uui-popover": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-popover/-/uui-popover-1.13.0.tgz", + "integrity": "sha512-fpN0x+9p7cxrKiBYzEbpAYuIFYxWlUDrv4jYw3+oEI1ZP2wlS4dKphxhNtLreGrbaYsSUXe8Vgx9wy3eFawfug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@umbraco-ui/uui-base": "1.13.0" + } + }, + "node_modules/@umbraco-ui/uui-popover-container": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-popover-container/-/uui-popover-container-1.13.0.tgz", + "integrity": "sha512-pNvfRLjFzRY7j8bJ1LDGROBZ1+h4HbKqr7O4bs8z8ZfdrxqHb1k/BssbSNt25JFmoHDSRZbFs3yBL9jhVEr/Xg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@umbraco-ui/uui-base": "1.13.0" + } + }, + "node_modules/@umbraco-ui/uui-progress-bar": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-progress-bar/-/uui-progress-bar-1.13.0.tgz", + "integrity": "sha512-QRgydD21AJfmv89WWiim8O/7XR0BTsWP73lga2Tbj3OU/8jjw0vcqmjzf0uhOir5SL1Y0Y1CT/SPUjgxc0VC0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@umbraco-ui/uui-base": "1.13.0" + } + }, + "node_modules/@umbraco-ui/uui-radio": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-radio/-/uui-radio-1.13.0.tgz", + "integrity": "sha512-SsSqyte7n2KEqEjmtI2ajUX6m0AL6nreSZ53IGViMBim8bTcW4oBq5Wbp3dll+s88WvExppozE2iA1kLgjijOw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@umbraco-ui/uui-base": "1.13.0" + } + }, + "node_modules/@umbraco-ui/uui-range-slider": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-range-slider/-/uui-range-slider-1.13.0.tgz", + "integrity": "sha512-gplKOzASnz2spVVkwgj1kYAPFRqp4KRuDFlWyr2IY5luvTajUZC6JOB4aQDs5+OMbgYvF4G+PKFEapuYnR7gNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@umbraco-ui/uui-base": "1.13.0" + } + }, + "node_modules/@umbraco-ui/uui-ref": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref/-/uui-ref-1.13.0.tgz", + "integrity": "sha512-jgVgHFa7/5zcg86Rtkecp7XO9FENQUQ7uMZyCAUHYCPur/n0CKNBrVjwQ/PEI0o1VR+eRGUG5iByZgQW1yWTtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@umbraco-ui/uui-base": "1.13.0" + } + }, + "node_modules/@umbraco-ui/uui-ref-list": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-list/-/uui-ref-list-1.13.0.tgz", + "integrity": "sha512-9Nw03JwymtGnkqvnEeRhmSS+F7Xlzp7yef4R/WdZEIYASV42vwCIAj5Wdj2JI+Apc0ZVZ4hCQhbfNVsr+e7ddQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@umbraco-ui/uui-base": "1.13.0" + } + }, + "node_modules/@umbraco-ui/uui-ref-node": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node/-/uui-ref-node-1.13.0.tgz", + "integrity": "sha512-otjHMgmaekLl3iPwDjLgJ6H7HIWF3QqNLNAiHnGZ1pdShJyLfajvHxnDULeUuI6zRDdjavzz3fhPUnjzyraYpQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@umbraco-ui/uui-base": "1.13.0", + "@umbraco-ui/uui-icon": "1.13.0", + "@umbraco-ui/uui-ref": "1.13.0" + } + }, + "node_modules/@umbraco-ui/uui-ref-node-data-type": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-data-type/-/uui-ref-node-data-type-1.13.0.tgz", + "integrity": "sha512-M5+7ekzpoNJmjD8YvH5TQPb1ENbIwnjyXyUv7IiXV2AtTF/H/g0t4pEU+SYhlrAe61VyW5TedtAMWK+oDKrWUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@umbraco-ui/uui-base": "1.13.0", + "@umbraco-ui/uui-ref-node": "1.13.0" + } + }, + "node_modules/@umbraco-ui/uui-ref-node-document-type": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-document-type/-/uui-ref-node-document-type-1.13.0.tgz", + "integrity": "sha512-AsTjfuAKak/cdaZaJCjuu19YyYwC2FunPP2fz2PuXPr7ULcmo78oYIZY6YJPUsPlBSMN5PIIr9iox3ob6Dd+Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@umbraco-ui/uui-base": "1.13.0", + "@umbraco-ui/uui-ref-node": "1.13.0" + } + }, + "node_modules/@umbraco-ui/uui-ref-node-form": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-form/-/uui-ref-node-form-1.13.0.tgz", + "integrity": "sha512-ySHrq0xH4l69NH12pXzfPrrMG9fRnHF7ul+iKSrPvqUdWnsNpEzYakGvt6XXji1x3ogZEKnKMlzAXrHZDL8LoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@umbraco-ui/uui-base": "1.13.0", + "@umbraco-ui/uui-ref-node": "1.13.0" + } + }, + "node_modules/@umbraco-ui/uui-ref-node-member": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-member/-/uui-ref-node-member-1.13.0.tgz", + "integrity": "sha512-UiSAxsPjpivbI0Hm1fZ1O7nTgkSjPigi9z5RWT4P0viiYetrc8ggJpZ5cDFEQH2xBe40qfBQQGr8vJ4Gsz3opg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@umbraco-ui/uui-base": "1.13.0", + "@umbraco-ui/uui-ref-node": "1.13.0" + } + }, + "node_modules/@umbraco-ui/uui-ref-node-package": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-package/-/uui-ref-node-package-1.13.0.tgz", + "integrity": "sha512-iANsUZDFjLQdalKVI007LuNDlEsruh88peWiBrDN47HtRZlZ/tLV67Ljd5oRjZhAFZLjjFQ1jl0DOkkD3lKIFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@umbraco-ui/uui-base": "1.13.0", + "@umbraco-ui/uui-ref-node": "1.13.0" + } + }, + "node_modules/@umbraco-ui/uui-ref-node-user": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-user/-/uui-ref-node-user-1.13.0.tgz", + "integrity": "sha512-XckwV6nrJjWeeBZX7j28fJdJZlzugyhfXIacp6+AnFHrL5NXjsb3Hi4ZLt00CurcxmhVVta+J5uvmOLSVi7Myw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@umbraco-ui/uui-base": "1.13.0", + "@umbraco-ui/uui-ref-node": "1.13.0" + } + }, + "node_modules/@umbraco-ui/uui-scroll-container": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-scroll-container/-/uui-scroll-container-1.13.0.tgz", + "integrity": "sha512-3Qnl6aGxRs2FYvZzskZYFXnDsej5vBHalu/0b7VKfTPdUMJuAtR+1rz+veLvm9hL5pf9sJbSx4IZe+BubrYmnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@umbraco-ui/uui-base": "1.13.0" + } + }, + "node_modules/@umbraco-ui/uui-select": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-select/-/uui-select-1.13.0.tgz", + "integrity": "sha512-JF4Jtgc/H61tdPVD01kkBRkUofWatrUG9diRMuaGPvQsWEVNvbCJKXJ+Fww9pMQts25EidLhhtqG2hX3ZSsgYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@umbraco-ui/uui-base": "1.13.0" + } + }, + "node_modules/@umbraco-ui/uui-slider": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-slider/-/uui-slider-1.13.0.tgz", + "integrity": "sha512-r4QE+V0LTyn1NAyHsLBkHAvu1jzqfpQ9mOIzwt7ekpuKUrlbaB+LWVo+lxiX7ShUVHxL+0squ0/1CNrLquz0AA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@umbraco-ui/uui-base": "1.13.0" + } + }, + "node_modules/@umbraco-ui/uui-symbol-expand": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-expand/-/uui-symbol-expand-1.13.0.tgz", + "integrity": "sha512-nR6Ld0ZrWQX0Ijk1y+3sRXMaAh87uaQkhcIHgS9Ziz+F1JbCf5WCygla3Xux5t+zpxhPVy6yvZc3iWJxQMp1TA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@umbraco-ui/uui-base": "1.13.0" + } + }, + "node_modules/@umbraco-ui/uui-symbol-file": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-file/-/uui-symbol-file-1.13.0.tgz", + "integrity": "sha512-F3+MyQaGDUYr+Z0VyBmZaIirPKSkON2Gu6jrb8kX04UuqVPIRvoxjubGTmu6wU5M0ATRt/NIG5CzYJp33R8bGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@umbraco-ui/uui-base": "1.13.0" + } + }, + "node_modules/@umbraco-ui/uui-symbol-file-dropzone": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-file-dropzone/-/uui-symbol-file-dropzone-1.13.0.tgz", + "integrity": "sha512-ko3+WSdgd3pG3SI5eUPJ/VbOYTW86AW6EmYDrsALAdRdKhQ5Kusbe7M8Uds8BB3EJ9GT9+xcjocLNMCTxV8soA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@umbraco-ui/uui-base": "1.13.0" + } + }, + "node_modules/@umbraco-ui/uui-symbol-file-thumbnail": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-file-thumbnail/-/uui-symbol-file-thumbnail-1.13.0.tgz", + "integrity": "sha512-fgdJcecVz39MuFTTneD9yI8K/4KZQkHaARfvcWmc2rvRD8S5HzGcp/a+y0zOmzLIpKi3Sjlwc/4d123nE3V0NQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@umbraco-ui/uui-base": "1.13.0" + } + }, + "node_modules/@umbraco-ui/uui-symbol-folder": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-folder/-/uui-symbol-folder-1.13.0.tgz", + "integrity": "sha512-TIh45JTtvv6l+/7+UtSQxxyvtIyiv9tVv1QC4SKesW19opUkWiaNd5awaKlshi+Iu9CbXvCVwxTJ6TK5z3hsaA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@umbraco-ui/uui-base": "1.13.0" + } + }, + "node_modules/@umbraco-ui/uui-symbol-lock": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-lock/-/uui-symbol-lock-1.13.0.tgz", + "integrity": "sha512-/39U8n0DfHNI4I2X1WX8dJv6pSOHeJMvpyS1Cla54Q45gtt7RHMU55aNEGBZoF19oPV2W74gl7vfcHGTtnPKNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@umbraco-ui/uui-base": "1.13.0" + } + }, + "node_modules/@umbraco-ui/uui-symbol-more": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-more/-/uui-symbol-more-1.13.0.tgz", + "integrity": "sha512-mEwbSezTZfG77MUdWrFGXkMaSBHpC899lToDONTnQurkvLBxbBRBlT+xhHo54psAzJX7C6NLRvExTMztGU1OeQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@umbraco-ui/uui-base": "1.13.0" + } + }, + "node_modules/@umbraco-ui/uui-symbol-sort": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-sort/-/uui-symbol-sort-1.13.0.tgz", + "integrity": "sha512-BDcvHXrueX3d6sFcQa5jzxlV1C0OdhymN1Q5GzXpby2xLJTjNbeGISdgHGCxjLPsmHUAAZ7XCGR8pqI0F+8Hpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@umbraco-ui/uui-base": "1.13.0" + } + }, + "node_modules/@umbraco-ui/uui-table": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-table/-/uui-table-1.13.0.tgz", + "integrity": "sha512-CKoPIsqURMtR6PwaSs4UvB56LVLMTop93gArI//yN9Ox1/w7awxnnkRN2skpKIbtDHrbEBI//NJE50jPoS82eg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@umbraco-ui/uui-base": "1.13.0" + } + }, + "node_modules/@umbraco-ui/uui-tabs": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-tabs/-/uui-tabs-1.13.0.tgz", + "integrity": "sha512-b50xJ1Xka8nu51GCR8n2RZtCFjwTYDXV5zQF+s5KXpgC6A8mahCvzmmINHdgGnKm1JNG3c8abhwrueyxxVdI8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@umbraco-ui/uui-base": "1.13.0", + "@umbraco-ui/uui-button": "1.13.0", + "@umbraco-ui/uui-popover-container": "1.13.0", + "@umbraco-ui/uui-symbol-more": "1.13.0" + } + }, + "node_modules/@umbraco-ui/uui-tag": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-tag/-/uui-tag-1.13.0.tgz", + "integrity": "sha512-SBY9Mi9A89jIag7URKQqXW3omDk5Eczw2LuNF7VnkXmQCuvsiRP6/BzSBCh9m0RrD4QOLSXpYGgSoLSpS7MitA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@umbraco-ui/uui-base": "1.13.0" + } + }, + "node_modules/@umbraco-ui/uui-textarea": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-textarea/-/uui-textarea-1.13.0.tgz", + "integrity": "sha512-H4XChy1m5gq43eySQ3Zp/AsBvh35Gk0VLijFxdhCfV+HHpuyrt0fJsYnjq1W1xoqhyt7h84YRpNIJMyAIm2WHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@umbraco-ui/uui-base": "1.13.0" + } + }, + "node_modules/@umbraco-ui/uui-toast-notification": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-toast-notification/-/uui-toast-notification-1.13.0.tgz", + "integrity": "sha512-o45G8hWXgqcfGaJM+nhCTDSpevREJd+gPKT5XhTkD2wA99/kevdedmlYIgKS+9wONLk5A0j8qnsbWntinbb+rw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@umbraco-ui/uui-base": "1.13.0", + "@umbraco-ui/uui-button": "1.13.0", + "@umbraco-ui/uui-css": "1.13.0", + "@umbraco-ui/uui-icon": "1.13.0", + "@umbraco-ui/uui-icon-registry-essential": "1.13.0" + } + }, + "node_modules/@umbraco-ui/uui-toast-notification-container": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-toast-notification-container/-/uui-toast-notification-container-1.13.0.tgz", + "integrity": "sha512-9O0t73v7qkb3+VE8i0pD1vo33tNt1U7t3L6699jNMZZr+7R6a5YOAVrFt+gs+kQcQXWt0HCfQxhKJ8opLoBOyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@umbraco-ui/uui-base": "1.13.0", + "@umbraco-ui/uui-toast-notification": "1.13.0" + } + }, + "node_modules/@umbraco-ui/uui-toast-notification-layout": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-toast-notification-layout/-/uui-toast-notification-layout-1.13.0.tgz", + "integrity": "sha512-yhz8msOc1ngA//oBDefrR8pagTbvAenBiyk/fPuEwGQriM43e8bbVCJvhmrsTuAzAL8nn/ilKhAU5lrkn2rAmg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@umbraco-ui/uui-base": "1.13.0", + "@umbraco-ui/uui-css": "1.13.0" + } + }, + "node_modules/@umbraco-ui/uui-toggle": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-toggle/-/uui-toggle-1.13.0.tgz", + "integrity": "sha512-tHzG/Lh9vRLjPu7EhFupaD7jkpVenyEM3iIsA24wBVKmqJGxacpuuuOwpTv6vGGiIYSKfRDXTDk07Q6MHDSy4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@umbraco-ui/uui-base": "1.13.0", + "@umbraco-ui/uui-boolean-input": "1.13.0" + } + }, + "node_modules/@umbraco-ui/uui-visually-hidden": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-visually-hidden/-/uui-visually-hidden-1.13.0.tgz", + "integrity": "sha512-1ayTJylWnpAl0VQE7X2PBJCKLZ15R+xfZ3yy4ygT751k4wML26nvdWscp/tYfl4MteqrHtNJKTRTFoQ1Dn/r/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@umbraco-ui/uui-base": "1.13.0" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-escapes/node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-width": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", + "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", + "engines": { + "node": ">= 12" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/colord": { + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", + "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==", + "dev": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/esbuild": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.12.tgz", + "integrity": "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.19.12", + "@esbuild/android-arm": "0.19.12", + "@esbuild/android-arm64": "0.19.12", + "@esbuild/android-x64": "0.19.12", + "@esbuild/darwin-arm64": "0.19.12", + "@esbuild/darwin-x64": "0.19.12", + "@esbuild/freebsd-arm64": "0.19.12", + "@esbuild/freebsd-x64": "0.19.12", + "@esbuild/linux-arm": "0.19.12", + "@esbuild/linux-arm64": "0.19.12", + "@esbuild/linux-ia32": "0.19.12", + "@esbuild/linux-loong64": "0.19.12", + "@esbuild/linux-mips64el": "0.19.12", + "@esbuild/linux-ppc64": "0.19.12", + "@esbuild/linux-riscv64": "0.19.12", + "@esbuild/linux-s390x": "0.19.12", + "@esbuild/linux-x64": "0.19.12", + "@esbuild/netbsd-x64": "0.19.12", + "@esbuild/openbsd-x64": "0.19.12", + "@esbuild/sunos-x64": "0.19.12", + "@esbuild/win32-arm64": "0.19.12", + "@esbuild/win32-ia32": "0.19.12", + "@esbuild/win32-x64": "0.19.12" + } + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/figures": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "dependencies": { + "escape-string-regexp": "^1.0.5" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/graphql": { + "version": "16.8.1", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.8.1.tgz", + "integrity": "sha512-59LZHPdGZVh695Ud9lRzPBVTtlX9ZCV150Er2W43ro37wVof0ctenSaskPPjN7lVTIN8mSZt8PHUNKZuNQUuxw==", + "engines": { + "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/headers-polyfill": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/headers-polyfill/-/headers-polyfill-4.0.2.tgz", + "integrity": "sha512-EWGTfnTqAO2L/j5HZgoM/3z82L7necsJ0pO9Tp0X1wil3PDLrkypTBRgVO2ExehEEvUycejZD3FuRaXpZZc3kw==" + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-node-process": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-node-process/-/is-node-process-1.2.0.tgz", + "integrity": "sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw==" + }, + "node_modules/lit": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/lit/-/lit-3.1.2.tgz", + "integrity": "sha512-VZx5iAyMtX7CV4K8iTLdCkMaYZ7ipjJZ0JcSdJ0zIdGxxyurjIn7yuuSxNBD7QmjvcNJwr0JS4cAdAtsy7gZ6w==", + "dependencies": { + "@lit/reactive-element": "^2.0.4", + "lit-element": "^4.0.4", + "lit-html": "^3.1.2" + } + }, + "node_modules/lit-element": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lit-element/-/lit-element-4.0.4.tgz", + "integrity": "sha512-98CvgulX6eCPs6TyAIQoJZBCQPo80rgXR+dVBs61cstJXqtI+USQZAbA4gFHh6L/mxBx9MrgPLHLsUgDUHAcCQ==", + "dependencies": { + "@lit-labs/ssr-dom-shim": "^1.2.0", + "@lit/reactive-element": "^2.0.4", + "lit-html": "^3.1.2" + } + }, + "node_modules/lit-html": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-3.1.2.tgz", + "integrity": "sha512-3OBZSUrPnAHoKJ9AMjRL/m01YJxQMf+TMHanNtTHG68ubjnZxK0RFl102DPzsw4mWnHibfZIBJm3LWCZ/LmMvg==", + "dependencies": { + "@types/trusted-types": "^2.0.2" + } + }, + "node_modules/msw": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/msw/-/msw-2.2.0.tgz", + "integrity": "sha512-98cUGcIphhdf3KDbmSxji7XFqLxeSFAmPUNV00N/U76GOkuUKEwp6MHqM6KW70rlpgeJP8qIWueppdnVThzG1g==", + "hasInstallScript": true, + "dependencies": { + "@bundled-es-modules/cookie": "^2.0.0", + "@bundled-es-modules/statuses": "^1.0.1", + "@inquirer/confirm": "^3.0.0", + "@mswjs/cookies": "^1.1.0", + "@mswjs/interceptors": "^0.25.16", + "@open-draft/until": "^2.1.0", + "@types/cookie": "^0.6.0", + "@types/statuses": "^2.0.4", + "chalk": "^4.1.2", + "graphql": "^16.8.1", + "headers-polyfill": "^4.0.2", + "is-node-process": "^1.2.0", + "outvariant": "^1.4.2", + "path-to-regexp": "^6.2.0", + "strict-event-emitter": "^0.5.1", + "type-fest": "^4.9.0", + "yargs": "^17.7.2" + }, + "bin": { + "msw": "cli/index.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mswjs" + }, + "peerDependencies": { + "typescript": ">= 4.7.x <= 5.3.x" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/mute-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-1.0.0.tgz", + "integrity": "sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/outvariant": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/outvariant/-/outvariant-1.4.2.tgz", + "integrity": "sha512-Ou3dJ6bA/UJ5GVHxah4LnqDwZRwAmWxrG3wtrHrbGnP4RnLCtA64A4F+ae7Y8ww660JaddSoArUR5HjipWSHAQ==" + }, + "node_modules/path-to-regexp": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.1.tgz", + "integrity": "sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==" + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "node_modules/postcss": { + "version": "8.4.35", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz", + "integrity": "sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.10.0.tgz", + "integrity": "sha512-t2v9G2AKxcQ8yrG+WGxctBes1AomT0M4ND7jTFBCVPXQ/WFTvNSefIrNSmLKhIKBrvN8SG+CZslimJcT3W2u2g==", + "dev": true, + "dependencies": { + "@types/estree": "1.0.5" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.10.0", + "@rollup/rollup-android-arm64": "4.10.0", + "@rollup/rollup-darwin-arm64": "4.10.0", + "@rollup/rollup-darwin-x64": "4.10.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.10.0", + "@rollup/rollup-linux-arm64-gnu": "4.10.0", + "@rollup/rollup-linux-arm64-musl": "4.10.0", + "@rollup/rollup-linux-riscv64-gnu": "4.10.0", + "@rollup/rollup-linux-x64-gnu": "4.10.0", + "@rollup/rollup-linux-x64-musl": "4.10.0", + "@rollup/rollup-win32-arm64-msvc": "4.10.0", + "@rollup/rollup-win32-ia32-msvc": "4.10.0", + "@rollup/rollup-win32-x64-msvc": "4.10.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-async": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-3.0.0.tgz", + "integrity": "sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/rxjs": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/strict-event-emitter": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/strict-event-emitter/-/strict-event-emitter-0.5.1.tgz", + "integrity": "sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==" + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tslib": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", + "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==" + }, + "node_modules/type-fest": { + "version": "4.10.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.10.2.tgz", + "integrity": "sha512-anpAG63wSpdEbLwOqH8L84urkL6PiVIov3EMmgIhhThevh9aiMQov+6Btx0wldNcvm4wV+e2/Rt1QdDwKHFbHw==", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", + "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", + "devOptional": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" + }, + "node_modules/vite": { + "version": "5.1.7", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.7.tgz", + "integrity": "sha512-sgnEEFTZYMui/sTlH1/XEnVNHMujOahPLGMxn1+5sIT45Xjng1Ec1K78jRP15dSmVgg5WBin9yO81j3o9OxofA==", + "dev": true, + "dependencies": { + "esbuild": "^0.19.3", + "postcss": "^8.4.35", + "rollup": "^4.2.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "engines": { + "node": ">=12" + } + } + } } diff --git a/src/Umbraco.Web.UI.Login/package.json b/src/Umbraco.Web.UI.Login/package.json index 12803ad6bea8..6dab12846d97 100644 --- a/src/Umbraco.Web.UI.Login/package.json +++ b/src/Umbraco.Web.UI.Login/package.json @@ -1,29 +1,29 @@ { - "name": "login", - "private": true, + "name": "login", + "private": true, "type": "module", - "scripts": { - "dev": "vite", - "build": "tsc && vite build", - "watch": "tsc && vite build --watch", - "preview": "vite preview" - }, - "engines": { - "node": ">=20.8", - "npm": ">=10.1" - }, - "dependencies": { - "lit": "^3.1.2", - "msw": "^2.2.0", - "rxjs": "^7.8.1" + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "watch": "tsc && vite build --watch", + "preview": "vite preview" }, - "devDependencies": { - "@umbraco-ui/uui": "1.12.2", - "@umbraco-ui/uui-css": "1.12.1", - "typescript": "^5.3.3", - "vite": "^5.1.7" - }, - "msw": { - "workerDirectory": "public" - } + "engines": { + "node": ">=20.8", + "npm": ">=10.1" + }, + "dependencies": { + "lit": "^3.1.2", + "msw": "^2.2.0", + "rxjs": "^7.8.1" + }, + "devDependencies": { + "@umbraco-ui/uui": "1.13.0", + "@umbraco-ui/uui-css": "1.13.0", + "typescript": "^5.3.3", + "vite": "^5.1.7" + }, + "msw": { + "workerDirectory": "public" + } } diff --git a/src/Umbraco.Web.UI.Login/src/auth.element.ts b/src/Umbraco.Web.UI.Login/src/auth.element.ts index e195f3986a21..0d34c67e8560 100644 --- a/src/Umbraco.Web.UI.Login/src/auth.element.ts +++ b/src/Umbraco.Web.UI.Login/src/auth.element.ts @@ -99,7 +99,11 @@ export default class UmbAuthElement extends LitElement { @property({attribute: 'return-url'}) set returnPath(value: string) { - umbAuthContext.returnPath = value; + if (value) { + umbAuthContext.returnPath = `${value}${encodeURIComponent(window.location.hash)}`; + } else { + umbAuthContext.returnPath = value; + } } get returnPath() { return umbAuthContext.returnPath; diff --git a/src/Umbraco.Web.UI.Login/src/components/external-login-provider.element.ts b/src/Umbraco.Web.UI.Login/src/components/external-login-provider.element.ts index 0729f97027d2..ffa4e6e1f4ea 100644 --- a/src/Umbraco.Web.UI.Login/src/components/external-login-provider.element.ts +++ b/src/Umbraco.Web.UI.Login/src/components/external-login-provider.element.ts @@ -74,7 +74,12 @@ export class UmbExternalLoginProviderElement extends LitElement { set externalLoginUrl(value: string) { const tempUrl = new URL(value, window.location.origin); const searchParams = new URLSearchParams(window.location.search); - tempUrl.searchParams.append('redirectUrl', decodeURIComponent(searchParams.get('returnPath') ?? '')); + let returnUrl = decodeURIComponent(searchParams.get('returnPath') ?? ''); + if(!returnUrl && window.location.hash) { + returnUrl = `/umbraco${window.location.hash}`; + } + + tempUrl.searchParams.append('redirectUrl', returnUrl); this.#externalLoginUrl = tempUrl.pathname + tempUrl.search; } diff --git a/src/Umbraco.Web.UI.New.Client b/src/Umbraco.Web.UI.New.Client new file mode 160000 index 000000000000..c48ede4dee5c --- /dev/null +++ b/src/Umbraco.Web.UI.New.Client @@ -0,0 +1 @@ +Subproject commit c48ede4dee5c9f1fd7986042edb67ddee5258054 diff --git a/src/Umbraco.Web.Website/Cache/PartialViewCacheInvalidators/MemberPartialViewCacheInvalidator.cs b/src/Umbraco.Web.Website/Cache/PartialViewCacheInvalidators/MemberPartialViewCacheInvalidator.cs new file mode 100644 index 000000000000..8ee4be4c00ee --- /dev/null +++ b/src/Umbraco.Web.Website/Cache/PartialViewCacheInvalidators/MemberPartialViewCacheInvalidator.cs @@ -0,0 +1,38 @@ +using Umbraco.Cms.Core.Cache; +using Umbraco.Cms.Core.Cache.PartialViewCacheInvalidators; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Web.Website.Cache.PartialViewCacheInvalidators; + +/// +/// Implementation of that only remove cached partial views +/// that were cached for the specified member(s). +/// +public class MemberPartialViewCacheInvalidator : IMemberPartialViewCacheInvalidator +{ + private readonly AppCaches _appCaches; + + /// + /// Initializes a new instance of the class. + /// + public MemberPartialViewCacheInvalidator(AppCaches appCaches) => _appCaches = appCaches; + + /// + /// + /// Partial view cache keys follow the following format: + /// [] is optional or only added if the information is available + /// {} is a parameter + /// "Umbraco.Web.PartialViewCacheKey{partialViewName}-[{currentThreadCultureName}-][m{memberId}-][c{contextualKey}-]" + /// See for more information. + /// + public void ClearPartialViewCacheItems(IEnumerable memberIds) + { + foreach (var memberId in memberIds) + { + _appCaches.RuntimeCache.ClearByRegex($"{CoreCacheHelperExtensions.PartialViewCacheKey}.*-m{memberId}-*"); + } + + // since it is possible to add a cache item linked to members without a member logged in, we should always clear these items. + _appCaches.RuntimeCache.ClearByRegex($"{CoreCacheHelperExtensions.PartialViewCacheKey}.*-m-*"); + } +} diff --git a/src/Umbraco.Web.Website/DependencyInjection/UmbracoBuilderExtensions.cs b/src/Umbraco.Web.Website/DependencyInjection/UmbracoBuilderExtensions.cs index f182bda7b6f2..c48e94afb32e 100644 --- a/src/Umbraco.Web.Website/DependencyInjection/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.Web.Website/DependencyInjection/UmbracoBuilderExtensions.cs @@ -5,6 +5,7 @@ using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Cache.PartialViewCacheInvalidators; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Routing; @@ -13,6 +14,7 @@ using Umbraco.Cms.Infrastructure.DependencyInjection; using Umbraco.Cms.Web.Common.Middleware; using Umbraco.Cms.Web.Common.Routing; +using Umbraco.Cms.Web.Website.Cache.PartialViewCacheInvalidators; using Umbraco.Cms.Web.Website.Collections; using Umbraco.Cms.Web.Website.Models; using Umbraco.Cms.Web.Website.Routing; @@ -74,6 +76,9 @@ public static IUmbracoBuilder AddWebsite(this IUmbracoBuilder builder) builder.Services.AddSingleton(); builder.Services.AddSingleton(); + // Partial view cache invalidators + builder.Services.AddUnique(); + builder .AddDistributedCache() .AddModelsBuilder(); diff --git a/src/Umbraco.Web.Website/Extensions/HtmlHelperRenderExtensions.cs b/src/Umbraco.Web.Website/Extensions/HtmlHelperRenderExtensions.cs index d14f0bded3d2..388c72c25ef2 100644 --- a/src/Umbraco.Web.Website/Extensions/HtmlHelperRenderExtensions.cs +++ b/src/Umbraco.Web.Website/Extensions/HtmlHelperRenderExtensions.cs @@ -104,7 +104,45 @@ public static IHtmlContent PreviewBadge( ViewDataDictionary? viewData = null, Func? contextualKeyBuilder = null) { - var cacheKey = new StringBuilder(partialViewName); + IUmbracoContextAccessor umbracoContextAccessor = GetRequiredService(htmlHelper); + umbracoContextAccessor.TryGetUmbracoContext(out IUmbracoContext? umbracoContext); + + string cacheKey = await GenerateCacheKeyForCachedPartialViewAsync( + partialViewName, + cacheByPage, + umbracoContext, + cacheByMember, + cacheByMember ? GetRequiredService(htmlHelper) : null, + model, + viewData, + contextualKeyBuilder); + + AppCaches appCaches = GetRequiredService(htmlHelper); + IHostingEnvironment hostingEnvironment = GetRequiredService(htmlHelper); + + return appCaches.CachedPartialView( + hostingEnvironment, + umbracoContext!, + htmlHelper, + partialViewName, + model, + cacheTimeout, + cacheKey.ToString(), + viewData); + } + + // Internal for tests. + internal static async Task GenerateCacheKeyForCachedPartialViewAsync( + string partialViewName, + bool cacheByPage, + IUmbracoContext? umbracoContext, + bool cacheByMember, + IMemberManager? memberManager, + object model, + ViewDataDictionary? viewData, + Func? contextualKeyBuilder) + { + var cacheKey = new StringBuilder(partialViewName + "-"); // let's always cache by the current culture to allow variants to have different cache results var cultureName = Thread.CurrentThread.CurrentUICulture.Name; @@ -113,15 +151,12 @@ public static IHtmlContent PreviewBadge( cacheKey.AppendFormat("{0}-", cultureName); } - IUmbracoContextAccessor umbracoContextAccessor = GetRequiredService(htmlHelper); - umbracoContextAccessor.TryGetUmbracoContext(out IUmbracoContext? umbracoContext); - if (cacheByPage) { if (umbracoContext == null) { throw new InvalidOperationException( - "Cannot cache by page if the UmbracoContext has not been initialized, this parameter can only be used in the context of an Umbraco request"); + "Cannot cache by page if the UmbracoContext has not been initialized, this parameter can only be used in the context of an Umbraco request."); } cacheKey.AppendFormat("{0}-", umbracoContext.PublishedRequest?.PublishedContent?.Id ?? 0); @@ -129,8 +164,12 @@ public static IHtmlContent PreviewBadge( if (cacheByMember) { - IMemberManager memberManager = - htmlHelper.ViewContext.HttpContext.RequestServices.GetRequiredService(); + if (memberManager == null) + { + throw new InvalidOperationException( + "Cannot cache by member if the MemberManager is not available."); + } + MemberIdentityUser? currentMember = await memberManager.GetCurrentMemberAsync(); cacheKey.AppendFormat("m{0}-", currentMember?.Id ?? "0"); } @@ -141,18 +180,7 @@ public static IHtmlContent PreviewBadge( cacheKey.AppendFormat("c{0}-", contextualKey); } - AppCaches appCaches = GetRequiredService(htmlHelper); - IHostingEnvironment hostingEnvironment = GetRequiredService(htmlHelper); - - return appCaches.CachedPartialView( - hostingEnvironment, - umbracoContext!, - htmlHelper, - partialViewName, - model, - cacheTimeout, - cacheKey.ToString(), - viewData); + return cacheKey.ToString(); } // public static IHtmlContent EditorFor(this IHtmlHelper htmlHelper, string templateName = "", string htmlFieldName = "", object additionalViewData = null) diff --git a/src/Umbraco.Web.Website/Routing/EagerMatcherPolicy.cs b/src/Umbraco.Web.Website/Routing/EagerMatcherPolicy.cs index b6cd4a361522..1f6ff7e2f00f 100644 --- a/src/Umbraco.Web.Website/Routing/EagerMatcherPolicy.cs +++ b/src/Umbraco.Web.Website/Routing/EagerMatcherPolicy.cs @@ -1,4 +1,4 @@ -using System.Reflection; +using System.Reflection; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.AspNetCore.Routing; @@ -126,8 +126,20 @@ public async Task ApplyAsync(HttpContext httpContext, CandidateSet candidates) return; } + // If it's an attribute routed IVirtualPageController with a Host attribute we should ignore if the host doesn't match the current request. + // Maybe we would expect that it wouldn't be in the provided CandidateSet, but it will be included just based on the Route. + // See: https://github.com/umbraco/Umbraco-CMS/issues/16816 + if (controllerTypeInfo is not null && controllerTypeInfo.IsType()) + { + HostAttribute? hostAttribute = controllerTypeInfo.GetCustomAttribute(); + if (hostAttribute is not null && hostAttribute.Hosts.InvariantContains(httpContext.Request.Host.Value) is false) + { + continue; + } + } + // If it's an UmbracoPageController we need to do some domain routing. - // We need to do this in oder to handle cultures for our Dictionary. + // We need to do this in order to handle cultures for our Dictionary. // This is because UmbracoPublishedContentCultureProvider is ued to set the Thread.CurrentThread.CurrentUICulture // The CultureProvider is run before the actual routing, this means that our UmbracoVirtualPageFilterAttribute is hit AFTER the culture is set. // Meaning we have to route the domain part already now, this is not pretty, but it beats having to look for content we know doesn't exist. diff --git a/tests/Umbraco.Tests.AcceptanceTest/playwright.config.ts b/tests/Umbraco.Tests.AcceptanceTest/playwright.config.ts index e72026e0e8f7..8c54205e10ad 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/playwright.config.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/playwright.config.ts @@ -25,7 +25,7 @@ const config: PlaywrightTestConfig = { // We don't want to run parallel, as tests might differ in state workers: 1, /* Reporter to use. See https://playwright.dev/docs/test-reporters */ - reporter: process.env.CI ? 'line' : 'html', + reporter: process.env.CI ? [['line'], ['junit', {outputFile: 'results/results.xml'}]] : 'html', outputDir : "./results", /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/BlockGridEditor/Content/blockGridEditorRendering.spec.ts-snapshots/Block-grid-editor-chromium-linux.png b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/BlockGridEditor/Content/blockGridEditorRendering.spec.ts-snapshots/Block-grid-editor-chromium-linux.png index 364e1ed8ec6f..24f7428a4eff 100644 Binary files a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/BlockGridEditor/Content/blockGridEditorRendering.spec.ts-snapshots/Block-grid-editor-chromium-linux.png and b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/BlockGridEditor/Content/blockGridEditorRendering.spec.ts-snapshots/Block-grid-editor-chromium-linux.png differ diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/BlockGridEditor/Content/blockGridEditorRendering.spec.ts-snapshots/Block-grid-editor-with-area-chromium-linux.png b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/BlockGridEditor/Content/blockGridEditorRendering.spec.ts-snapshots/Block-grid-editor-with-area-chromium-linux.png index 161e2705e816..7655665aa247 100644 Binary files a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/BlockGridEditor/Content/blockGridEditorRendering.spec.ts-snapshots/Block-grid-editor-with-area-chromium-linux.png and b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/BlockGridEditor/Content/blockGridEditorRendering.spec.ts-snapshots/Block-grid-editor-with-area-chromium-linux.png differ diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/BlockGridEditor/Content/blockGridEditorRendering.spec.ts-snapshots/Block-grid-editor-with-area-with-area-chromium-linux.png b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/BlockGridEditor/Content/blockGridEditorRendering.spec.ts-snapshots/Block-grid-editor-with-area-with-area-chromium-linux.png index 4ba97a629d44..ee289cf00b28 100644 Binary files a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/BlockGridEditor/Content/blockGridEditorRendering.spec.ts-snapshots/Block-grid-editor-with-area-with-area-chromium-linux.png and b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/BlockGridEditor/Content/blockGridEditorRendering.spec.ts-snapshots/Block-grid-editor-with-area-with-area-chromium-linux.png differ diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/BlockGridEditor/Content/blockGridEditorRendering.spec.ts-snapshots/Block-grid-editor-with-custom-stylesheet-chromium-linux.png b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/BlockGridEditor/Content/blockGridEditorRendering.spec.ts-snapshots/Block-grid-editor-with-custom-stylesheet-chromium-linux.png index 95b1bc2969e0..2306d22fc8f7 100644 Binary files a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/BlockGridEditor/Content/blockGridEditorRendering.spec.ts-snapshots/Block-grid-editor-with-custom-stylesheet-chromium-linux.png and b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/BlockGridEditor/Content/blockGridEditorRendering.spec.ts-snapshots/Block-grid-editor-with-custom-stylesheet-chromium-linux.png differ diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/BlockGridEditor/Content/blockGridEditorRendering.spec.ts-snapshots/Block-grid-editor-with-multiple-areas-chromium-linux.png b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/BlockGridEditor/Content/blockGridEditorRendering.spec.ts-snapshots/Block-grid-editor-with-multiple-areas-chromium-linux.png index dad19a45ee89..39cfcc426821 100644 Binary files a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/BlockGridEditor/Content/blockGridEditorRendering.spec.ts-snapshots/Block-grid-editor-with-multiple-areas-chromium-linux.png and b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/BlockGridEditor/Content/blockGridEditorRendering.spec.ts-snapshots/Block-grid-editor-with-multiple-areas-chromium-linux.png differ diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/BlockGridEditor/Content/blockGridEditorRendering.spec.ts-snapshots/Block-grid-editor-with-two-custom-stylesheets-chromium-linux.png b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/BlockGridEditor/Content/blockGridEditorRendering.spec.ts-snapshots/Block-grid-editor-with-two-custom-stylesheets-chromium-linux.png index 17cdeda52176..dcca637c0327 100644 Binary files a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/BlockGridEditor/Content/blockGridEditorRendering.spec.ts-snapshots/Block-grid-editor-with-two-custom-stylesheets-chromium-linux.png and b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/BlockGridEditor/Content/blockGridEditorRendering.spec.ts-snapshots/Block-grid-editor-with-two-custom-stylesheets-chromium-linux.png differ diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/BlockGridEditor/Content/blockGridEditorRendering.spec.ts-snapshots/Block-grid-editor-with-two-elements-chromium-linux.png b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/BlockGridEditor/Content/blockGridEditorRendering.spec.ts-snapshots/Block-grid-editor-with-two-elements-chromium-linux.png index 9cd0b5d3c21f..4bfba7608b2e 100644 Binary files a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/BlockGridEditor/Content/blockGridEditorRendering.spec.ts-snapshots/Block-grid-editor-with-two-elements-chromium-linux.png and b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/BlockGridEditor/Content/blockGridEditorRendering.spec.ts-snapshots/Block-grid-editor-with-two-elements-chromium-linux.png differ diff --git a/tests/Umbraco.Tests.Integration/DependencyInjection/UmbracoBuilderExtensions.cs b/tests/Umbraco.Tests.Integration/DependencyInjection/UmbracoBuilderExtensions.cs index bae9c7efbee9..2116dabff44d 100644 --- a/tests/Umbraco.Tests.Integration/DependencyInjection/UmbracoBuilderExtensions.cs +++ b/tests/Umbraco.Tests.Integration/DependencyInjection/UmbracoBuilderExtensions.cs @@ -11,6 +11,7 @@ using Moq; using NUnit.Framework; using Umbraco.Cms.Core.Cache; +using Umbraco.Cms.Core.Cache.PartialViewCacheInvalidators; using Umbraco.Cms.Core.Composing; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.DistributedLocking; @@ -43,6 +44,8 @@ public static class UmbracoBuilderExtensions public static IUmbracoBuilder AddTestServices(this IUmbracoBuilder builder, TestHelper testHelper) { builder.Services.AddUnique(AppCaches.NoCache); + builder.Services.AddUnique(Mock.Of()); + builder.Services.AddUnique(Mock.Of()); builder.Services.AddUnique(testHelper.MainDom); diff --git a/tests/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs b/tests/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs index d9bdb0097611..a8985b1627a9 100644 --- a/tests/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs +++ b/tests/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs @@ -56,6 +56,8 @@ public abstract class UmbracoIntegrationTest : UmbracoIntegrationTestBase protected IShortStringHelper ShortStringHelper => Services.GetRequiredService(); + protected IIdKeyMap IdKeyMap => Services.GetRequiredService(); + protected GlobalSettings GlobalSettings => Services.GetRequiredService>().Value; protected IMapperCollection Mappers => Services.GetRequiredService(); diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Core/DeliveryApi/OpenApiContractTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Core/DeliveryApi/OpenApiContractTest.cs index d3328f2c1447..5b16458f19e3 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Core/DeliveryApi/OpenApiContractTest.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Core/DeliveryApi/OpenApiContractTest.cs @@ -2269,6 +2269,10 @@ public async Task Validate_OpenApi_Contract() "type": "string", "readOnly": true }, + "queryString": { + "type": "string", + "nullable": true + }, "startItem": { "$ref": "#/components/schemas/IApiContentStartItemModel" } diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Core/DeliveryApi/RequestRoutingServiceTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Core/DeliveryApi/RequestRoutingServiceTests.cs new file mode 100644 index 000000000000..50c5406983e3 --- /dev/null +++ b/tests/Umbraco.Tests.Integration/Umbraco.Core/DeliveryApi/RequestRoutingServiceTests.cs @@ -0,0 +1,74 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; +using Moq; +using NUnit.Framework; +using Umbraco.Cms.Api.Delivery.Services; +using Umbraco.Cms.Core.Cache; +using Umbraco.Cms.Core.DeliveryApi; +using Umbraco.Cms.Core.PublishedCache; +using Umbraco.Cms.Core.Routing; +using Umbraco.Cms.Tests.Common.Testing; +using Umbraco.Cms.Tests.Integration.Testing; + +namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.DeliveryApi; + +[TestFixture] +[UmbracoTest( + Database = UmbracoTestOptions.Database.NewSchemaPerFixture, + WithApplication = true)] +public class RequestRoutingServiceTests : UmbracoIntegrationTest +{ + private IRequestRoutingService RequestRoutingService => GetRequiredService(); + + protected override void CustomTestSetup(IUmbracoBuilder builder) + { + builder.Services.AddUnique(); + + var elementCache = new FastDictionaryAppCache(); + var snapshotCache = new FastDictionaryAppCache(); + + var domainCacheMock = new Mock(); + domainCacheMock.Setup(x => x.GetAll(It.IsAny())) + .Returns( + [ + new Domain(1, "localhost/en", 1000, "en-us", false, 0), + new Domain(2, "localhost/jp", 1000, "ja-jp", false, 1), + ]); + var publishedSnapshotMock = new Mock(); + publishedSnapshotMock.SetupGet(p => p.ElementsCache).Returns(elementCache); + publishedSnapshotMock.SetupGet(p => p.SnapshotCache).Returns(snapshotCache); + publishedSnapshotMock.SetupGet(p => p.Domains).Returns(domainCacheMock.Object); + + var publishedSnapshot = publishedSnapshotMock.Object; + var publishedSnapshotAccessor = new Mock(); + publishedSnapshotAccessor.Setup(p => p.TryGetPublishedSnapshot(out publishedSnapshot)).Returns(true); + builder.Services.AddSingleton(provider => publishedSnapshotAccessor.Object); + } + + [TestCase(null, "")] + [TestCase("", "")] + [TestCase("/", "/")] + [TestCase("/en/test/", "1000/test/")] // Verifies matching a domain. + [TestCase("/da/test/", "/da/test/")] // Verifies that with no matching domain, so route will be returned as is. + [TestCase("/jp/オフィス/", "1000/オフィス/")] // Verifies that with a URL segment containing special characters, the route remains decoded. + public void GetContentRoute_ReturnsExpectedRoute(string? requestedRoute, string expectedResult) + { + if (!string.IsNullOrEmpty(requestedRoute)) + { + var httpContextAccessor = GetRequiredService(); + + httpContextAccessor.HttpContext = new DefaultHttpContext + { + Request = + { + Scheme = "https", + Host = new HostString("localhost"), + Path = requestedRoute, + }, + }; + } + + var result = RequestRoutingService.GetContentRoute(requestedRoute); + Assert.AreEqual(expectedResult, result); + } +} diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/BackOfficeExamineSearcherTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/BackOfficeExamineSearcherTests.cs index 3447dff634db..6ed7e29a6dd4 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/BackOfficeExamineSearcherTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/BackOfficeExamineSearcherTests.cs @@ -73,9 +73,9 @@ protected override void CustomTestSetup(IUmbracoBuilder builder) builder.Services.AddHostedService(); } - private IEnumerable BackOfficeExamineSearch(string query, int pageSize = 20, int pageIndex = 0) => + private IEnumerable BackOfficeExamineSearch(string query, int pageSize = 20, int pageIndex = 0, bool ignoreUserStartNodes = false) => BackOfficeExamineSearcher.Search(query, UmbracoEntityTypes.Document, - pageSize, pageIndex, out _, ignoreUserStartNodes: true); + pageSize, pageIndex, out _, ignoreUserStartNodes: ignoreUserStartNodes); private async Task SetupUserIdentity(string userId) { diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Examine/DeliveryApiContentIndexHelperTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Examine/DeliveryApiContentIndexHelperTests.cs new file mode 100644 index 000000000000..853914dd9114 --- /dev/null +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Examine/DeliveryApiContentIndexHelperTests.cs @@ -0,0 +1,120 @@ +using Microsoft.Extensions.Options; +using Moq; +using NUnit.Framework; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Infrastructure.Examine; +using Umbraco.Cms.Infrastructure.Persistence; +using Umbraco.Cms.Tests.Common.Builders; +using Umbraco.Cms.Tests.Common.Testing; +using Umbraco.Cms.Tests.Integration.Testing; + +namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Examine; + +[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)] +[TestFixture] +public class DeliveryApiContentIndexHelperTests : UmbracoIntegrationTestWithContent +{ + public override void CreateTestData() + { + base.CreateTestData(); + + // Save an extra, published content item of a different type to those created via the base class, + // that we'll use to test filtering out disallowed content types. + var template = TemplateBuilder.CreateTextPageTemplate("textPage2"); + FileService.SaveTemplate(template); + + var contentType = ContentTypeBuilder.CreateSimpleContentType("umbTextpage2", "Textpage2", defaultTemplateId: template.Id); + contentType.Key = Guid.NewGuid(); + ContentTypeService.Save(contentType); + + ContentType.AllowedContentTypes = + [ + new ContentTypeSort(ContentType.Id, 0), + new ContentTypeSort(contentType.Id, 1), + ]; + ContentTypeService.Save(ContentType); + + var subpage = ContentBuilder.CreateSimpleContent(contentType, "Alternate Text Page 4", Textpage.Id); + ContentService.Save(subpage); + + // And then add some more of the first type, so the one we'll filter out in tests isn't in the last page. + for (int i = 0; i < 5; i++) + { + subpage = ContentBuilder.CreateSimpleContent(ContentType, $"Text Page {5 + i}", Textpage.Id); + ContentService.Save(subpage); + } + } + + [Test] + public void Can_Enumerate_Descendants_For_Content_Index() + { + var sut = CreateDeliveryApiContentIndexHelper(); + + var expectedNumberOfContentItems = GetExpectedNumberOfContentItems(); + + var contentEnumerated = 0; + Action actionToPerform = content => + { + contentEnumerated += content.Length; + }; + + const int pageSize = 3; + sut.EnumerateApplicableDescendantsForContentIndex( + Cms.Core.Constants.System.Root, + actionToPerform, + pageSize); + + Assert.AreEqual(expectedNumberOfContentItems, contentEnumerated); + } + + [Test] + public void Can_Enumerate_Descendants_For_Content_Index_With_Disallowed_Content_Type() + { + var sut = CreateDeliveryApiContentIndexHelper(["umbTextPage2"]); + + var expectedNumberOfContentItems = GetExpectedNumberOfContentItems(); + + var contentEnumerated = 0; + Action actionToPerform = content => + { + contentEnumerated += content.Length; + }; + + const int pageSize = 3; + sut.EnumerateApplicableDescendantsForContentIndex( + Cms.Core.Constants.System.Root, + actionToPerform, + pageSize); + + Assert.AreEqual(expectedNumberOfContentItems - 1, contentEnumerated); + } + + private DeliveryApiContentIndexHelper CreateDeliveryApiContentIndexHelper(string[]? disallowedContentTypeAliases = null) + { + return new DeliveryApiContentIndexHelper( + ContentService, + GetRequiredService(), + GetDeliveryApiSettings(disallowedContentTypeAliases ?? [])); + } + + private IOptionsMonitor GetDeliveryApiSettings(string[] disallowedContentTypeAliases) + { + var deliveryApiSettings = new DeliveryApiSettings + { + DisallowedContentTypeAliases = disallowedContentTypeAliases, + }; + + var optionsMonitorMock = new Mock>(); + optionsMonitorMock.Setup(o => o.CurrentValue).Returns(deliveryApiSettings); + return optionsMonitorMock.Object; + } + + private int GetExpectedNumberOfContentItems() + { + var result = ContentService.GetAllPublished().Count(); + Assert.AreEqual(10, result); + return result; + } +} diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/DocumentRepositoryTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/DocumentRepositoryTest.cs index ef579ea3922e..a9faf5ec5464 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/DocumentRepositoryTest.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/DocumentRepositoryTest.cs @@ -120,7 +120,7 @@ private DocumentRepository CreateRepository(IScopeAccessor scopeAccessor, out Co new ContentTypeCommonRepository(scopeAccessor, templateRepository, appCaches, ShortStringHelper); var languageRepository = new LanguageRepository(scopeAccessor, appCaches, LoggerFactory.CreateLogger()); - contentTypeRepository = new ContentTypeRepository(scopeAccessor, appCaches, LoggerFactory.CreateLogger(), commonRepository, languageRepository, ShortStringHelper); + contentTypeRepository = new ContentTypeRepository(scopeAccessor, appCaches, LoggerFactory.CreateLogger(), commonRepository, languageRepository, ShortStringHelper, new Lazy(() => IdKeyMap)); var relationTypeRepository = new RelationTypeRepository(scopeAccessor, AppCaches.Disabled, LoggerFactory.CreateLogger()); var entityRepository = new EntityRepository(scopeAccessor, AppCaches.Disabled); var relationRepository = new RelationRepository(scopeAccessor, LoggerFactory.CreateLogger(), relationTypeRepository, entityRepository); diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/MediaRepositoryTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/MediaRepositoryTest.cs index 932be80a0176..08be5cdee7b8 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/MediaRepositoryTest.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/MediaRepositoryTest.cs @@ -55,7 +55,7 @@ private MediaRepository CreateRepository(IScopeProvider provider, out MediaTypeR new ContentTypeCommonRepository(scopeAccessor, TemplateRepository, appCaches, ShortStringHelper); var languageRepository = new LanguageRepository(scopeAccessor, appCaches, LoggerFactory.CreateLogger()); - mediaTypeRepository = new MediaTypeRepository(scopeAccessor, appCaches, LoggerFactory.CreateLogger(), commonRepository, languageRepository, ShortStringHelper); + mediaTypeRepository = new MediaTypeRepository(scopeAccessor, appCaches, LoggerFactory.CreateLogger(), commonRepository, languageRepository, ShortStringHelper, new Lazy(() => IdKeyMap)); var tagRepository = new TagRepository(scopeAccessor, appCaches, LoggerFactory.CreateLogger()); var relationTypeRepository = new RelationTypeRepository(scopeAccessor, AppCaches.Disabled, LoggerFactory.CreateLogger()); var entityRepository = new EntityRepository(scopeAccessor, AppCaches.Disabled); diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/MediaTypeRepositoryTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/MediaTypeRepositoryTest.cs index 70a7ca2dbc59..ad08e0923f64 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/MediaTypeRepositoryTest.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/MediaTypeRepositoryTest.cs @@ -9,6 +9,7 @@ using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Persistence; using Umbraco.Cms.Core.Persistence.Repositories; +using Umbraco.Cms.Core.Services; using Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement; using Umbraco.Cms.Infrastructure.Scoping; using Umbraco.Cms.Tests.Common.Builders; @@ -411,7 +412,7 @@ public void Can_Verify_PropertyTypes_On_File_MediaType() } private MediaTypeRepository CreateRepository(IScopeProvider provider) => - new((IScopeAccessor)provider, AppCaches.Disabled, LoggerFactory.CreateLogger(), CommonRepository, LanguageRepository, ShortStringHelper); + new((IScopeAccessor)provider, AppCaches.Disabled, LoggerFactory.CreateLogger(), CommonRepository, LanguageRepository, ShortStringHelper, new Lazy(() => IdKeyMap)); private EntityContainerRepository CreateContainerRepository(IScopeProvider provider) => new((IScopeAccessor)provider, AppCaches.Disabled, LoggerFactory.CreateLogger(), Constants.ObjectTypes.MediaTypeContainer); diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/MemberTypeRepositoryTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/MemberTypeRepositoryTest.cs index 27d0cce9859a..48798f9db00a 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/MemberTypeRepositoryTest.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/MemberTypeRepositoryTest.cs @@ -10,6 +10,7 @@ using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Persistence; using Umbraco.Cms.Core.Persistence.Repositories; +using Umbraco.Cms.Core.Services; using Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement; using Umbraco.Cms.Infrastructure.Scoping; using Umbraco.Cms.Tests.Common.Builders; @@ -26,7 +27,7 @@ private MemberTypeRepository CreateRepository(IScopeProvider provider) { var commonRepository = GetRequiredService(); var languageRepository = GetRequiredService(); - return new MemberTypeRepository((IScopeAccessor)provider, AppCaches.Disabled, Mock.Of>(), commonRepository, languageRepository, ShortStringHelper); + return new MemberTypeRepository((IScopeAccessor)provider, AppCaches.Disabled, Mock.Of>(), commonRepository, languageRepository, ShortStringHelper, new Lazy(() => IdKeyMap)); } [Test] diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/TagRepositoryTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/TagRepositoryTest.cs index 297bd5069900..7df55aa4b059 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/TagRepositoryTest.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/TagRepositoryTest.cs @@ -1,7 +1,6 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Linq; using Microsoft.Extensions.Logging; using NUnit.Framework; using Umbraco.Cms.Core.Cache; @@ -1047,6 +1046,98 @@ public void Can_Get_Tagged_Entities_For_Tag() } } + [Test] + public void Can_Create_Tag_Relations_With_Mixed_Casing_For_Tag() + { + var provider = ScopeProvider; + using (var scope = ScopeProvider.CreateScope()) + { + (IContentType contentType, IContent content1, IContent content2) = CreateContentForCreateTagTests(); + + var repository = CreateRepository(provider); + + // Note two tags are applied, but they differ only in case for the tag. + Tag[] tags1 = { new() { Text = "tag1", Group = "test" }, new() { Text = "Tag1", Group = "test" } }; + repository.Assign( + content1.Id, + contentType.PropertyTypes.First().Id, + tags1, + false); + + // Note the casing is different from the tag in tags1, but both should be considered equivalent. + Tag[] tags2 = { new() { Text = "TAG1", Group = "test" } }; + repository.Assign( + content2.Id, + contentType.PropertyTypes.First().Id, + tags2, + false); + + // Only one tag should have been saved. + var tagCount = scope.Database.ExecuteScalar( + "SELECT COUNT(*) FROM cmsTags WHERE [group] = 'test'"); + Assert.AreEqual(1, tagCount); + + // Both content items should be found as tagged by the tag, even though one was assigned with the tag differing in case. + Assert.AreEqual(2, repository.GetTaggedEntitiesByTag(TaggableObjectTypes.Content, "tag1").Count()); + } + } + + [Test] + public void Can_Create_Tag_Relations_With_Mixed_Casing_For_Group() + { + var provider = ScopeProvider; + using (var scope = ScopeProvider.CreateScope()) + { + (IContentType contentType, IContent content1, IContent content2) = CreateContentForCreateTagTests(); + + var repository = CreateRepository(provider); + + // Note two tags are applied, but they differ only in case for the group. + Tag[] tags1 = { new() { Text = "tag1", Group = "group1" }, new() { Text = "tag1", Group = "Group1" } }; + repository.Assign( + content1.Id, + contentType.PropertyTypes.First().Id, + tags1, + false); + + // Note the casing is different from the group in tags1, but both should be considered equivalent. + Tag[] tags2 = { new() { Text = "tag1", Group = "GROUP1" } }; + repository.Assign( + content2.Id, + contentType.PropertyTypes.First().Id, + tags2, + false); + + // Only one tag/group should have been saved. + var tagCount = scope.Database.ExecuteScalar( + "SELECT COUNT(*) FROM cmsTags WHERE [tag] = 'tag1'"); + Assert.AreEqual(1, tagCount); + + var groupCount = scope.Database.ExecuteScalar( + "SELECT COUNT(*) FROM cmsTags WHERE [group] = 'group1'"); + Assert.AreEqual(1, groupCount); + + // Both content items should be found as tagged by the tag, even though one was assigned with the group differing in case. + Assert.AreEqual(2, repository.GetTaggedEntitiesByTagGroup(TaggableObjectTypes.Content, "group1").Count()); + } + } + + private (IContentType ContentType, IContent Content1, IContent Content2) CreateContentForCreateTagTests() + { + var template = TemplateBuilder.CreateTextPageTemplate(); + FileService.SaveTemplate(template); + + var contentType = ContentTypeBuilder.CreateSimpleContentType("test", "Test", defaultTemplateId: template.Id); + ContentTypeRepository.Save(contentType); + + var content1 = ContentBuilder.CreateSimpleContent(contentType); + var content2 = ContentBuilder.CreateSimpleContent(contentType); + DocumentRepository.Save(content1); + DocumentRepository.Save(content2); + + return (contentType, content1, content2); + } + private TagRepository CreateRepository(IScopeProvider provider) => new((IScopeAccessor)provider, AppCaches.Disabled, LoggerFactory.CreateLogger()); } diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/TemplateRepositoryTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/TemplateRepositoryTest.cs index ce267555e4cb..b25c25f33ec9 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/TemplateRepositoryTest.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/TemplateRepositoryTest.cs @@ -265,7 +265,7 @@ public void Can_Perform_Delete_When_Assigned_To_Doc() var commonRepository = new ContentTypeCommonRepository(scopeAccessor, templateRepository, AppCaches, ShortStringHelper); var languageRepository = new LanguageRepository(scopeAccessor, AppCaches.Disabled, LoggerFactory.CreateLogger()); - var contentTypeRepository = new ContentTypeRepository(scopeAccessor, AppCaches.Disabled, LoggerFactory.CreateLogger(), commonRepository, languageRepository, ShortStringHelper); + var contentTypeRepository = new ContentTypeRepository(scopeAccessor, AppCaches.Disabled, LoggerFactory.CreateLogger(), commonRepository, languageRepository, ShortStringHelper, new Lazy(() => IdKeyMap)); var relationTypeRepository = new RelationTypeRepository(scopeAccessor, AppCaches.Disabled, LoggerFactory.CreateLogger()); var entityRepository = new EntityRepository(scopeAccessor, AppCaches.Disabled); var relationRepository = new RelationRepository(scopeAccessor, LoggerFactory.CreateLogger(), relationTypeRepository, entityRepository); diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Scoping/NestedScopeTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Scoping/NestedScopeTests.cs new file mode 100644 index 000000000000..4d0587c236e3 --- /dev/null +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Scoping/NestedScopeTests.cs @@ -0,0 +1,222 @@ +using Microsoft.Extensions.DependencyInjection; +using NUnit.Framework; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Scoping; +using Umbraco.Cms.Persistence.EFCore.Scoping; +using Umbraco.Cms.Tests.Common.Testing; +using Umbraco.Cms.Tests.Integration.Testing; +using Umbraco.Cms.Tests.Integration.Umbraco.Persistence.EFCore.DbContext; +using IScopeProvider = Umbraco.Cms.Infrastructure.Scoping.IScopeProvider; + +namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Scoping +{ + /// + /// These tests verify that the various types of scopes we have can be created and disposed within each other. + /// + /// + /// Scopes are: + /// - "Normal" - created by "/>. + /// - "Core" - created by "/>. + /// - "EFCore" - created by "/>. + /// + [TestFixture] + [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)] + internal sealed class NestedScopeTests : UmbracoIntegrationTest + { + private new IScopeProvider ScopeProvider => Services.GetRequiredService(); + + private ICoreScopeProvider CoreScopeProvider => Services.GetRequiredService(); + + private IEFCoreScopeProvider EfCoreScopeProvider => + Services.GetRequiredService>(); + + [Test] + public void CanNestScopes_Normal_Core_EfCore() + { + using (var ambientScope = ScopeProvider.CreateScope()) + { + ambientScope.WriteLock(Constants.Locks.ContentTree); + + using (var outerScope = CoreScopeProvider.CreateCoreScope()) + { + outerScope.WriteLock(Constants.Locks.ContentTree); + + using (var innerScope = EfCoreScopeProvider.CreateScope()) + { + innerScope.WriteLock(Constants.Locks.ContentTree); + + innerScope.Complete(); + outerScope.Complete(); + ambientScope.Complete(); + } + } + } + } + + [Test] + public void CanNestScopes_Normal_EfCore_Core() + { + using (var ambientScope = ScopeProvider.CreateScope()) + { + ambientScope.WriteLock(Constants.Locks.ContentTree); + + using (var outerScope = EfCoreScopeProvider.CreateScope()) + { + outerScope.WriteLock(Constants.Locks.ContentTree); + + using (var innerScope = CoreScopeProvider.CreateCoreScope()) + { + innerScope.WriteLock(Constants.Locks.ContentTree); + + innerScope.Complete(); + outerScope.Complete(); + ambientScope.Complete(); + } + } + } + } + + [Test] + public void CanNestScopes_Core_Normal_Efcore() + { + using (var ambientScope = CoreScopeProvider.CreateCoreScope()) + { + ambientScope.WriteLock(Constants.Locks.ContentTree); + + using (var outerScope = ScopeProvider.CreateScope()) + { + outerScope.WriteLock(Constants.Locks.ContentTree); + + using (var innerScope = EfCoreScopeProvider.CreateScope()) + { + innerScope.WriteLock(Constants.Locks.ContentTree); + + innerScope.Complete(); + outerScope.Complete(); + ambientScope.Complete(); + } + } + } + } + + [Test] + public void CanNestScopes_Core_EfCore_Normal() + { + using (var ambientScope = CoreScopeProvider.CreateCoreScope()) + { + ambientScope.WriteLock(Constants.Locks.ContentTree); + + using (var outerScope = EfCoreScopeProvider.CreateScope()) + { + outerScope.WriteLock(Constants.Locks.ContentTree); + + using (var innerScope = ScopeProvider.CreateScope()) + { + innerScope.WriteLock(Constants.Locks.ContentTree); + + innerScope.Complete(); + outerScope.Complete(); + ambientScope.Complete(); + } + } + } + } + + [Test] + public void CanNestScopes_EfCore_Normal_Core() + { + using (var ambientScope = EfCoreScopeProvider.CreateScope()) + { + ambientScope.WriteLock(Constants.Locks.ContentTree); + + using (var outerScope = ScopeProvider.CreateScope()) + { + outerScope.WriteLock(Constants.Locks.ContentTree); + + using (var innerScope = CoreScopeProvider.CreateCoreScope()) + { + innerScope.WriteLock(Constants.Locks.ContentTree); + + innerScope.Complete(); + outerScope.Complete(); + ambientScope.Complete(); + } + } + } + } + + [Test] + public void CanNestScopes_EfCore_Core_Normal() + { + using (var ambientScope = EfCoreScopeProvider.CreateScope()) + { + ambientScope.WriteLock(Constants.Locks.ContentTree); + + using (var outerScope = CoreScopeProvider.CreateCoreScope()) + { + outerScope.WriteLock(Constants.Locks.ContentTree); + + using (var innerScope = ScopeProvider.CreateScope()) + { + innerScope.WriteLock(Constants.Locks.ContentTree); + + innerScope.Complete(); + outerScope.Complete(); + ambientScope.Complete(); + } + } + } + } + + [Test] + public void CanNestScopes_Normal_Normal() + { + using (var ambientScope = ScopeProvider.CreateScope()) + { + ambientScope.WriteLock(Constants.Locks.ContentTree); + + using (var inner = ScopeProvider.CreateScope()) + { + inner.WriteLock(Constants.Locks.ContentTree); + + inner.Complete(); + ambientScope.Complete(); + } + } + } + + [Test] + public void CanNestScopes_Core_Core() + { + using (var ambientScope = CoreScopeProvider.CreateCoreScope()) + { + ambientScope.WriteLock(Constants.Locks.ContentTree); + + using (var inner = CoreScopeProvider.CreateCoreScope()) + { + inner.WriteLock(Constants.Locks.ContentTree); + + inner.Complete(); + ambientScope.Complete(); + } + } + } + + [Test] + public void CanNestScopes_EfCore_EfCore() + { + using (var ambientScope = EfCoreScopeProvider.CreateScope()) + { + ambientScope.WriteLock(Constants.Locks.ContentTree); + + using (var inner = EfCoreScopeProvider.CreateScope()) + { + inner.WriteLock(Constants.Locks.ContentTree); + + inner.Complete(); + ambientScope.Complete(); + } + } + } + } +} diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentTypeServiceTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentTypeServiceTests.cs index 0c435f537dab..3083fac61e5a 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentTypeServiceTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentTypeServiceTests.cs @@ -1967,6 +1967,65 @@ public void Variations_In_Compositions() .Variations); } + [Test] + public void Can_Create_Property_Type_Based_On_DataTypeKey() + { + // Arrange + var cts = ContentTypeService; + var dtdYesNo = DataTypeService.GetDataType(-49); + IContentType ctBase = new ContentType(ShortStringHelper, -1) + { + Name = "Base", + Alias = "Base", + Icon = "folder.gif", + Thumbnail = "folder.png" + }; + ctBase.AddPropertyType(new PropertyType(ShortStringHelper, "ShouldNotMatter", ValueStorageType.Nvarchar) + { + Name = "Hide From Navigation", + Alias = Constants.Conventions.Content.NaviHide, + DataTypeKey = dtdYesNo.Key + }); + cts.Save(ctBase); + + // Assert + ctBase = cts.Get(ctBase.Key); + Assert.That(ctBase, Is.Not.Null); + Assert.That(ctBase.HasIdentity, Is.True); + Assert.That(ctBase.PropertyTypes.Count(), Is.EqualTo(1)); + Assert.That(ctBase.PropertyTypes.First().DataTypeId, Is.EqualTo(dtdYesNo.Id)); + Assert.That(ctBase.PropertyTypes.First().PropertyEditorAlias, Is.EqualTo(dtdYesNo.EditorAlias)); + } + + [Test] + public void Can_Create_Property_Type_Based_On_PropertyEditorAlias() + { + // Arrange + var cts = ContentTypeService; + var dtdYesNo = DataTypeService.GetDataType(-49); + IContentType ctBase = new ContentType(ShortStringHelper, -1) + { + Name = "Base", + Alias = "Base", + Icon = "folder.gif", + Thumbnail = "folder.png" + }; + ctBase.AddPropertyType(new PropertyType(ShortStringHelper, "Umbraco.TrueFalse", ValueStorageType.Nvarchar) + { + Name = "Hide From Navigation", + Alias = Constants.Conventions.Content.NaviHide, + }); + cts.Save(ctBase); + + // Assert + ctBase = cts.Get(ctBase.Key); + Assert.That(ctBase, Is.Not.Null); + Assert.That(ctBase.HasIdentity, Is.True); + Assert.That(ctBase.PropertyTypes.Count(), Is.EqualTo(1)); + Assert.That(ctBase.PropertyTypes.First().DataTypeId, Is.EqualTo(dtdYesNo.Id)); + Assert.That(ctBase.PropertyTypes.First().PropertyEditorAlias, Is.EqualTo(dtdYesNo.EditorAlias)); + } + private ContentType CreateComponent() { var component = new ContentType(ShortStringHelper, -1) diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/UserServiceTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/UserServiceTests.cs index 8884622bf2b8..c3d5ebef5413 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/UserServiceTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/UserServiceTests.cs @@ -967,6 +967,73 @@ public void Can_Get_Assigned_StartNodes_For_User() } } + [Test] + public void Can_Get_Next_Users_In_Batches() + { + var users = UserBuilder.CreateMulipleUsers(10).ToArray(); + UserService.Save(users); + + var userBatch1 = UserService.GetNextUsers(Constants.Security.SuperUserId, 3); + var userBatch2 = UserService.GetNextUsers(1, 6); + var userBatch3 = UserService.GetNextUsers(4, 5); + var userBatch4 = UserService.GetNextUsers(9, 5); + var allUsers = UserService.GetNextUsers(Constants.Security.SuperUserId, int.MaxValue); + + Assert.AreEqual(3, userBatch1.Count()); + Assert.AreEqual(Constants.Security.SuperUserId, userBatch1.First().Id); + Assert.AreEqual(2, userBatch1.Last().Id); + + Assert.AreEqual(6, userBatch2.Count()); + Assert.AreEqual(1, userBatch2.First().Id); + Assert.AreEqual(6, userBatch2.Last().Id); + + Assert.AreEqual(5, userBatch3.Count()); + Assert.AreEqual(4, userBatch3.First().Id); + Assert.AreEqual(8, userBatch3.Last().Id); + + Assert.AreEqual(2, userBatch4.Count()); + Assert.AreEqual(9, userBatch4.First().Id); + Assert.AreEqual(10, userBatch4.Last().Id); + + Assert.AreEqual(11, allUsers.Count()); + } + + [Test] + public void Can_Get_Next_Approved_Users_In_Batches() + { + var users = UserBuilder.CreateMulipleUsers(10).ToArray(); + for (int i = 0; i < users.Length; i++) + { + users[i].IsApproved = !(i == 0 || i == 6); // Setup all users as approved except for a couple. + } + + UserService.Save(users); + + var userBatch1 = UserService.GetNextApprovedUsers(Constants.Security.SuperUserId, 3); + var userBatch2 = UserService.GetNextApprovedUsers(1, 6); + var userBatch3 = UserService.GetNextApprovedUsers(4, 5); + var userBatch4 = UserService.GetNextApprovedUsers(9, 5); + var allApprovedUsers = UserService.GetNextApprovedUsers(Constants.Security.SuperUserId, int.MaxValue); + + Assert.AreEqual(3, userBatch1.Count()); + Assert.AreEqual(Constants.Security.SuperUserId, userBatch1.First().Id); + Assert.AreEqual(3, userBatch1.Last().Id); + + Assert.AreEqual(6, userBatch2.Count()); + Assert.AreEqual(2, userBatch2.First().Id); + Assert.AreEqual(8, userBatch2.Last().Id); + + Assert.AreEqual(5, userBatch3.Count()); + Assert.AreEqual(4, userBatch3.First().Id); + Assert.AreEqual(9, userBatch3.Last().Id); + + Assert.AreEqual(2, userBatch4.Count()); + Assert.AreEqual(9, userBatch4.First().Id); + Assert.AreEqual(10, userBatch4.Last().Id); + + Assert.AreEqual(9, allApprovedUsers.Count()); + } + private Content[] BuildContentItems(int numberToCreate) { var template = TemplateBuilder.CreateTextPageTemplate(); diff --git a/tests/Umbraco.Tests.UnitTests/TestHelpers/PublishedSnapshotServiceTestBase.cs b/tests/Umbraco.Tests.UnitTests/TestHelpers/PublishedSnapshotServiceTestBase.cs index 46f950ee603f..dded4a1bf54f 100644 --- a/tests/Umbraco.Tests.UnitTests/TestHelpers/PublishedSnapshotServiceTestBase.cs +++ b/tests/Umbraco.Tests.UnitTests/TestHelpers/PublishedSnapshotServiceTestBase.cs @@ -7,6 +7,7 @@ using Moq; using NUnit.Framework; using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Hosting; @@ -22,6 +23,7 @@ using Umbraco.Cms.Core.Strings; using Umbraco.Cms.Core.Sync; using Umbraco.Cms.Core.Web; +using Umbraco.Cms.Infrastructure.HostedServices; using Umbraco.Cms.Infrastructure.PublishedCache; using Umbraco.Cms.Infrastructure.PublishedCache.DataSource; using Umbraco.Cms.Infrastructure.Serialization; @@ -280,7 +282,9 @@ protected void InitializedCache( PublishedModelFactory, TestHelper.GetHostingEnvironment(), Options.Create(nuCacheSettings), - new ContentDataSerializer(new DictionaryOfPropertyDataSerializer())); + new ContentDataSerializer(new DictionaryOfPropertyDataSerializer()), + Mock.Of(), + AppCaches.NoCache); // invariant is the current default VariationContextAccessor.VariationContext = new VariationContext(); diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Cms.Api.Delivery/Services/RequestPreviewServiceTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Cms.Api.Delivery/Services/RequestPreviewServiceTests.cs new file mode 100644 index 000000000000..6f5c5e0790a2 --- /dev/null +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Cms.Api.Delivery/Services/RequestPreviewServiceTests.cs @@ -0,0 +1,31 @@ +using Microsoft.AspNetCore.Http; +using Moq; +using NUnit.Framework; +using Umbraco.Cms.Api.Delivery.Services; + +namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Cms.Api.Delivery.Services; + +[TestFixture] +public class RequestPreviewServiceTests +{ + [TestCase(null, false)] + [TestCase("", false)] + [TestCase("false", false)] + [TestCase("true", true)] + [TestCase("True", true)] + public void IsPreview_Returns_Expected_Result(string? headerValue, bool expected) + { + var httpContext = new DefaultHttpContext(); + httpContext.Request.Headers["Preview"] = headerValue; + + var httpContextAccessorMock = new Mock(); + httpContextAccessorMock + .Setup(x => x.HttpContext) + .Returns(httpContext); + var sut = new RequestPreviewService(httpContextAccessorMock.Object); + + var result = sut.IsPreview(); + + Assert.AreEqual(expected, result); + } +} diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Cache/DistributedCacheExtensionsTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Cache/DistributedCacheExtensionsTests.cs new file mode 100644 index 000000000000..288e6dfc4027 --- /dev/null +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Cache/DistributedCacheExtensionsTests.cs @@ -0,0 +1,64 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using NUnit.Framework; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Tests.Common.Builders; +using Umbraco.Cms.Tests.Common.Builders.Extensions; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Cache; + +[TestFixture] +public class DistributedCacheExtensionsTests +{ + [Test] + public void Member_GetPayloads_CorrectlyCreatesPayloads() + { + var members = new List() + { + CreateMember(1, "Fred", "fred", "fred@test.com"), + CreateMember(1, "Fred", "fred", "fred@test.com"), + CreateMember(2, "Sally", "sally", "sally@test.com"), + CreateMember(3, "Jane", "jane", "jane@test.com", "janeold"), + }; + + var payloads = DistributedCacheExtensions.GetPayloads(members, false); + Assert.AreEqual(3, payloads.Count()); + + var payloadForFred = payloads.First(); + Assert.AreEqual("fred", payloadForFred.Username); + Assert.AreEqual(1, payloadForFred.Id); + Assert.IsNull(payloadForFred.PreviousUsername); + + var payloadForSally = payloads.Skip(1).First(); + Assert.AreEqual("sally", payloadForSally.Username); + Assert.AreEqual(2, payloadForSally.Id); + Assert.IsNull(payloadForSally.PreviousUsername); + + var payloadForJane = payloads.Skip(2).First(); + Assert.AreEqual("jane", payloadForJane.Username); + Assert.AreEqual(3, payloadForJane.Id); + Assert.AreEqual("janeold", payloadForJane.PreviousUsername); + } + + private static IMember CreateMember(int id, string name, string username, string email, string? previousUserName = null) + { + var memberBuilder = new MemberBuilder() + .AddMemberType() + .Done() + .WithId(id) + .WithName(name) + .WithLogin(username, "password") + .WithEmail(email); + + if (previousUserName != null) + { + memberBuilder.AddAdditionalData() + .WithKeyValue(global::Umbraco.Cms.Core.Constants.Entities.AdditionalDataKeys.MemberPreviousUserName, previousUserName) + .Done(); + } + + return memberBuilder.Build(); + } +} diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/DeliveryApi/ApiContentPathResolverTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/DeliveryApi/ApiContentPathResolverTests.cs new file mode 100644 index 000000000000..23b6d42415aa --- /dev/null +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/DeliveryApi/ApiContentPathResolverTests.cs @@ -0,0 +1,52 @@ +using Moq; +using NUnit.Framework; +using Umbraco.Cms.Core.DeliveryApi; +using Umbraco.Cms.Core.Models.PublishedContent; + +namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.DeliveryApi; + +[TestFixture] +public class ApiContentPathResolverTests +{ + private const string TestPath = "/test/page"; + + [TestCase(TestPath, true)] + [TestCase("/", true)] + [TestCase("file.txt", false)] + [TestCase("test/file.txt", false)] + [TestCase("test/test2/file.txt", false)] + [TestCase("/file.txt", false)] + [TestCase("/test/file.txt", false)] + [TestCase("/test/test2/file.txt", false)] + public void Can_Verify_Resolveable_Paths(string path, bool expected) + { + var resolver = CreateResolver(); + var result = resolver.IsResolvablePath(path); + Assert.AreEqual(expected, result); + } + + [TestCase(TestPath)] + [TestCase("/")] + public void Resolves_Content_For_Path(string path) + { + var resolver = CreateResolver(); + var result = resolver.ResolveContentPath(path); + Assert.IsNotNull(result); + } + + private static ApiContentPathResolver CreateResolver() + { + var mockRequestRoutingService = new Mock(); + mockRequestRoutingService + .Setup(x => x.GetContentRoute(It.IsAny())) + .Returns((string path) => path); + var mockApiPublishedContentCache = new Mock(); + mockApiPublishedContentCache + .Setup(x => x.GetByRoute(TestPath)) + .Returns(new Mock().Object); + mockApiPublishedContentCache + .Setup(x => x.GetByRoute("/")) + .Returns(new Mock().Object); + return new ApiContentPathResolver(mockRequestRoutingService.Object, mockApiPublishedContentCache.Object); + } +} diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/DeliveryApi/RichTextParserTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/DeliveryApi/RichTextParserTests.cs index 2a8da7acb07d..d78fce702de1 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/DeliveryApi/RichTextParserTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/DeliveryApi/RichTextParserTests.cs @@ -1,4 +1,4 @@ -using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging; using Moq; using NUnit.Framework; using Umbraco.Cms.Core; @@ -10,6 +10,7 @@ using Umbraco.Cms.Core.PropertyEditors.ValueConverters; using Umbraco.Cms.Core.PublishedCache; using Umbraco.Cms.Infrastructure.DeliveryApi; +using Umbraco.Extensions; namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.DeliveryApi; @@ -127,12 +128,16 @@ public void ParseElement_DataAttributesDoNotOverwriteExistingAttributes() Assert.AreEqual("the original something", span.Attributes.First().Value); } - [Test] - public void ParseElement_CanParseContentLink() + + [TestCase(null)] + [TestCase("")] + [TestCase("#some-anchor")] + [TestCase("?something=true")] + public void ParseElement_CanParseContentLink(string? postfix) { var parser = CreateRichTextElementParser(); - var element = parser.Parse($"

    ") as RichTextRootElement; + var element = parser.Parse($"

    ") as RichTextRootElement; Assert.IsNotNull(element); var link = element.Elements.OfType().Single().Elements.Single() as RichTextGenericElement; Assert.IsNotNull(link); @@ -142,6 +147,7 @@ public void ParseElement_CanParseContentLink() var route = link.Attributes.First().Value as IApiContentRoute; Assert.IsNotNull(route); Assert.AreEqual("/some-content-path", route.Path); + Assert.AreEqual(postfix.NullOrWhiteSpaceAsNull(), route.QueryString); Assert.AreEqual(_contentRootKey, route.StartItem.Id); Assert.AreEqual("the-root-path", route.StartItem.Path); } @@ -176,6 +182,22 @@ public void ParseElement_CanHandleNonLocalLink() Assert.AreEqual("https://some.where/else/", link.Attributes.First().Value); } + [TestCase("#some-anchor")] + [TestCase("?something=true")] + public void ParseElement_CanHandleNonLocalLink_WithPostfix(string postfix) + { + var parser = CreateRichTextElementParser(); + + var element = parser.Parse($"

    ") as RichTextRootElement; + Assert.IsNotNull(element); + var link = element.Elements.OfType().Single().Elements.Single() as RichTextGenericElement; + Assert.IsNotNull(link); + Assert.AreEqual("a", link.Tag); + Assert.AreEqual(1, link.Attributes.Count); + Assert.AreEqual("href", link.Attributes.First().Key); + Assert.AreEqual($"https://some.where/else/{postfix}", link.Attributes.First().Value); + } + [Test] public void ParseElement_LinkTextIsWrappedInTextElement() { @@ -357,16 +379,71 @@ public void ParseElement_CanHandleMixedInlineAndBlockLevelBlocks() Assert.IsEmpty(blockLevelBlock.Elements); } + private const string TestParagraph = "What follows from here is just a bunch of text."; + [Test] public void ParseElement_CanHandleWhitespaceAroundInlineElemements() { var parser = CreateRichTextElementParser(); - var element = parser.Parse("

    What follows from here is just a bunch of text.

    ") as RichTextRootElement; + var element = parser.Parse($"

    {TestParagraph}

    ") as RichTextRootElement; Assert.IsNotNull(element); var paragraphElement = element.Elements.Single() as RichTextGenericElement; Assert.IsNotNull(paragraphElement); + AssertTestParagraph(paragraphElement); + } + + [TestCase(1, "\n")] + [TestCase(2, "\n")] + [TestCase(1, "\r")] + [TestCase(2, "\r")] + [TestCase(1, "\r\n")] + [TestCase(2, "\r\n")] + public void ParseElement_RemovesNewLinesAroundHtmlStructuralElements(int numberOfNewLineCharacters, string newlineCharacter) + { + var parser = CreateRichTextElementParser(); + + var newLineSeparator = string.Concat(Enumerable.Repeat(newlineCharacter, numberOfNewLineCharacters)); + var element = parser.Parse($"{newLineSeparator}{newLineSeparator}{newLineSeparator}{newLineSeparator}
    {TestParagraph}
    ") as RichTextRootElement; + Assert.IsNotNull(element); + var tableElement = element.Elements.Single() as RichTextGenericElement; + Assert.IsNotNull(tableElement); + + var rowElement = tableElement.Elements.Single() as RichTextGenericElement; + Assert.IsNotNull(rowElement); + + var cellElement = rowElement.Elements.Single() as RichTextGenericElement; + Assert.IsNotNull(cellElement); + + AssertTestParagraph(cellElement); + } + + [TestCase(1, "\n")] + [TestCase(2, "\n")] + [TestCase(1, "\r")] + [TestCase(2, "\r")] + [TestCase(1, "\r\n")] + [TestCase(2, "\r\n")] + public void ParseElement_RemovesNewLinesAroundHtmlContentElements(int numberOfNewLineCharacters, string newlineCharacter) + { + var parser = CreateRichTextElementParser(); + + var newLineSeparator = string.Concat(Enumerable.Repeat(newlineCharacter, numberOfNewLineCharacters)); + var element = parser.Parse($"

    {TestParagraph}

    {newLineSeparator}

    {newLineSeparator}

     

    {newLineSeparator}

    {TestParagraph}

    ") as RichTextRootElement; + Assert.IsNotNull(element); + var divElement = element.Elements.Single() as RichTextGenericElement; + Assert.IsNotNull(divElement); + + var paragraphELements = divElement.Elements; + Assert.AreEqual(4, paragraphELements.Count()); + + AssertTestParagraph(paragraphELements.First() as RichTextGenericElement); + AssertTestParagraph(paragraphELements.Last() as RichTextGenericElement); + } + + private static void AssertTestParagraph(RichTextGenericElement paragraphElement) + { var childElements = paragraphElement.Elements.ToArray(); Assert.AreEqual(7, childElements.Length); @@ -410,6 +487,18 @@ public void ParseMarkup_CanParseContentLink() Assert.IsTrue(result.Contains($"data-start-item-id=\"{_contentRootKey:D}\"")); } + [TestCase("#some-anchor")] + [TestCase("?something=true")] + public void ParseMarkup_CanParseContentLink_WithPostfix(string postfix) + { + var parser = CreateRichTextMarkupParser(); + + var result = parser.Parse($"

    "); + Assert.IsTrue(result.Contains($"href=\"/some-content-path{postfix}\"")); + Assert.IsTrue(result.Contains("data-start-item-path=\"the-root-path\"")); + Assert.IsTrue(result.Contains($"data-start-item-id=\"{_contentRootKey:D}\"")); + } + [Test] public void ParseMarkup_CanParseMediaLink() { @@ -430,6 +519,8 @@ public void ParseMarkup_InvalidLocalLinkYieldsEmptyLink(string href) } [TestCase("

    ")] + [TestCase("

    ")] + [TestCase("

    ")] [TestCase("

    ")] public void ParseMarkup_CanHandleNonLocalReferences(string html) { diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Extensions/IntExtensionsTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Extensions/IntExtensionsTests.cs new file mode 100644 index 000000000000..46cab0df546a --- /dev/null +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Extensions/IntExtensionsTests.cs @@ -0,0 +1,41 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using NUnit.Framework; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Extensions; + +[TestFixture] +public class IntExtensionsTests +{ + [TestCase(20, "00000014-0000-0000-0000-000000000000")] + [TestCase(106, "0000006a-0000-0000-0000-000000000000")] + [TestCase(999999, "000f423f-0000-0000-0000-000000000000")] + [TestCase(555555555, "211d1ae3-0000-0000-0000-000000000000")] + public void ToGuid_Creates_Expected_Guid(int input, string expected) + { + var result = input.ToGuid(); + Assert.AreEqual(expected, result.ToString()); + } + + [TestCase("00000014-0000-0000-0000-000000000000", 20)] + [TestCase("0000006a-0000-0000-0000-000000000000", 106)] + [TestCase("000f423f-0000-0000-0000-000000000000", 999999)] + [TestCase("211d1ae3-0000-0000-0000-000000000000", 555555555)] + [TestCase("0d93047e-558d-4311-8a9d-b89e6fca0337", null)] + public void TryParseFromGuid_Parses_Expected_Integer(string input, int? expected) + { + var result = IntExtensions.TryParseFromGuid(Guid.Parse(input), out int? intValue); + if (expected is null) + { + Assert.IsFalse(result); + Assert.IsFalse(intValue.HasValue); + } + else + { + Assert.IsTrue(result); + Assert.AreEqual(expected, intValue.Value); + } + } +} diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/RichTextPropertyIndexValueFactoryTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/RichTextPropertyIndexValueFactoryTests.cs new file mode 100644 index 000000000000..40b479b88963 --- /dev/null +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/RichTextPropertyIndexValueFactoryTests.cs @@ -0,0 +1,82 @@ +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Moq; +using NUnit.Framework; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Core.Serialization; +using Umbraco.Cms.Core.Services; + +namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.PropertyEditors; + +/// +/// Tests for to ensure it correctly creates index values from rich text properties. +/// +public class RichTextPropertyIndexValueFactoryTests +{ + /// + /// Tests that the factory can create index values from a rich text property with valid content + /// + /// + /// + [Test] + [TestCase("

    Sample text

    ", "Sample text")] + [TestCase("

    John Smith
    Company ABC
    London

    ", "John Smith Company ABC London")] + [TestCase("

    John SmithCompany ABCLondon

    ", "John SmithCompany ABCLondon")] + [TestCase("

    John Smith
    Company ABCLondon

    ", "John Smith Company ABCLondon")] + [TestCase("

    Another sample text with bold content

    ", "Another sample text with bold content")] + [TestCase("

    Text with link

    ", "Text with link")] + [TestCase("

    Text with \"image\"

    ", "Text with")] + [TestCase("

    Text with styled text

    ", "Text with styled text")] + [TestCase("

    Text with emphasized content

    ", "Text with emphasized content")] + [TestCase("

    Text with underlined content

    ", "Text with underlined content")] + [TestCase("

    Text with inline code

    ", "Text with inline code")] + [TestCase("

    Text with

    code block

    ", "Text with code block")] + [TestCase("

    Text with

    quoted text

    ", "Text with quoted text")] + [TestCase("

    Text with

    • list item 1
    • list item 2

    ", + "Text with list item 1list item 2")] + [TestCase("

    Text with

    1. ordered item 1
    2. ordered item 2

    ", + "Text with ordered item 1ordered item 2")] + [TestCase("

    Text with

    div content

    ", "Text with div content")] + [TestCase("

    Text with span content

    ", "Text with span content")] + [TestCase("

    Text with bold and italic content

    ", + "Text with bold and italic content")] + [TestCase("

    Text with external link

    ", + "Text with external link")] + [TestCase("

    John Smith
    Company ABC
    London

    ", "John Smith Company ABC London")] + [TestCase("

    John Smith
    Company ABC
    London

    ", "John Smith Company ABC London")] + public void Can_Create_Index_Values_From_RichText_Property(string testContent, string expected) + { + var propertyEditorCollection = new PropertyEditorCollection(new DataEditorCollection(() => null)); + var jsonSerializer = Mock.Of(); + var indexingSettings = Mock.Of>(); + Mock.Get(indexingSettings).Setup(x => x.CurrentValue).Returns(new IndexingSettings { }); + var contentTypeService = Mock.Of(); + var logger = Mock.Of>(); + string alias = "richText"; + + var factory = new RichTextPropertyIndexValueFactory( + propertyEditorCollection, + jsonSerializer, + indexingSettings, + contentTypeService, + logger); + + // create a mock property with the rich text value + var property = Mock.Of(p => p.Alias == alias + && (string)p.GetValue(It.IsAny(), It.IsAny(), + It.IsAny()) == testContent); + + // get the index value for the property + var indexValue = factory + .GetIndexValues(property, null, null, true, [], new Dictionary()) + .FirstOrDefault(kvp => kvp.Key == alias); + Assert.IsNotNull(indexValue); + + // assert that index the value is created correctly (it might contain a trailing whitespace, but that's OK) + var expectedIndexValue = indexValue.Value.SingleOrDefault() as string; + Assert.IsNotNull(expectedIndexValue); + Assert.AreEqual(expected, expectedIndexValue.TrimEnd()); + } +} diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Security/MemberUserStoreTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Security/MemberUserStoreTests.cs index c31a6a258edf..f3c053ac9846 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Security/MemberUserStoreTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Security/MemberUserStoreTests.cs @@ -204,6 +204,64 @@ public async Task GivenIUpdateAUser_ThenIShouldGetASuccessResultAsync() _mockMemberService.Verify(x => x.ReplaceRoles(new[] { 123 }, new[] { "role1", "role2" })); } + [Test] + public async Task GivenIUpdateAUsersLoginPropertiesOnly_ThenIShouldGetASuccessResultAsync() + { + // arrange + var sut = CreateSut(); + var fakeUser = new MemberIdentityUser + { + Id = "123", + Name = "a", + Email = "a@b.com", + UserName = "c", + Comments = "e", + LastLoginDateUtc = DateTime.UtcNow, + SecurityStamp = "abc", + }; + + IMemberType fakeMemberType = new MemberType(new MockShortStringHelper(), 77); + var mockMember = Mock.Of(m => + m.Id == 123 && + m.Name == "a" && + m.Email == "a@b.com" && + m.Username == "c" && + m.Comments == "e" && + m.ContentTypeAlias == fakeMemberType.Alias && + m.HasIdentity == true && + m.EmailConfirmedDate == DateTime.MinValue && + m.FailedPasswordAttempts == 0 && + m.LastLockoutDate == DateTime.MinValue && + m.IsApproved == false && + m.RawPasswordValue == "xyz" && + m.SecurityStamp == "xyz"); + + _mockMemberService.Setup(x => x.UpdateLoginPropertiesAsync(mockMember)); + _mockMemberService.Setup(x => x.GetById(123)).Returns(mockMember); + + // act + var identityResult = await sut.UpdateAsync(fakeUser, CancellationToken.None); + + // assert + Assert.IsTrue(identityResult.Succeeded); + Assert.IsTrue(!identityResult.Errors.Any()); + + Assert.AreEqual(fakeUser.Name, mockMember.Name); + Assert.AreEqual(fakeUser.Email, mockMember.Email); + Assert.AreEqual(fakeUser.UserName, mockMember.Username); + Assert.AreEqual(fakeUser.Comments, mockMember.Comments); + Assert.IsFalse(fakeUser.LastPasswordChangeDateUtc.HasValue); + Assert.AreEqual(fakeUser.LastLoginDateUtc.Value.ToLocalTime(), mockMember.LastLoginDate); + Assert.AreEqual(fakeUser.AccessFailedCount, mockMember.FailedPasswordAttempts); + Assert.AreEqual(fakeUser.IsLockedOut, mockMember.IsLockedOut); + Assert.AreEqual(fakeUser.IsApproved, mockMember.IsApproved); + Assert.AreEqual(fakeUser.SecurityStamp, mockMember.SecurityStamp); + + _mockMemberService.Verify(x => x.Save(mockMember), Times.Never); + _mockMemberService.Verify(x => x.UpdateLoginPropertiesAsync(mockMember)); + _mockMemberService.Verify(x => x.GetById(123)); + } + [Test] public async Task GivenIDeleteUser_AndTheUserIsNotPresent_ThenIShouldGetAFailedResultAsync() { diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Controllers/PreviewControllerTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Controllers/PreviewControllerTests.cs new file mode 100644 index 000000000000..9e8c151de167 --- /dev/null +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Controllers/PreviewControllerTests.cs @@ -0,0 +1,34 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Globalization; +using NUnit.Framework; +using Umbraco.Cms.Web.BackOffice.Controllers; + +namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.BackOffice.Controllers; + +[TestFixture] +public class PreviewControllerTests +{ + [TestCase("en-US", true)] // A framework culture. + [TestCase("en-JP", true)] // A valid culture string, but not one that's in the framework. + [TestCase("a!", false)] // Not a valid culture string. + [TestCase("", false)] + public void ValidateProvidedCulture_Validates_Culture(string culture, bool expectValid) + { + var result = PreviewController.ValidateProvidedCulture(culture); + Assert.AreEqual(expectValid, result); + } + + [Test] + public void ValidateProvidedCulture_Validates_Culture_For_All_Framework_Cultures() + { + var cultures = CultureInfo.GetCultures(CultureTypes.AllCultures); + foreach (var culture in cultures) + { + Assert.IsTrue(PreviewController.ValidateProvidedCulture(culture.Name), $"{culture.Name} is not considered a valid culture."); + Assert.IsTrue(PreviewController.ValidateProvidedCulture(culture.Name.ToUpperInvariant()), $"{culture.Name.ToUpperInvariant()} is not considered a valid culture."); + Assert.IsTrue(PreviewController.ValidateProvidedCulture(culture.Name.ToLowerInvariant()), $"{culture.Name.ToLowerInvariant()} is not considered a valid culture."); + } + } +} diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Cache/PartialViewCacheInvalidators/MemberPartialViewCacheInvalidatorTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Cache/PartialViewCacheInvalidators/MemberPartialViewCacheInvalidatorTests.cs new file mode 100644 index 000000000000..3e67e27d4fca --- /dev/null +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Cache/PartialViewCacheInvalidators/MemberPartialViewCacheInvalidatorTests.cs @@ -0,0 +1,76 @@ +using System.Text.RegularExpressions; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.AspNetCore.Mvc.ViewFeatures; +using Moq; +using NUnit.Framework; +using Umbraco.Cms.Core.Cache; +using Umbraco.Cms.Core.Security; +using Umbraco.Cms.Core.Web; +using Umbraco.Cms.Web.Website.Cache.PartialViewCacheInvalidators; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.Website.Routing; + +[TestFixture] +public class MemberPartialViewCacheInvalidatorTests +{ + [Test] + public void ClearPartialViewCacheItems_Clears_ExpectedItems() + { + var runTimeCacheMock = new Mock(); + runTimeCacheMock + .Setup(x => x.ClearByRegex(It.IsAny())) + .Verifiable(); + var appCaches = new AppCaches( + runTimeCacheMock.Object, + NoAppCache.Instance, + new IsolatedCaches(type => new ObjectCacheAppCache())); + var memberPartialViewCacheInvalidator = new MemberPartialViewCacheInvalidator(appCaches); + + var memberIds = new[] { 1, 2, 3 }; + + memberPartialViewCacheInvalidator.ClearPartialViewCacheItems(memberIds); + + foreach (var memberId in memberIds) + { + var regex = $"Umbraco.Web.PartialViewCacheKey.*-m{memberId}-*"; + runTimeCacheMock + .Verify(x => x.ClearByRegex(It.Is(x => x == regex)), Times.Once); + } + } + + [Test] + public async Task ClearPartialViewCacheItems_Regex_Matches_CachedKeys() + { + const int MemberId = 1234; + + var memberManagerMock = new Mock(); + memberManagerMock + .Setup(x => x.GetCurrentMemberAsync()) + .ReturnsAsync(new MemberIdentityUser { Id = MemberId.ToString() }); + + var cacheKey = await HtmlHelperRenderExtensions.GenerateCacheKeyForCachedPartialViewAsync( + "TestPartial.cshtml", + true, + Mock.Of(), + true, + memberManagerMock.Object, + new TestViewModel(), + new ViewDataDictionary(new EmptyModelMetadataProvider(), new ModelStateDictionary()), + null); + cacheKey = CoreCacheHelperExtensions.PartialViewCacheKey + cacheKey; + Assert.AreEqual("Umbraco.Web.PartialViewCacheKeyTestPartial.cshtml-en-US-0-m1234-", cacheKey); + + var regexForMember = $"Umbraco.Web.PartialViewCacheKey.*-m{MemberId}-*"; + var regexMatch = Regex.IsMatch(cacheKey, regexForMember); + Assert.IsTrue(regexMatch); + + var regexForAnotherMember = $"Umbraco.Web.PartialViewCacheKey.*-m{4321}-*"; + regexMatch = Regex.IsMatch(cacheKey, regexForAnotherMember); + Assert.IsFalse(regexMatch); + } + + private class TestViewModel + { + } +} diff --git a/version.json b/version.json index 94daac6c7ac6..c06015a0824a 100644 --- a/version.json +++ b/version.json @@ -1,6 +1,6 @@ { "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/main/src/NerdBank.GitVersioning/version.schema.json", - "version": "13.8.0-rc", + "version": "13.11.0-rc", "assemblyVersion": { "precision": "build" },