From 779242f289c02d87bd7ebd4cb2dbf6e49806f4fa Mon Sep 17 00:00:00 2001 From: Arkadiusz biel Date: Mon, 3 Jun 2024 13:20:44 +0100 Subject: [PATCH 01/84] chore: make indexing more resiliant --- .../Configuration/Models/IndexingSettings.cs | 4 ++ .../UmbracoBuilder.Examine.cs | 2 +- .../Examine/ContentIndexPopulator.cs | 45 +++++++++++++++++-- .../Examine/ExamineIndexRebuilder.cs | 27 ++++++++++- .../Examine/IIndexRebuildStatusManager.cs | 7 +++ .../Examine/IndexRebuildStatusManager.cs | 28 ++++++++++++ .../ExamineManagementController.cs | 30 ++++++------- src/Umbraco.Web.UI.New.Client | 1 + 8 files changed, 122 insertions(+), 22 deletions(-) create mode 100644 src/Umbraco.Infrastructure/Examine/IIndexRebuildStatusManager.cs create mode 100644 src/Umbraco.Infrastructure/Examine/IndexRebuildStatusManager.cs create mode 160000 src/Umbraco.Web.UI.New.Client 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.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..19bdac7dbcfd 100644 --- a/src/Umbraco.Infrastructure/Examine/ContentIndexPopulator.cs +++ b/src/Umbraco.Infrastructure/Examine/ContentIndexPopulator.cs @@ -1,5 +1,9 @@ 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 +18,7 @@ public class ContentIndexPopulator : IndexPopulator { private readonly IContentService _contentService; private readonly IValueSetBuilder _contentValueSetBuilder; + private IndexingSettings _indexingSettings; private readonly ILogger _logger; private readonly int? _parentId; @@ -28,6 +33,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 +46,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 +62,37 @@ 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) + { + _contentService = contentService ?? throw new ArgumentNullException(nameof(contentService)); + _umbracoDatabaseFactory = umbracoDatabaseFactory ?? throw new ArgumentNullException(nameof(umbracoDatabaseFactory)); + _contentValueSetBuilder = contentValueSetBuilder ?? throw new ArgumentNullException(nameof(contentValueSetBuilder)); + _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 +112,7 @@ protected override void PopulateIndexes(IReadOnlyList indexes) return; } - const int pageSize = 10000; + var pageIndex = 0; var contentParentId = -1; @@ -86,11 +123,11 @@ 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); } } diff --git a/src/Umbraco.Infrastructure/Examine/ExamineIndexRebuilder.cs b/src/Umbraco.Infrastructure/Examine/ExamineIndexRebuilder.cs index 5fe98dca0bb3..8169c86d0df0 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.SetRebuildingIndexStatus([indexName], true); index.CreateIndex(); // clear the index foreach (IIndexPopulator populator in _populators) { @@ -158,6 +173,7 @@ private void RebuildIndex(string indexName, TimeSpan delay, CancellationToken ca populator.Populate(index); } + _indexRebuildStatusManager.SetRebuildingIndexStatus([indexName], false); } } finally @@ -175,6 +191,7 @@ private void RebuildIndexes(bool onlyEmptyIndexes, TimeSpan delay, CancellationT { Thread.Sleep(delay); } + IEnumerable? rebuildedIndex = null; try { @@ -190,7 +207,8 @@ private void RebuildIndexes(bool onlyEmptyIndexes, TimeSpan delay, CancellationT ? _examineManager.Indexes.Where(x => !x.IndexExists() || (x is IIndexStats stats && stats.GetDocumentCount() == 0)) : _examineManager.Indexes).ToArray(); - + rebuildedIndex = indexes.Select(x => x.Name); + _indexRebuildStatusManager.SetRebuildingIndexStatus(rebuildedIndex, true); if (indexes.Length == 0) { return; @@ -218,10 +236,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..10c70563d0ef --- /dev/null +++ b/src/Umbraco.Infrastructure/Examine/IIndexRebuildStatusManager.cs @@ -0,0 +1,7 @@ +namespace Umbraco.Cms.Infrastructure.Examine; + +public interface IIndexRebuildStatusManager +{ + void SetRebuildingIndexStatus(IEnumerable indexes, bool b); + bool GetRebuildingIndexStatus(string index); +} diff --git a/src/Umbraco.Infrastructure/Examine/IndexRebuildStatusManager.cs b/src/Umbraco.Infrastructure/Examine/IndexRebuildStatusManager.cs new file mode 100644 index 000000000000..2218b5f2afce --- /dev/null +++ b/src/Umbraco.Infrastructure/Examine/IndexRebuildStatusManager.cs @@ -0,0 +1,28 @@ +using Examine; + +namespace Umbraco.Cms.Infrastructure.Examine; + +/// +/// +/// +public class IndexRebuildStatusManager : IIndexRebuildStatusManager +{ + IDictionary _rebuildingStatus = new Dictionary(); + public IndexRebuildStatusManager(IExamineManager examineManager) + { + foreach (var index in examineManager.Indexes) + { + _rebuildingStatus.Add(index.Name, false); + } + } + + public void SetRebuildingIndexStatus(IEnumerable indexes, bool isRebuilding) + { + foreach (var index in indexes) + { + _rebuildingStatus[index] = isRebuilding; + } + } + + public bool GetRebuildingIndexStatus(string index) => _rebuildingStatus.TryGetValue(index, out var isRebuilding) && isRebuilding; +} diff --git a/src/Umbraco.Web.BackOffice/Controllers/ExamineManagementController.cs b/src/Umbraco.Web.BackOffice/Controllers/ExamineManagementController.cs index 21cb7fdcd942..e8c79c63aa9d 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,11 +140,8 @@ public ActionResult GetSearchResults(string searcherName, string? return validate; } - var cacheKey = "temp_indexing_op_" + indexName; - var found = _runtimeCache.Get(cacheKey); - //if its still there then it's not done - return found != null + return _rebuildStatusManager.GetRebuildingIndexStatus(indexName) ? null : CreateModel(index!); } @@ -167,12 +175,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) @@ -307,8 +310,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.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 From ad5ccefeef7dc35d2ef93c587b0140bd50275d1c Mon Sep 17 00:00:00 2001 From: Arkadiusz Biel - bielu Date: Sat, 15 Jun 2024 19:16:48 +0100 Subject: [PATCH 02/84] POC: add populator information to reindex model --- .../Examine/ContentIndexPopulator.cs | 21 ++++- .../Examine/ExamineIndexRebuilder.cs | 3 +- .../Examine/IIndexRebuildStatusManager.cs | 29 ++++++- .../Examine/IndexRebuildStatusManager.cs | 76 +++++++++++++++++-- .../ExamineManagementController.cs | 14 +++- 5 files changed, 125 insertions(+), 18 deletions(-) diff --git a/src/Umbraco.Infrastructure/Examine/ContentIndexPopulator.cs b/src/Umbraco.Infrastructure/Examine/ContentIndexPopulator.cs index 19bdac7dbcfd..6b97fe7595df 100644 --- a/src/Umbraco.Infrastructure/Examine/ContentIndexPopulator.cs +++ b/src/Umbraco.Infrastructure/Examine/ContentIndexPopulator.cs @@ -1,3 +1,4 @@ +using System.Text.RegularExpressions; using Examine; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -18,6 +19,7 @@ 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; @@ -78,11 +80,13 @@ public ContentIndexPopulator( IContentService contentService, IUmbracoDatabaseFactory umbracoDatabaseFactory, IValueSetBuilder contentValueSetBuilder, - IOptionsMonitor indexingSettings) + 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 => { @@ -134,22 +138,33 @@ protected override void PopulateIndexes(IReadOnlyList 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/ExamineIndexRebuilder.cs b/src/Umbraco.Infrastructure/Examine/ExamineIndexRebuilder.cs index 8169c86d0df0..ac83462fa814 100644 --- a/src/Umbraco.Infrastructure/Examine/ExamineIndexRebuilder.cs +++ b/src/Umbraco.Infrastructure/Examine/ExamineIndexRebuilder.cs @@ -162,7 +162,7 @@ private void RebuildIndex(string indexName, TimeSpan delay, CancellationToken ca { throw new InvalidOperationException($"No index found with name {indexName}"); } - _indexRebuildStatusManager.SetRebuildingIndexStatus([indexName], true); + _indexRebuildStatusManager.SetReindexStatus(index, _populators.Where(x=>x.IsRegistered(index))); index.CreateIndex(); // clear the index foreach (IIndexPopulator populator in _populators) { @@ -173,7 +173,6 @@ private void RebuildIndex(string indexName, TimeSpan delay, CancellationToken ca populator.Populate(index); } - _indexRebuildStatusManager.SetRebuildingIndexStatus([indexName], false); } } finally diff --git a/src/Umbraco.Infrastructure/Examine/IIndexRebuildStatusManager.cs b/src/Umbraco.Infrastructure/Examine/IIndexRebuildStatusManager.cs index 10c70563d0ef..2d0b45b151ae 100644 --- a/src/Umbraco.Infrastructure/Examine/IIndexRebuildStatusManager.cs +++ b/src/Umbraco.Infrastructure/Examine/IIndexRebuildStatusManager.cs @@ -1,7 +1,30 @@ -namespace Umbraco.Cms.Infrastructure.Examine; +using Examine; + +namespace Umbraco.Cms.Infrastructure.Examine; public interface IIndexRebuildStatusManager { - void SetRebuildingIndexStatus(IEnumerable indexes, bool b); - bool GetRebuildingIndexStatus(string index); + 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 index 2218b5f2afce..322283b557d4 100644 --- a/src/Umbraco.Infrastructure/Examine/IndexRebuildStatusManager.cs +++ b/src/Umbraco.Infrastructure/Examine/IndexRebuildStatusManager.cs @@ -7,22 +7,86 @@ namespace Umbraco.Cms.Infrastructure.Examine; /// public class IndexRebuildStatusManager : IIndexRebuildStatusManager { - IDictionary _rebuildingStatus = new Dictionary(); + private readonly IExamineManager _examineManager; + IDictionary _rebuildingStatus = new Dictionary(); + public IndexRebuildStatusManager(IExamineManager examineManager) { + _examineManager = examineManager; foreach (var index in examineManager.Indexes) { - _rebuildingStatus.Add(index.Name, false); + _rebuildingStatus.Add(index.Name, new IndexStatus()); } } - public void SetRebuildingIndexStatus(IEnumerable indexes, bool isRebuilding) + public IndexStatus? GetRebuildingIndexStatus(string index) => + _rebuildingStatus.TryGetValue(index, out var status) ? status : null; + + public void SetReindexStatus(IIndex index, IEnumerable where) { - foreach (var index in indexes) + if (!_rebuildingStatus.TryGetValue(index.Name, out var status)) { - _rebuildingStatus[index] = isRebuilding; + status = new IndexStatus(); + _rebuildingStatus.Add(index.Name, status); } + + status.IsRebuilding = true; + status.PopulatorStatuses = where.Select(x => new PopulatorStatus(x.GetType().Name)); } - public bool GetRebuildingIndexStatus(string index) => _rebuildingStatus.TryGetValue(index, out var isRebuilding) && isRebuilding; + 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.Web.BackOffice/Controllers/ExamineManagementController.cs b/src/Umbraco.Web.BackOffice/Controllers/ExamineManagementController.cs index e8c79c63aa9d..aea281e42390 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/ExamineManagementController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/ExamineManagementController.cs @@ -140,9 +140,10 @@ public ActionResult GetSearchResults(string searcherName, string? return validate; } + var status = _rebuildStatusManager.GetRebuildingIndexStatus(indexName); //if its still there then it's not done - return _rebuildStatusManager.GetRebuildingIndexStatus(indexName) - ? null + return status?.IsRebuilding == true + ? CreateModel(index!, status) : CreateModel(index!); } @@ -191,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; @@ -230,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; From eb979625d1ad8a56a74cf968ee27444a157c224e Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Tue, 18 Mar 2025 11:05:54 +0100 Subject: [PATCH 03/84] Render folders before files in static files picker. (#18701) --- .../Trees/StaticFilesTreeController.cs | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) 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) From 5570583f707b1872b9bae3e1593dc5bcc35dc13b Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Tue, 18 Mar 2025 11:10:23 +0100 Subject: [PATCH 04/84] Fixes issue with macro rendering in an RTE when GUIDs are used for backoffice document routes (#18691) * Fixes issue with macro rendering in an RTE when GUIDs are used for backoffice document routes. * Fixed null reference error. --- .../Controllers/MacroRenderingController.cs | 46 +++++++++++++++++-- 1 file changed, 41 insertions(+), 5 deletions(-) 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; } From 68acc2aa517060ace3d86366639007acc36a5bb2 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Wed, 19 Mar 2025 16:39:34 +0100 Subject: [PATCH 05/84] Bumped version to 13.9.0-rc. --- version.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.json b/version.json index 94daac6c7ac6..d6effe648ee1 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.9.0-rc", "assemblyVersion": { "precision": "build" }, From eb91f4fef4244e5c0a039ab22212449444f1fed8 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Thu, 20 Mar 2025 06:53:43 +0100 Subject: [PATCH 06/84] Make preview check for delivery API content case insensitive. (#18731) --- .../Services/RequestPreviewService.cs | 4 +-- .../Services/RequestPreviewServiceTests.cs | 31 +++++++++++++++++++ 2 files changed, 33 insertions(+), 2 deletions(-) create mode 100644 tests/Umbraco.Tests.UnitTests/Umbraco.Cms.Api.Delivery/Services/RequestPreviewServiceTests.cs diff --git a/src/Umbraco.Cms.Api.Delivery/Services/RequestPreviewService.cs b/src/Umbraco.Cms.Api.Delivery/Services/RequestPreviewService.cs index 874e2af7bb1e..f891aee6894f 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("Preview"), "true", StringComparison.OrdinalIgnoreCase); } 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); + } +} From 9284b9e0b1ecc3533037b87ab5a0cfc84ed2f6c8 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Thu, 20 Mar 2025 06:53:43 +0100 Subject: [PATCH 07/84] Make preview check for delivery API content case insensitive. (#18731) --- .../Services/RequestPreviewService.cs | 4 +-- .../Services/RequestPreviewServiceTests.cs | 31 +++++++++++++++++++ 2 files changed, 33 insertions(+), 2 deletions(-) create mode 100644 tests/Umbraco.Tests.UnitTests/Umbraco.Cms.Api.Delivery/Services/RequestPreviewServiceTests.cs diff --git a/src/Umbraco.Cms.Api.Delivery/Services/RequestPreviewService.cs b/src/Umbraco.Cms.Api.Delivery/Services/RequestPreviewService.cs index 874e2af7bb1e..f891aee6894f 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("Preview"), "true", StringComparison.OrdinalIgnoreCase); } 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); + } +} From 42a81beeacc2236f1892790e35510b05fb1a3fe1 Mon Sep 17 00:00:00 2001 From: Andreas Zerbst <73799582+andr317c@users.noreply.github.com> Date: Thu, 20 Mar 2025 11:59:03 +0100 Subject: [PATCH 08/84] V13 hotfix sqlserver integration tests (#18744) * Disabled encrypt * Skips integration tests for SQl Server on releases * Removed encrypt --- build/azure-pipelines.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/build/azure-pipelines.yml b/build/azure-pipelines.yml index aa0ddf1db68a..251326676529 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: From fdca086a47ffb1b24adfde8691b1abbb571197c7 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Wed, 26 Mar 2025 15:32:29 +0100 Subject: [PATCH 09/84] build(deps): bump @umbraco-ui/uui from 1.12.2 to 1.13.0 (#18830) --- src/Umbraco.Web.UI.Client/package-lock.json | 963 ++-- src/Umbraco.Web.UI.Client/package.json | 4 +- src/Umbraco.Web.UI.Login/package-lock.json | 4742 ++++++++++--------- src/Umbraco.Web.UI.Login/package.json | 50 +- 4 files changed, 2891 insertions(+), 2868 deletions(-) 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.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" + } } From 05a7d337de628d4c09964464deb387572d752b05 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Thu, 3 Apr 2025 10:32:27 +0200 Subject: [PATCH 10/84] Check we have matched a preview URL by ID when exiting preview. (#18841) --- src/Umbraco.Web.BackOffice/Controllers/PreviewController.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Umbraco.Web.BackOffice/Controllers/PreviewController.cs b/src/Umbraco.Web.BackOffice/Controllers/PreviewController.cs index 676e18a58905..3af0628313b7 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/PreviewController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/PreviewController.cs @@ -217,10 +217,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) From 3e9ff6b5cbc88c4503c3d54d21463fc48dcd562c Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Thu, 3 Apr 2025 10:32:27 +0200 Subject: [PATCH 11/84] Check we have matched a preview URL by ID when exiting preview. (#18841) --- src/Umbraco.Web.BackOffice/Controllers/PreviewController.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Umbraco.Web.BackOffice/Controllers/PreviewController.cs b/src/Umbraco.Web.BackOffice/Controllers/PreviewController.cs index 676e18a58905..3af0628313b7 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/PreviewController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/PreviewController.cs @@ -217,10 +217,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) From d60a2a217c77fdcf06368165d2c045d13a515613 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Thu, 3 Apr 2025 21:13:45 +0200 Subject: [PATCH 12/84] Verify endpoint selection candidates with host attribute are ignored if request doesn't match the configured hosts. (#18820) --- .../Routing/EagerMatcherPolicy.cs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) 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. From a3db45609aaa910dabc38fddeec36a32a3cd664d Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Thu, 3 Apr 2025 21:58:56 +0200 Subject: [PATCH 13/84] Move database cache rebuild to a background task with polling (13) (#18922) * Converts rebuild database cache operation to submit and poll. * Update src/Umbraco.Web.UI.Client/src/views/dashboard/settings/publishedsnapshotcache.controller.js Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Handle HTTP error in status retrieval. * Fixed test build. --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../IPublishedSnapshotService.cs | 54 +++++++++++++++ .../PublishedSnapshotService.cs | 68 ++++++++++++++++++- .../PublishedSnapshotStatus.cs | 5 ++ .../PublishedSnapshotCacheStatusController.cs | 23 ++++++- .../publishedsnapshotcache.controller.js | 25 +++++-- .../PublishedSnapshotServiceTestBase.cs | 6 +- 6 files changed, 169 insertions(+), 12 deletions(-) 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.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/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.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/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(); From 0f025841434c83427f64910a46356491ae2806d1 Mon Sep 17 00:00:00 2001 From: Navya Sinha <143813469+Navya-Sinhaa@users.noreply.github.com> Date: Fri, 4 Apr 2025 02:52:48 +0100 Subject: [PATCH 14/84] attempted fix for Datepicker v13 issue #16008 (#18903) Co-authored-by: Navya Sinha --- .../views/propertyeditors/datepicker/datepicker.controller.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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); } } From 0e0aca55af8ed570fa4b32c4ef4234a10e539af1 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Thu, 3 Apr 2025 21:13:45 +0200 Subject: [PATCH 15/84] Verify endpoint selection candidates with host attribute are ignored if request doesn't match the configured hosts. (#18820) --- .../Routing/EagerMatcherPolicy.cs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) 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. From a486d5df33167812bff09d94d84da83e5db33dc3 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Thu, 10 Apr 2025 07:38:20 +0200 Subject: [PATCH 16/84] Bumped version to 13.8.0. --- version.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.json b/version.json index 94daac6c7ac6..d77e83543874 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.8.0", "assemblyVersion": { "precision": "build" }, From ab31fbb0aaf1a21c3c97fb52f6832de15e8bed61 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Thu, 17 Apr 2025 11:14:17 +0200 Subject: [PATCH 17/84] Bump version to 13.8.1. --- version.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.json b/version.json index d77e83543874..1dffb5f7133e 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", + "version": "13.8.1", "assemblyVersion": { "precision": "build" }, From c6effef622424ac7330cef3c12e8505bf3486b66 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Tue, 22 Apr 2025 06:46:33 +0200 Subject: [PATCH 18/84] Handle file paths as not found in delivery API by route requests (#19063) * Handle file paths as not found in delivery API by route requests. * Move check earlier to handle redirect logic as well. * Spelling: Changed "resolveable" to "resolvable" --------- Co-authored-by: kjac --- .../Content/ByRouteContentApiController.cs | 5 ++ .../DeliveryApi/ApiContentPathResolver.cs | 17 +++++++ .../DeliveryApi/IApiContentPathResolver.cs | 2 + .../ApiContentPathResolverTests.cs | 47 +++++++++++++++++++ 4 files changed, 71 insertions(+) create mode 100644 tests/Umbraco.Tests.UnitTests/Umbraco.Core/DeliveryApi/ApiContentPathResolverTests.cs diff --git a/src/Umbraco.Cms.Api.Delivery/Controllers/Content/ByRouteContentApiController.cs b/src/Umbraco.Cms.Api.Delivery/Controllers/Content/ByRouteContentApiController.cs index 4fed35a28196..f60fa442651b 100644 --- a/src/Umbraco.Cms.Api.Delivery/Controllers/Content/ByRouteContentApiController.cs +++ b/src/Umbraco.Cms.Api.Delivery/Controllers/Content/ByRouteContentApiController.cs @@ -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.Core/DeliveryApi/ApiContentPathResolver.cs b/src/Umbraco.Core/DeliveryApi/ApiContentPathResolver.cs index 4ca6be3932aa..cb3613b4e042 100644 --- a/src/Umbraco.Core/DeliveryApi/ApiContentPathResolver.cs +++ b/src/Umbraco.Core/DeliveryApi/ApiContentPathResolver.cs @@ -15,6 +15,23 @@ public ApiContentPathResolver(IRequestRoutingService requestRoutingService, IApi _apiPublishedContentCache = apiPublishedContentCache; } + 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).Last().Contains('.'); + 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..2e7a92c7a3cc 100644 --- a/src/Umbraco.Core/DeliveryApi/IApiContentPathResolver.cs +++ b/src/Umbraco.Core/DeliveryApi/IApiContentPathResolver.cs @@ -4,5 +4,7 @@ namespace Umbraco.Cms.Core.DeliveryApi; public interface IApiContentPathResolver { + bool IsResolvablePath(string path) => true; + IPublishedContent? ResolveContentPath(string path); } 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..7e4f093794c6 --- /dev/null +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/DeliveryApi/ApiContentPathResolverTests.cs @@ -0,0 +1,47 @@ +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("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); + } + + [Test] + public void Resolves_Content_For_Path() + { + var resolver = CreateResolver(); + var result = resolver.ResolveContentPath(TestPath); + 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(It.Is(y => y == TestPath))) + .Returns(new Mock().Object); + return new ApiContentPathResolver(mockRequestRoutingService.Object, mockApiPublishedContentCache.Object); + } +} From afa6fa5ca2f22c1c8b66de89fcd35795219f79b0 Mon Sep 17 00:00:00 2001 From: Navya Sinha <143813469+Navya-Sinhaa@users.noreply.github.com> Date: Fri, 25 Apr 2025 20:28:46 +0100 Subject: [PATCH 19/84] attempted fix for Save and Preview button #15360 (#19138) Co-authored-by: Navya Sinha --- .../common/directives/components/content/edit.controller.js | 3 +++ 1 file changed, 3 insertions(+) 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 .... }); } From a62fa93c773ed04e5714dc3d52e1579c41d4d124 Mon Sep 17 00:00:00 2001 From: mole Date: Mon, 28 Apr 2025 10:58:29 +0200 Subject: [PATCH 20/84] Use windows agent for nuget push --- build/azure-pipelines.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/build/azure-pipelines.yml b/build/azure-pipelines.yml index 251326676529..384a937d484d 100644 --- a/build/azure-pipelines.yml +++ b/build/azure-pipelines.yml @@ -756,6 +756,8 @@ stages: artifact: nupkg path: $(Build.ArtifactStagingDirectory)/nupkg - task: NuGetCommand@2 + 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: NuGet push inputs: command: 'push' @@ -773,6 +775,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 From 097d0456ccb3a7022e4913636edd2019c487d603 Mon Sep 17 00:00:00 2001 From: Andreas Zerbst <73799582+andr317c@users.noreply.github.com> Date: Tue, 29 Apr 2025 13:48:38 +0200 Subject: [PATCH 21/84] V13 QA updated pipeline for acceptance to avoid issue when installing playwright (#19140) * Updated pipeline to install only the chromium browser * Added junit as reporter for acceptance tests --- build/azure-pipelines.yml | 34 +++++++++++++++---- .../playwright.config.ts | 2 +- 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/build/azure-pipelines.yml b/build/azure-pipelines.yml index 251326676529..4bac2d4d5e37 100644 --- a/build/azure-pipelines.yml +++ b/build/azure-pipelines.yml @@ -531,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 @@ -562,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}}) @@ -687,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 @@ -727,7 +737,7 @@ stages: displayName: Copy Playwright results condition: succeededOrFailed() - # Publish + # Publish test artifacts - task: PublishPipelineArtifact@1 displayName: Publish test artifacts condition: succeededOrFailed() @@ -735,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 ############################################### 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. */ From 8d852590253cef95716469fec3e682f056175b83 Mon Sep 17 00:00:00 2001 From: Dave Woestenborghs Date: Tue, 29 Apr 2025 14:15:27 +0200 Subject: [PATCH 22/84] Upgrade examine to 3.7.1 (#19186) --- Directory.Packages.props | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 @@ - - + + From 1efe860a895ec27b91e892253d0cccf54ce5e29e Mon Sep 17 00:00:00 2001 From: Mole Date: Tue, 29 Apr 2025 18:03:28 +0200 Subject: [PATCH 23/84] V13: Clear Member Username Cache in Load Balanced Environments (#19191) * Clear usernamekey * Odd explaining comment * Update src/Umbraco.Core/Cache/Refreshers/Implement/MemberCacheRefresher.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Make UserNameCachePrefix readonly for better immutabilityly * Move prefix to CacheKeys constants --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/Umbraco.Core/Cache/CacheKeys.cs | 2 ++ .../Implement/MemberCacheRefresher.cs | 17 ++++++++++++++--- .../Repositories/Implement/MemberRepository.cs | 7 +++---- 3 files changed, 19 insertions(+), 7 deletions(-) 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/Refreshers/Implement/MemberCacheRefresher.cs b/src/Umbraco.Core/Cache/Refreshers/Implement/MemberCacheRefresher.cs index ac9dac5a09d1..a2f0a5aa5949 100644 --- a/src/Umbraco.Core/Cache/Refreshers/Implement/MemberCacheRefresher.cs +++ b/src/Umbraco.Core/Cache/Refreshers/Implement/MemberCacheRefresher.cs @@ -73,11 +73,22 @@ private void ClearCache(params JsonPayload[] payloads) foreach (JsonPayload p in payloads) { _idKeyMap.ClearCache(p.Id); - if (memberCache.Success) + if (memberCache.Success is false) { - memberCache.Result?.Clear(RepositoryCacheKeys.GetKey(p.Id)); - memberCache.Result?.Clear(RepositoryCacheKeys.GetKey(p.Username)); + 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)); } } } diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberRepository.cs index 0815595c0695..e71be05fec38 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,7 +843,7 @@ 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(); } From 3caa43a5bf43b9082092e7929b48c0c3f4d6a568 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Sat, 3 May 2025 15:26:20 +0200 Subject: [PATCH 24/84] Fix root path regression for the Delivery API (#19216) --- .../DeliveryApi/ApiContentPathResolver.cs | 6 +++++- .../DeliveryApi/IApiContentPathResolver.cs | 1 + .../DeliveryApi/ApiContentPathResolverTests.cs | 13 +++++++++---- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Core/DeliveryApi/ApiContentPathResolver.cs b/src/Umbraco.Core/DeliveryApi/ApiContentPathResolver.cs index cb3613b4e042..c3967c0bf02d 100644 --- a/src/Umbraco.Core/DeliveryApi/ApiContentPathResolver.cs +++ b/src/Umbraco.Core/DeliveryApi/ApiContentPathResolver.cs @@ -15,6 +15,7 @@ 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 @@ -30,7 +31,10 @@ public virtual bool IsResolvablePath(string path) return true; } - private static bool IsFileRequest(string path) => path.Split('/', StringSplitOptions.RemoveEmptyEntries).Last().Contains('.'); + private static bool IsFileRequest(string path) => path + .Split('/', StringSplitOptions.RemoveEmptyEntries) + .LastOrDefault()? + .Contains('.') is true; public virtual IPublishedContent? ResolveContentPath(string path) { diff --git a/src/Umbraco.Core/DeliveryApi/IApiContentPathResolver.cs b/src/Umbraco.Core/DeliveryApi/IApiContentPathResolver.cs index 2e7a92c7a3cc..d8301666fbc5 100644 --- a/src/Umbraco.Core/DeliveryApi/IApiContentPathResolver.cs +++ b/src/Umbraco.Core/DeliveryApi/IApiContentPathResolver.cs @@ -4,6 +4,7 @@ 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/tests/Umbraco.Tests.UnitTests/Umbraco.Core/DeliveryApi/ApiContentPathResolverTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/DeliveryApi/ApiContentPathResolverTests.cs index 7e4f093794c6..23b6d42415aa 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/DeliveryApi/ApiContentPathResolverTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/DeliveryApi/ApiContentPathResolverTests.cs @@ -11,6 +11,7 @@ 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)] @@ -24,11 +25,12 @@ public void Can_Verify_Resolveable_Paths(string path, bool expected) Assert.AreEqual(expected, result); } - [Test] - public void Resolves_Content_For_Path() + [TestCase(TestPath)] + [TestCase("/")] + public void Resolves_Content_For_Path(string path) { var resolver = CreateResolver(); - var result = resolver.ResolveContentPath(TestPath); + var result = resolver.ResolveContentPath(path); Assert.IsNotNull(result); } @@ -40,7 +42,10 @@ private static ApiContentPathResolver CreateResolver() .Returns((string path) => path); var mockApiPublishedContentCache = new Mock(); mockApiPublishedContentCache - .Setup(x => x.GetByRoute(It.Is(y => y == TestPath))) + .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); } From 83107bb31a7fe98f6c5b0a601c0e8ee898cd274b Mon Sep 17 00:00:00 2001 From: Daniel Nelson Date: Mon, 5 May 2025 14:11:47 +0100 Subject: [PATCH 25/84] Fix: #18421 - Added Max Length validation to PropertyTypeBasic Alias (#18427) Co-authored-by: Daniel Nelson --- .../Models/ContentEditing/PropertyTypeBasic.cs | 1 + .../src/views/components/umb-locked-field.html | 8 +++++++- 2 files changed, 8 insertions(+), 1 deletion(-) 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.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 +
From 34709be6cce9752dfa767dffbf551305f48839bc Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Tue, 6 May 2025 05:11:04 +0200 Subject: [PATCH 26/84] Merge commit from fork * Backport user enumeration fix. * Bump gitversion. * Remove full path details from exception when requesting a path outside of the physical file system's root. * Adds randomness to failed login duration. --- Directory.Build.props | 2 +- .../Configuration/Models/SecuritySettings.cs | 27 +++ src/Umbraco.Core/IO/PhysicalFileSystem.cs | 2 +- src/Umbraco.Core/TimedScope.cs | 162 ++++++++++++++++++ .../Controllers/AuthenticationController.cs | 85 ++++++--- 5 files changed, 253 insertions(+), 25 deletions(-) create mode 100644 src/Umbraco.Core/TimedScope.cs 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/src/Umbraco.Core/Configuration/Models/SecuritySettings.cs b/src/Umbraco.Core/Configuration/Models/SecuritySettings.cs index e68162e6efe0..92f585905af6 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; + private const long StaticUserDefaultFailedLoginDurationInMilliseconds = 1000; + private 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, long.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, long.MaxValue)] + [DefaultValue(StaticUserMinimumFailedLoginDurationInMilliseconds)] + public long UserMinimumFailedLoginDurationInMilliseconds { get; set; } = StaticUserMinimumFailedLoginDurationInMilliseconds; } 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/TimedScope.cs b/src/Umbraco.Core/TimedScope.cs new file mode 100644 index 000000000000..f12f0e90eda8 --- /dev/null +++ b/src/Umbraco.Core/TimedScope.cs @@ -0,0 +1,162 @@ +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); + } + } + + /// + /// 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); + } + } + + private bool TryGetRemaining(out TimeSpan remaining) + { + remaining = _duration.Subtract(Elapsed); + + return remaining > TimeSpan.Zero; + } +} diff --git a/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs b/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs index aedb545a44dd..484a80ac7ef0 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs @@ -74,6 +74,9 @@ public class AuthenticationController : UmbracoApiControllerBase private readonly IUserService _userService; private readonly WebRoutingSettings _webRoutingSettings; + private const int FailedLoginDurationRandomOffsetInMilliseconds = 100; + 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( @@ -415,42 +418,78 @@ public async Task IsAuthenticated() [Authorize(Policy = AuthorizationPolicies.DenyLocalLoginIfConfigured)] public async Task> PostLogin(LoginModel loginModel) { + // Start a timed scope to ensure failed responses return is a consistent time + await using var timedScope = new TimedScope(GetLoginDuration(), CancellationToken.None); + // 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) - { - // return the user detail - return GetUserDetail(_userService.GetByUsername(loginModel.Username)); - } - - if (result.RequiresTwoFactor) + 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; + + // 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)); + } + + private long GetLoginDuration() + { + var loginDuration = Math.Max(_loginDurationAverage ?? _securitySettings.UserDefaultFailedLoginDurationInMilliseconds, _securitySettings.UserMinimumFailedLoginDurationInMilliseconds); + var random = new Random(); + var randomDelay = random.Next(-FailedLoginDurationRandomOffsetInMilliseconds, FailedLoginDurationRandomOffsetInMilliseconds); + loginDuration += randomDelay; + + // Just be sure we don't get a negative number - possible if someone has configured a very low UserMinimumFailedLoginDurationInMilliseconds value. + if (loginDuration < 0) + { + loginDuration = 0; + } - // 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(); + return loginDuration; } /// From e94e16559302127b352e4226e899f22b9c47d56c Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Tue, 6 May 2025 05:17:05 +0200 Subject: [PATCH 27/84] Fixed pipeline definition. --- build/azure-pipelines.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build/azure-pipelines.yml b/build/azure-pipelines.yml index 384a937d484d..48e5e78efa0b 100644 --- a/build/azure-pipelines.yml +++ b/build/azure-pipelines.yml @@ -747,6 +747,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 @@ -756,8 +758,6 @@ stages: artifact: nupkg path: $(Build.ArtifactStagingDirectory)/nupkg - task: NuGetCommand@2 - 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: NuGet push inputs: command: 'push' From dcbbed4160488278a6e7742fd6ada876c6e87f79 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Tue, 6 May 2025 05:35:21 +0200 Subject: [PATCH 28/84] Fixed failures in unit tests. --- src/Umbraco.Core/Configuration/Models/SecuritySettings.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Umbraco.Core/Configuration/Models/SecuritySettings.cs b/src/Umbraco.Core/Configuration/Models/SecuritySettings.cs index 92f585905af6..9570676ebaa2 100644 --- a/src/Umbraco.Core/Configuration/Models/SecuritySettings.cs +++ b/src/Umbraco.Core/Configuration/Models/SecuritySettings.cs @@ -139,7 +139,6 @@ public class SecuritySettings /// 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, long.MaxValue)] [DefaultValue(StaticUserDefaultFailedLoginDurationInMilliseconds)] public long UserDefaultFailedLoginDurationInMilliseconds { get; set; } = StaticUserDefaultFailedLoginDurationInMilliseconds; @@ -149,7 +148,6 @@ public class SecuritySettings /// /// The minimum duration (in milliseconds) of failed login attempts. /// - [Range(0, long.MaxValue)] [DefaultValue(StaticUserMinimumFailedLoginDurationInMilliseconds)] public long UserMinimumFailedLoginDurationInMilliseconds { get; set; } = StaticUserMinimumFailedLoginDurationInMilliseconds; } From 643f0739450d983cbc6a41e90568844c44f70d69 Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Wed, 7 May 2025 18:30:49 +0200 Subject: [PATCH 29/84] `TimedScope` improvements and login duration clean-up (#19243) Co-authored-by: Andy Butland --- .../Configuration/Models/SecuritySettings.cs | 2 ++ src/Umbraco.Core/TimedScope.cs | 4 ++++ .../Controllers/AuthenticationController.cs | 24 +++---------------- 3 files changed, 9 insertions(+), 21 deletions(-) diff --git a/src/Umbraco.Core/Configuration/Models/SecuritySettings.cs b/src/Umbraco.Core/Configuration/Models/SecuritySettings.cs index 9570676ebaa2..ac0727889dc4 100644 --- a/src/Umbraco.Core/Configuration/Models/SecuritySettings.cs +++ b/src/Umbraco.Core/Configuration/Models/SecuritySettings.cs @@ -139,6 +139,7 @@ public class SecuritySettings /// 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; @@ -148,6 +149,7 @@ public class SecuritySettings /// /// 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/TimedScope.cs b/src/Umbraco.Core/TimedScope.cs index f12f0e90eda8..6cd009f3035c 100644 --- a/src/Umbraco.Core/TimedScope.cs +++ b/src/Umbraco.Core/TimedScope.cs @@ -133,6 +133,8 @@ public void Dispose() { Thread.Sleep(remaining); } + + _cancellationTokenSource.Dispose(); } /// @@ -151,6 +153,8 @@ public async ValueTask DisposeAsync() { await Task.Delay(remaining, _timeProvider, _cancellationTokenSource.Token).ConfigureAwait(false); } + + _cancellationTokenSource.Dispose(); } private bool TryGetRemaining(out TimeSpan remaining) diff --git a/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs b/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs index 484a80ac7ef0..ca9cfedf3615 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs @@ -74,7 +74,6 @@ public class AuthenticationController : UmbracoApiControllerBase private readonly IUserService _userService; private readonly WebRoutingSettings _webRoutingSettings; - private const int FailedLoginDurationRandomOffsetInMilliseconds = 100; 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 @@ -419,13 +418,12 @@ public async Task IsAuthenticated() public async Task> PostLogin(LoginModel loginModel) { // Start a timed scope to ensure failed responses return is a consistent time - await using var timedScope = new TimedScope(GetLoginDuration(), CancellationToken.None); + var loginDuration = Math.Max(_loginDurationAverage ?? _securitySettings.UserDefaultFailedLoginDurationInMilliseconds, _securitySettings.UserMinimumFailedLoginDurationInMilliseconds); + await using var timedScope = new TimedScope(loginDuration, HttpContext.RequestAborted); // 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); - + SignInResult result = await _signInManager.PasswordSignInAsync(loginModel.Username, loginModel.Password, true, true); if (result.Succeeded is false) { BackOfficeIdentityUser? user = await _userManager.FindByNameAsync(loginModel.Username.Trim()); @@ -476,22 +474,6 @@ await _userManager.CheckPasswordAsync(user, loginModel.Password)) return GetUserDetail(_userService.GetByUsername(loginModel.Username)); } - private long GetLoginDuration() - { - var loginDuration = Math.Max(_loginDurationAverage ?? _securitySettings.UserDefaultFailedLoginDurationInMilliseconds, _securitySettings.UserMinimumFailedLoginDurationInMilliseconds); - var random = new Random(); - var randomDelay = random.Next(-FailedLoginDurationRandomOffsetInMilliseconds, FailedLoginDurationRandomOffsetInMilliseconds); - loginDuration += randomDelay; - - // Just be sure we don't get a negative number - possible if someone has configured a very low UserMinimumFailedLoginDurationInMilliseconds value. - if (loginDuration < 0) - { - loginDuration = 0; - } - - return loginDuration; - } - /// /// Processes a password reset request. Looks for a match on the provided email address /// and if found sends an email with a link to reset it From 6a4aa04b4733b1393c888854b912f63b6177020e Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Thu, 8 May 2025 10:29:40 +0200 Subject: [PATCH 30/84] Handle existing RichTextEditorValue when parsing from markup or JSON structure (#19266) Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../PropertyEditors/RichTextPropertyEditorHelper.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) 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) { From 0eee3076222cc425d4c1ad54522dac2d87be048b Mon Sep 17 00:00:00 2001 From: Tom van Enckevort Date: Thu, 8 May 2025 13:51:06 +0100 Subject: [PATCH 31/84] Added custom RichTextRegexValidator to validate markup instead of JSON (#19045) Co-authored-by: Migaroez --- .../UmbracoBuilder.CoreServices.cs | 1 + .../PropertyEditors/RichTextPropertyEditor.cs | 52 ++++++++++++++++++- .../Validators/IRichTextRegexValidator.cs | 5 ++ .../Validators/RichTextRegexValidator.cs | 30 +++++++++++ 4 files changed, 86 insertions(+), 2 deletions(-) create mode 100644 src/Umbraco.Infrastructure/PropertyEditors/Validators/IRichTextRegexValidator.cs create mode 100644 src/Umbraco.Infrastructure/PropertyEditors/Validators/RichTextRegexValidator.cs 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/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/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; +} From 3d44a6fdf924477824371468c4b4bf36b44d9b32 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Fri, 9 May 2025 12:22:13 +0200 Subject: [PATCH 32/84] Invalidate external login session on removal of provider (#19273) --- .../Configuration/Models/SecuritySettings.cs | 4 +- src/Umbraco.Core/Extensions/IntExtensions.cs | 38 ++++++++++--- .../IExternalLoginWithKeyRepository.cs | 14 +++-- .../Repositories/IUserRepository.cs | 8 ++- .../Services/ExternalLoginService.cs | 10 ++++ .../Services/IExternalLoginWithKeyService.cs | 26 +++++---- src/Umbraco.Core/Services/IUserService.cs | 6 +++ src/Umbraco.Core/Services/UserService.cs | 10 ++++ .../Implement/ExternalLoginRepository.cs | 6 +++ .../Repositories/Implement/UserRepository.cs | 40 ++++++++++++++ .../UmbracoBuilder.BackOfficeAuth.cs | 3 ++ .../ExternalLoginProviderStartupHandler.cs | 40 ++++++++++++++ .../BackOfficeExternalLoginProviders.cs | 54 +++++++++++++++++++ .../IBackOfficeExternalLoginProviders.cs | 7 +++ .../Security/ConfigureSecurityStampOptions.cs | 2 +- .../Extensions/IntExtensionsTests.cs | 41 ++++++++++++++ 16 files changed, 285 insertions(+), 24 deletions(-) create mode 100644 src/Umbraco.Web.BackOffice/NotificationHandlers/ExternalLoginProviderStartupHandler.cs create mode 100644 tests/Umbraco.Tests.UnitTests/Umbraco.Core/Extensions/IntExtensionsTests.cs diff --git a/src/Umbraco.Core/Configuration/Models/SecuritySettings.cs b/src/Umbraco.Core/Configuration/Models/SecuritySettings.cs index ac0727889dc4..46fc9cf07339 100644 --- a/src/Umbraco.Core/Configuration/Models/SecuritySettings.cs +++ b/src/Umbraco.Core/Configuration/Models/SecuritySettings.cs @@ -28,8 +28,8 @@ public class SecuritySettings internal const int StaticMemberDefaultLockoutTimeInMinutes = 30 * 24 * 60; internal const int StaticUserDefaultLockoutTimeInMinutes = 30 * 24 * 60; - private const long StaticUserDefaultFailedLoginDurationInMilliseconds = 1000; - private const long StaticUserMinimumFailedLoginDurationInMilliseconds = 250; + internal const long StaticUserDefaultFailedLoginDurationInMilliseconds = 1000; + internal const long StaticUserMinimumFailedLoginDurationInMilliseconds = 250; /// /// Gets or sets a value indicating whether to keep the user logged in. 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/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/IUserRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IUserRepository.cs index 35458d6eba10..7c2a7e266458 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; @@ -110,4 +110,10 @@ IEnumerable GetPagedResultsByQuery( void ClearLoginSession(Guid sessionId); IEnumerable GetNextUsers(int id, int count); + + /// + /// 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/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/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/IUserService.cs b/src/Umbraco.Core/Services/IUserService.cs index 40a3fbd89978..801a4b4e02cb 100644 --- a/src/Umbraco.Core/Services/IUserService.cs +++ b/src/Umbraco.Core/Services/IUserService.cs @@ -233,6 +233,12 @@ IEnumerable GetAll( IEnumerable GetNextUsers(int id, int count); + /// + /// 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) { } + #region User groups /// diff --git a/src/Umbraco.Core/Services/UserService.cs b/src/Umbraco.Core/Services/UserService.cs index e0f65cdd5c93..d96824c89fd0 100644 --- a/src/Umbraco.Core/Services/UserService.cs +++ b/src/Umbraco.Core/Services/UserService.cs @@ -720,6 +720,16 @@ public IEnumerable GetNextUsers(int id, int count) } } + /// + public void InvalidateSessionsForRemovedProviders(IEnumerable currentProviderKeys) + { + using (ICoreScope scope = ScopeProvider.CreateCoreScope()) + { + _userRepository.InvalidateSessionsForRemovedProviders(currentProviderKeys); + scope.Complete(); + } + } + /// /// Gets a list of objects associated with a given group /// diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ExternalLoginRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ExternalLoginRepository.cs index 352b1dd3fd47..f111cd5f0f78 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ExternalLoginRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ExternalLoginRepository.cs @@ -56,6 +56,12 @@ public int Count(IQuery query) public void DeleteUserLogins(Guid userOrMemberKey) => Database.Delete("WHERE userOrMemberKey=@userOrMemberKey", new { userOrMemberKey }); + /// + public void DeleteUserLoginsForRemovedProviders(IEnumerable currentLoginProviders) => + Database.Execute(Sql() + .Delete() + .WhereNotIn(x => x.LoginProvider, currentLoginProviders)); + /// public void Save(Guid userOrMemberKey, IEnumerable logins) { diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserRepository.cs index eda072f04963..3fd40fec1c79 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserRepository.cs @@ -1070,5 +1070,45 @@ public IEnumerable GetNextUsers(int id, int count) : GetMany(ids).OrderBy(x => x.Id) ?? Enumerable.Empty(); } + /// + public void InvalidateSessionsForRemovedProviders(IEnumerable currentProviderKeys) + { + // Get all the user or member keys associated with the removed providers. + Sql idsQuery = SqlContext.Sql() + .Select(x => x.UserOrMemberKey) + .From() + .WhereNotIn(x => x.ProviderKey, currentProviderKeys); + List userAndMemberKeysAssociatedWithRemovedProviders = Database.Fetch(idsQuery); + if (userAndMemberKeysAssociatedWithRemovedProviders.Count == 0) + { + return; + } + + // Filter for actual users and convert to integer IDs. + var userIdsAssociatedWithRemovedProviders = userAndMemberKeysAssociatedWithRemovedProviders + .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. + // Note also that umbracoExternalLogin contains members too, as proper GUIDs, so we need to ignore them. + IntExtensions.TryParseFromGuid(userOrMemberKey, out int? userId) ? userId : null; + #endregion } 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/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.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/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); + } + } +} From c0a0a34cbb66a9bb0667fc5bd3f73f4a99f28a7b Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Fri, 9 May 2025 14:57:08 +0200 Subject: [PATCH 33/84] Backport variable name updates and fix found porting the invalidation of sessions after removal of external login provider to 16. --- src/Umbraco.Core/Services/IUserService.cs | 4 ++-- src/Umbraco.Core/Services/UserService.cs | 4 ++-- .../Persistence/Repositories/Implement/UserRepository.cs | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Core/Services/IUserService.cs b/src/Umbraco.Core/Services/IUserService.cs index 801a4b4e02cb..eb69f51ff568 100644 --- a/src/Umbraco.Core/Services/IUserService.cs +++ b/src/Umbraco.Core/Services/IUserService.cs @@ -236,8 +236,8 @@ IEnumerable GetAll( /// /// 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) { } + /// The keys for the currently configured providers. + void InvalidateSessionsForRemovedProviders(IEnumerable currentLoginProviders) { } #region User groups diff --git a/src/Umbraco.Core/Services/UserService.cs b/src/Umbraco.Core/Services/UserService.cs index d96824c89fd0..625dd8e77816 100644 --- a/src/Umbraco.Core/Services/UserService.cs +++ b/src/Umbraco.Core/Services/UserService.cs @@ -721,11 +721,11 @@ public IEnumerable GetNextUsers(int id, int count) } /// - public void InvalidateSessionsForRemovedProviders(IEnumerable currentProviderKeys) + public void InvalidateSessionsForRemovedProviders(IEnumerable currentLoginProviders) { using (ICoreScope scope = ScopeProvider.CreateCoreScope()) { - _userRepository.InvalidateSessionsForRemovedProviders(currentProviderKeys); + _userRepository.InvalidateSessionsForRemovedProviders(currentLoginProviders); scope.Complete(); } } diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserRepository.cs index 3fd40fec1c79..1cda2e5259ce 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserRepository.cs @@ -1071,13 +1071,13 @@ public IEnumerable GetNextUsers(int id, int count) } /// - public void InvalidateSessionsForRemovedProviders(IEnumerable currentProviderKeys) + public void InvalidateSessionsForRemovedProviders(IEnumerable currentLoginProviders) { // Get all the user or member keys associated with the removed providers. Sql idsQuery = SqlContext.Sql() .Select(x => x.UserOrMemberKey) .From() - .WhereNotIn(x => x.ProviderKey, currentProviderKeys); + .WhereNotIn(x => x.LoginProvider, currentLoginProviders); List userAndMemberKeysAssociatedWithRemovedProviders = Database.Fetch(idsQuery); if (userAndMemberKeysAssociatedWithRemovedProviders.Count == 0) { From 0fb91ef8591f1bd9eb2a52787c3b913d303138a4 Mon Sep 17 00:00:00 2001 From: Sven Geusens Date: Mon, 12 May 2025 12:36:39 +0200 Subject: [PATCH 34/84] add single blocklist delete (#18073) Co-authored-by: Andy Butland --- .../EmbeddedResources/Lang/en.xml | 2 ++ .../EmbeddedResources/Lang/en_us.xml | 2 ++ .../umbBlockListPropertyEditor.component.js | 36 +++++++++++++++++-- 3 files changed, 37 insertions(+), 3 deletions(-) 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.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); } From e471c1fc8bd2b9bc0e5167fc16fa96ecdfcdb5b7 Mon Sep 17 00:00:00 2001 From: vantovic <87752132+vantovic@users.noreply.github.com> Date: Tue, 13 May 2025 06:35:42 +0200 Subject: [PATCH 35/84] [V13] User notifications not sent correctly when having more than 400 users (#18370) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Vedran Antović Co-authored-by: Andy Butland --- .../Repositories/IUserRepository.cs | 15 +++++ src/Umbraco.Core/Services/IUserService.cs | 15 +++++ .../Services/NotificationService.cs | 12 ++-- src/Umbraco.Core/Services/UserService.cs | 10 +++ .../Repositories/Implement/UserRepository.cs | 18 ++++- .../Services/UserServiceTests.cs | 67 +++++++++++++++++++ 6 files changed, 128 insertions(+), 9 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/IUserRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IUserRepository.cs index 7c2a7e266458..a8863f135034 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IUserRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IUserRepository.cs @@ -109,8 +109,23 @@ 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. /// diff --git a/src/Umbraco.Core/Services/IUserService.cs b/src/Umbraco.Core/Services/IUserService.cs index eb69f51ff568..df0965fcde80 100644 --- a/src/Umbraco.Core/Services/IUserService.cs +++ b/src/Umbraco.Core/Services/IUserService.cs @@ -231,8 +231,23 @@ 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. /// diff --git a/src/Umbraco.Core/Services/NotificationService.cs b/src/Umbraco.Core/Services/NotificationService.cs index 336435d97ad5..1c370fe5f0fc 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); } diff --git a/src/Umbraco.Core/Services/UserService.cs b/src/Umbraco.Core/Services/UserService.cs index 625dd8e77816..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,15 @@ 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) { diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserRepository.cs index 1cda2e5259ce..385735911209 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(); 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(); From 059766291b437a828ec0fc2732330a709a01a6e9 Mon Sep 17 00:00:00 2001 From: Lili <82643045+Lili-Rossiter@users.noreply.github.com> Date: Tue, 13 May 2025 05:36:20 +0100 Subject: [PATCH 36/84] Fix for block grid styling regression issue #15973 (#19014) Co-authored-by: Andy Butland --- .../blockgridentryeditors/gridblock/gridblock.editor.html | 2 ++ 1 file changed, 2 insertions(+) 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; From 4f1604fea10e7c8c19133e79fa6a5ea9019c5170 Mon Sep 17 00:00:00 2001 From: Sven Geusens Date: Wed, 14 May 2025 08:46:07 +0200 Subject: [PATCH 37/84] V13/bugfix/partial cache (#19314) * Make sure that each optional section of the cachekey always starts and ends with a - * Move secondary logic of clearing the membercaches into its own replacable class * Regsiter the new implementation * Add a mock to the integration tests as appCaches are disabled * Added header comments to components. * Refactored cache key into a method and exposed for testing. Added unit tests to verify behaviour. * Verified also that regex matches only the supplied member and asserted on the key itself. --------- Co-authored-by: Andy Butland --- .../IMemberPartialViewCacheInvalidator.cs | 16 ++++ .../Implement/MemberCacheRefresher.cs | 38 +++++++++- .../MemberPartialViewCacheInvalidator.cs | 35 +++++++++ .../UmbracoBuilderExtensions.cs | 5 ++ .../Extensions/HtmlHelperRenderExtensions.cs | 66 +++++++++++----- .../UmbracoBuilderExtensions.cs | 3 + .../MemberPartialViewCacheInvalidatorTests.cs | 76 +++++++++++++++++++ 7 files changed, 217 insertions(+), 22 deletions(-) create mode 100644 src/Umbraco.Core/Cache/PartialViewCacheInvalidators/IMemberPartialViewCacheInvalidator.cs create mode 100644 src/Umbraco.Web.Website/Cache/PartialViewCacheInvalidators/MemberPartialViewCacheInvalidator.cs create mode 100644 tests/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Cache/PartialViewCacheInvalidators/MemberPartialViewCacheInvalidatorTests.cs 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/Refreshers/Implement/MemberCacheRefresher.cs b/src/Umbraco.Core/Cache/Refreshers/Implement/MemberCacheRefresher.cs index a2f0a5aa5949..fa33832f0039 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 @@ -67,7 +97,9 @@ 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) 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..63f52ce53e31 --- /dev/null +++ b/src/Umbraco.Web.Website/Cache/PartialViewCacheInvalidators/MemberPartialViewCacheInvalidator.cs @@ -0,0 +1,35 @@ +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}-*"); + } + } +} 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/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.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 + { + } +} From 6c2f93e85d58094caa083381fff1667f07f54a37 Mon Sep 17 00:00:00 2001 From: Andreas Zerbst <73799582+andr317c@users.noreply.github.com> Date: Wed, 14 May 2025 09:19:32 +0200 Subject: [PATCH 38/84] V13 QA Updated linux test images to match actual (#19309) Updated linux images --- .../Block-grid-editor-chromium-linux.png | Bin 7374 -> 7140 bytes ...k-grid-editor-with-area-chromium-linux.png | Bin 11209 -> 10597 bytes ...tor-with-area-with-area-chromium-linux.png | Bin 14712 -> 13700 bytes ...-with-custom-stylesheet-chromium-linux.png | Bin 8430 -> 8125 bytes ...tor-with-multiple-areas-chromium-linux.png | Bin 25743 -> 23227 bytes ...-two-custom-stylesheets-chromium-linux.png | Bin 9671 -> 8915 bytes ...ditor-with-two-elements-chromium-linux.png | Bin 9285 -> 8791 bytes 7 files changed, 0 insertions(+), 0 deletions(-) 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 364e1ed8ec6f602499333a673e58dcb0e7cd3ae2..24f7428a4eff6c478af245bee8f67e9eccb960d3 100644 GIT binary patch literal 7140 zcmeHMTTqi{8veDORts^vi%@8w>+Hzxdcd_s6cnnfu$c-d8V(5utreF96A(i5Wz$pJ94I?)ujDh2{ubKxNL@=>_nz&!uR z(pqNbbQICn`ql-udq$U;egjxEZ>sa-))T-wAJm;)`=Q@m_Zwi14?esNZ1TI0)_vlK zO*;U8_$PbUZ1uZe{Ho3m{|bddfzVD;p8y>pIb!3R_$opcR}Fd3o{6AOamXbYDfwfC zqmYlS$EyvRkX22K1S7N@e8-pK;oo;|d^k5oXxboVHCtAB9a@wrN^a2O(9+?aGz1yn zo`xq{y5=pH2P~-Nk!Ty7#8fFex;rX!3#JJS>QnwHhlmIk(QwSf-l!x&p2?Ab_J5l* zI%`XvwO-RY4H4VUa0oS+(a~zoK|{gg-f~+*i)K>SeX81)mL=ZZyq`s8W9g&fH(|na zK~<)L8`UO)ZKKB^T6(vT6Nk&Hevs84Ckpjv6_Zw7hEFp|d@46Gu^Ti@jF<^Q)ZQ;` z4YBWYbw`2fJ4Gh#VKBvVrAaHlu0X=`h2p9#m-}qSRLe|+tfX`hyR2iPwKes0e>z|F zJwPk0SW15tgO!9g=ihQRaD?KV`p}u2R0Qtg`D_n?%hwWYS0gx-y}|?0B{?Z{A+CVQ zh@0sZnovy}goc4B7+E}F9vi+nnhc-C7^&PD98N5rH@O{knJx!;v@ zvm|2#q)hiLEVf(AegW6YW|B>WAY4^nUWCN!3KJe_;v(?nypJzeVh{r(v9BF0wK0<$ z*QFRov2hs04oNt{!;VD5lU!=oIYFM#L>`>}wXcr43Mjaal_O&!7t}28&0F80;gxT9 z%r+a-5#PinwyH1Jo}8VNxL0ONFGW6Kf!G#FyCy$LyizRc3May5^F22yGrhJV+d;PD zFlcR)Cw@}bWo>c9+g>QZOlgb8oCOz)_m7C7B&xQTJ$qbc4#wHJ%N!oBpr0E%g1;Sg z0yjG=H@>)>d$qChaM*5xQNAkJK~87ka;{FbxILwiy~B#xwJnyi7ldn0vBFh$#W~Zv z;}@UuE1?b+dUs^;(2So4!1MLKTBLK<=;iV$SoqSb(${;emn4i+sUup@oSd4>0IM<( zj>w6(1--i>`X%l~kQUGZgnmwE00P^*A()NV!VFDB}-O&Tudj#RENHxh;R7tSU{G0uQ4W8)vB%v1y%XFQ2kGaU_(E%$ zTN8>f9-yli*w>fBZ}rDh>_jpv4{ef`&Rc1aJN7jfWY*+Soq3RZR;D?8TU~N~@fp@n z^oR5c60l%rQF%e-oG#Z&C?p!{%arpq*3MT0Q{O-n&wfHJ6uf<4CkNwakP{>Jd?a(+uqfeF#jryBI_gXv z%m8|33Xvap?h_!cVc}{L{Q1!7jh)uUm-mt!&niLv5U2B zlvLrq2gZ}C$$|zbL0KyvCvQ*c)+^4Xb}DJR!Q`m~66-X&IT%k~K7vfY7)wtzzWwn* zArt}U?;C-GaCmaN3N$P@371S!YJG8LzrKjk`UICFr6Eo5%#u|{;DI2gG&LEG78)FK z>ZAV2QY7O9KHkIo$4|naH8FH!R2N6}%rfTd>1sMBja9_Kq;~eJa>b=Ap65lhOc*0B z@-Drc(@_-DJW6|x@d_=oQ`w^%)YiNiCd-u8bUKtMob2I%-C#$&n%*p0xnH>gcgkd( zMhB>zcD|O*k4lXe%G2p=d@LLoV)7BHD{+^Ym&i zEkdoPrRCLfX(~-s*{q5&4>iX?RhpXBxlY-}I5j}Hx=RB4}^*E=*w8+T#Af literal 7374 zcmeI1X;f2Z8pq>qManqTGRTrqBYFk|71>GRxX}oNwp4*4i3=f)vZoNj7Po@v0CJHf zhQ-!Ob)=+#!muS!1|blVQe_V$YzcdS03l?X_j=CEhxssPPG>&MhkSb8_uTW|`@GNd ze}2z!*Af}{SR)!=ATPbKSJo%dpQzoqVn8@vWtwd3^!W? z6Ih7bxmSq`caXdiZzkadn&=Q%9p2g3*EcCzTZ{YH#{bAV?aGyX#>U2`NTi^z z&wp6KRx}J)ZvwY(Y^&cS4yzu@_4PqJ5LE-c$6Q<(%m}uN4|?T`*x9};q*}a?G;5nwo{53_Msvbe*QZI)b<=6BWN?vDeQFe+&CgxJKBp zFp`Solk$RcgX&^QAt6@P!f4MBGTHv<(X2BMJ|@M+Ix-!@+J!=)b?xlyUIMecyj&RB zY^dutjYu3bvqz%~)6&v(Ys1-i*C~>KE@6o10dfROLFDN;+Ey^n@GtwPg?E@sE>=XWv`8@r+nzHHufo(+YVwD z)0FDR6`}nVrGeG9aKv~#uMGF4wtDRfOHdm#Q<0#UM{$jP+_Pe4dPvHp3EI#MA&=CN zOVX^b;Gpv_FQkWPBu*Ue_;@g8<%Lh@4M%vJ>cHj)`Anu6v=f&CTs3jSR8XY+k^HL} zrqE8TlIxS=VMz;i)~=1EqME5}d0|)0)YQ~o?h$)?`%n=(sSiAQQh0y2^;v0I!|Fhn zWiOGgnN+G|9J=%9z5OYu<{_#GeKD$K_bF&(wo-rzwJ(x=`@nWjp-Lm8>k4`u!-dW$ z(_GsiSNGMvtIc1V5Wo~95xcYbNaFHSq(ww>27J4q z+T(EE_^vah7bBat;EEcW-W=Jfi{hX63(9FoP)P0?JWS7hlm5X;l&JU@Zx9-qp;8$QiELLAt6m#*x!&q)h?uBQ8>)PEu-=5V)giCrV;F- zZu-n7@n;?u4)OJ5K@p9tV zXL^3XL1UR%?G_aVi(tPqm9M<>^-2F}SgxS0jigpmWy1xDOWgiCY6M(SZf?wUGpCU5 zzno7cVc_Swiymxk1jQinj)?b#$XEMsaq1LTiog;ScbA9x(fIJ_=;-A&BFiSx4o^paT2dC7Lr82Z2PX7zG8qJofUlE=(O&#?SF1Fb;U;qHyzU)%?X| zRD75d#y*iL8 zLS)GAQfB+g_k$hcAbWwkJ#ICLPCBBr2E8sluq#s>$2BpcMwW$P|s4 zDXoN1bgjI&*w~DC|4wH<3$SbAa0Ja08gJT$R9BNRa)u!I`t?5)Yi7w5;D)3bsf~oj zr-0D-AKLvRAN3o>06x)gy`3h$%f`2#GD1I+3$R#@dcw|wj*S?s3Ja)<4J%4spYrnZ z0z%SE`ADBX&1h#Qsg~XrYmaAQ)~|(gG^;4V)|OK-;u~$a=&^Js2}nZ6{!DjB09=F< zmFCHE5fHe82nw6eFO3QA)ad;_^<5@pEfjSgZ zGgZQbP#_pI^|W^Bp5hzMHK6ZO$!NRrNLixZFy=0nI#>1Gj}^0Of!$ z6K(r5d7~{4DnmLSgXS=iidBzHO7=v=HJe4N=BlM?xj@j0)h@!44X6qYa10xG)M3YQ z=T`1r0HpMUgg|-9uFT{|hXbpE<&w^)H8*-naZss$`93qp(6$OvM>Ndn&c_b@H54)) zz4A^N)oM{AzHP8nS*)><7EtgPScF0VZF1A?Evck88;F`|KRJM&OJG%~DVjQ8BbCfW z&=a())tm#kp0Xe&=vA)df~d1>KvD8Udk!4^WYwU^3TNA}{7d3IrMLrbuPzghWIn(S%ppp0>9)C_xp8yb}!w3`U_QA18WSE@A(L=Y}c>o_W#8} zLBI4rI#21tqz}`-9I^G1&`UxuiC^<_s;?Y<<>)I%UpfE&6PBJ5dP?Xip{InN690q$ l0eYC~VXBAe|1nH8M)wVe9=G4+SHm9!acA(S%T8Xp{dZ98vylJ* 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 161e2705e81698b8ae25393aa4fac82b5bfe1603..7655665aa24700bbf2ad69d45ccdc4e6f566885d 100644 GIT binary patch literal 10597 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 literal 14712 zcmeHucUaTe)^5}poui|kj5MhZ98si+(yQkfV5ANxy$zt!oAeeOJ!+&XilDTaARtmg z?+J)N2Bl0uinK`Yy@deDw>IZ~_y6y?-*@hPp6fq`pvnI2wby#r`@U=Y_-6y{<41Xq zVlbHF*M7noV=&*sj|cz!?l8P`gb$g*%K=|w?SEhj+WBWNm@}AbxWC^BN|_%DEH#-( zWsEFIdjG0>{pad4-yJ0u{qpXfvQ|t|Uy-)sr@vC~UCx+vij+BZ`Sw5Vy>gblM{_2M zatXgMzv%F@#CKdA97V)4k>7PzZLP9c-aZfCSKYv_Zv^)^!gnUsrlmP z#^~WNXLKj{LJy#y>Zi0Z-@xnL=)c@W@1MvV_$zw7B#Jo*FISqsxq{yQk%ajUy`DaW zK_5Hgc<>B*dsFYf^x@oHiF1Z&s!9(}3JDr&?D^HMcj~UJP=%=TpGz``L}6Y9PkxX7 z(#P$oD%u@sYL1Tt0tjlGeIg<2(?yh?_6yOxau+rSyy`ZTr#|Ie&v2y1&iNal=TNts z+Jv-FeuBG`KwCb_0X-Xje`V=)S&PqhUfKN1Xib8+(b zM}m=uPAGmUA*IN5zA?&}9wvvDx^#)Yl5^ua&Lm3WG}S8SdBMRB1fvYCCU|Ov^-dHI^OHTwnBkDQd>`gaHuc`O$Bym<4Egu+s0Mkj&-IdS z?zXt_ZR zPKSOn(r&T|9NMVL$HEGCJ31`)4uARMsmR#)GefD!2s)!#VYG-Un5)3j#FcsuDYt8| zcNyCq5rWv2%VQ1)ZA_PWq1 zdroPNHrDv<2_sWeF7)^FB^{p6Z@oGO>RtQt!F!b zdP8&Vu-hDnuv^CjvG=y91gwbs<|R|{Ob(}1e^UvYYQKcB9;(-7ejK5)d>D?Qay(vF zp5c$;b*H5H!1CMNg2YgP#Ci@@YxWk{Xsne%ZeWM1{i8m7=vZ%0D!aqlo}!*h4=a9G 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 A%m4rY literal 8430 zcmeHN`Cn7XvJWz&&c*fm5f?<9%TwNa4j>|nBI2mvAPO!V5F*4eBf-RgfJxYr;PBL8 zL||M%gb-1Oe>!#+2KyH3F6&;o93niTI0zzUac7SofmL*En}or3zFEl#Q_Lh4&*760YD1-UQd`(mdD%BRQH=-1uL8clvzy4Sa~?OzX$+MUSG&U!ep z-#I33-8ndXyT5}G^;DQqVTDZ0n)qtZ3v)ECb*>)CXF}zHQjvmOqJE zw4?0PVWQZD=;7DkS~43w-Dlvzu?&zx*q$HLRq9Bpk~OcOk>;Kyc&vdIkrG@g;JpX# z+Xc&+a6XR|#4s!XsDcoNoC#Ps^r^LK2?F=-e}Go&U|h>aASXPO@!>?A)3oua@BQ)l zmE-=15ieOuKf)OsYLSv82N>G%qs|r0d$~rVNyb%GGdZw;+_Q|fUD)itV9r^bAPn%B z?j|qNz)*r1v{`&2%Z7v5Aw}rIo8y<>8ZY0s<4vvaplFGU=Du7n(+X=pidQui6o7ga zZDF1+`XVu3GcnWSDz1^qX!Z3G;dv5jm^(LsSsWF^&Gg=cHN%T>qM3HGQP-W$h4VZGB!yM< z1;)p-fUQrUJ?jF0rk2fwuxIQcR*$^AlhVh6VgU zOzZbsPXLr_NBCPYfnhj+-{lcZA>u_&8YMM4FMXPY;EM)bud#I_HlkUTh}IiYEK8Em zgjb50@EC=}k^MwJJ)Vc+;NLv}y)Ca4V)gSP%x#-NgXaiU%^~#i*Y%XBe7c zKsVj<<^WbWhjAT@Umz}yIm$i0P}Oeek3$1CkP@1bqs|BLri+3*hpZJPi9EM`e< zRhx`p=nqK~h2X0-7_;b4H&LEagO28tSy3lvGPf}u6-fyGjTJ$ZIDd;rv~pL%h@!VA zfR$C->5A2C8|Y^h2ykN`sy~TpR8};h9@b+8L3W&C<;E5ujH|i zNQg(REW5fbDTSzLTS4j(BgH zC;Qt~#lHT{lDa&xXz1{+Q(&F43v5)OdH=iZ?LYl$q-ycCqsEcPF}|_n-t*6_rTE3T z;o&=?BJ1b`Ka@0@B`#Bo>O_a{-6?Gj^AM6ytVR^tt}F zd6o4{>IeEDC0;#Pujuq!*o#9;@{&5zYOTqvM1{J;S#);Fe{N>k;!6vx`Mq$7)?XJz zAtj0m6YkgxtJf0-Yh|6$+XrIIA$|?ZAq;lwf9@Hl=<=w~)4F60_G6y;;0WT9Gdd*U^Idq>!wXVgHDTM^ld zIHNzp6t5U#*{N>$uTnkgG9T)NvqD0H!(7;Am2QitC_*vrTrnqcnedU5+&y*_gX|4e z=p8DXhQx|{enG@(q?{MW1N53!K%(*C*Gs}ozW9wPspw`vI?@!VYx=P$YM4vHm!Z2) z2yX^91Y9SGpZkhCnN}wn+jFEX5!;k6W7`?INk?&9^d}?ivkT=-lQ=+aS|jn z((JYQuI1HgjX^Dn5rj#vu1dv1`3AnP5PRwUq-TdhHCQjJs0U{_Ns(Ttxd~Hr^F9pC z4y)@81GXG$kUhN-A7g^7@qHb1o4AwwYF-fWlZ83A^?pDQQ9lL1 zu>hlaxtK^|`D9#e9lU!YLOSt`W>B&4j&w^g6vkFq2QsE#$a9HCgL;TdrYFr@X=Zbc zQ-9dr9ygen9=YySj=s53=-))6A0ig_=0$>e{NFwWCKcvP;IvD!6~ z2YSqZv@}B`mKxQK(=Wi7kz1tiux=1Cm7D>*G(Yki)j@JL(t@q8RSz;_I@;`biwkN_ zFhg2^4T?k*`D3f%=lL|IgrkUibCt zfS^(#QLVc0W@mOu;%p0e+@`#4rcPzuhZBIQYYVgsxZW%82~cp>tLZ|#2)vwq<&z?Z zdsUJKsR2?Rs?T z-ELY%ZIR5N6Gg1%c}cP(99S3fw73tx_A}{Q`9|GptKLi9Bprit%aOyDuYv#t@N)=( z5>)#O@ z$iL^EBy={yTAazR5liy41=y{R{BdmfTZ}*w!__m~POPYvGcS-RV$o^M(}Di#@x( sR{AfV{94*?dIbCW8-SD9*SbH_%$# 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 literal 25743 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 )t1d2; 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 literal 9285 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 Date: Wed, 14 May 2025 10:25:49 +0200 Subject: [PATCH 39/84] Avoids unnecessary additional domain save notification publishing when sorting an already sorted collection of domains (#19106) Avoids unnecessary additional domain save notification publishings when sorting an already sorted collection of domains. --- src/Umbraco.Core/Services/DomainService.cs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) 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; + } } From 87b2153794df764e2406721546b8239dce009c71 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Wed, 14 May 2025 10:54:00 +0200 Subject: [PATCH 40/84] Adds support for retrieval of data type references when data type is routed using a GUID (#19184) * Adds support for retrieval of data type references when data type is routed using a GUID. * Fixed typos in comments. * Use IIdKeyMap to resolve ID instead of fetching datatype * Use IDKeyMap instead --------- Co-authored-by: mole --- .../Controllers/DataTypeController.cs | 64 ++++++++++++++++--- 1 file changed, 55 insertions(+), 9 deletions(-) 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) { From 8433b2b6376378014fc60314e4ebecee3e876b85 Mon Sep 17 00:00:00 2001 From: David Challener Date: Wed, 14 May 2025 10:03:53 +0100 Subject: [PATCH 41/84] User invite email fails if visited more than once without completing (#17901) Don't change securitystamp until user created --- .../Controllers/BackOfficeController.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs index e81db799c3a3..f6d7a76a488c 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" }); From 7d6a1e54e60a4df22dc0e39adc80768220e002da Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Tue, 20 May 2025 14:21:23 +0100 Subject: [PATCH 42/84] Optimize the member save as part of the member login process, by-passing locking and audit steps and handling only the expected update properties (#19308) * Optimize the member save as part of the member login process, by-passing locking and audit steps and handling only the expected update properties. * Added unit test to verify new behaviour. * Update src/Umbraco.Infrastructure/Security/MemberUserStore.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Updates from code review. * Improved comments. * Add state information to notification indicating whether a member is saved via only the update of login properties. --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Migaroez --- .../Repositories/IMemberRepository.cs | 7 ++ src/Umbraco.Core/Services/IMemberService.cs | 7 ++ src/Umbraco.Core/Services/MemberService.cs | 42 ++++++++++++ .../Implement/MemberRepository.cs | 45 +++++++++++++ .../Security/MemberUserStore.cs | 64 ++++++++++++------- .../Security/MemberUserStoreTests.cs | 58 +++++++++++++++++ 6 files changed, 200 insertions(+), 23 deletions(-) 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/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/MemberService.cs b/src/Umbraco.Core/Services/MemberService.cs index b405519616bc..ba21a371ac05 100644 --- a/src/Umbraco.Core/Services/MemberService.cs +++ b/src/Umbraco.Core/Services/MemberService.cs @@ -820,6 +820,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.Infrastructure/Persistence/Repositories/Implement/MemberRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberRepository.cs index e71be05fec38..6fd32b2d798b 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberRepository.cs @@ -848,5 +848,50 @@ protected override void PersistUpdatedItem(IMember entity) 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/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/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() { From 127c6c4b53957770e54cdea03400aa2a0d768208 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Tue, 20 May 2025 14:21:23 +0100 Subject: [PATCH 43/84] Optimize the member save as part of the member login process, by-passing locking and audit steps and handling only the expected update properties (#19308) * Optimize the member save as part of the member login process, by-passing locking and audit steps and handling only the expected update properties. * Added unit test to verify new behaviour. * Update src/Umbraco.Infrastructure/Security/MemberUserStore.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Updates from code review. * Improved comments. * Add state information to notification indicating whether a member is saved via only the update of login properties. --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Migaroez --- .../Repositories/IMemberRepository.cs | 7 ++ src/Umbraco.Core/Services/IMemberService.cs | 7 ++ src/Umbraco.Core/Services/MemberService.cs | 42 ++++++++++++ .../Implement/MemberRepository.cs | 45 +++++++++++++ .../Security/MemberUserStore.cs | 64 ++++++++++++------- .../Security/MemberUserStoreTests.cs | 58 +++++++++++++++++ 6 files changed, 200 insertions(+), 23 deletions(-) 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/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/MemberService.cs b/src/Umbraco.Core/Services/MemberService.cs index b405519616bc..ba21a371ac05 100644 --- a/src/Umbraco.Core/Services/MemberService.cs +++ b/src/Umbraco.Core/Services/MemberService.cs @@ -820,6 +820,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.Infrastructure/Persistence/Repositories/Implement/MemberRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberRepository.cs index e71be05fec38..6fd32b2d798b 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberRepository.cs @@ -848,5 +848,50 @@ protected override void PersistUpdatedItem(IMember entity) 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/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/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() { From eee04f47036f3e5b8e806035e58f3b1666be9928 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Thu, 22 May 2025 08:58:37 +0200 Subject: [PATCH 44/84] fix: check for NullRepresentationInCache in AppCacheExtensions (#19350) * fix: add appcache null check * Moved constant into standard location. Removed now unnecessary comment. --------- Co-authored-by: Andy Butland # Conflicts: # src/Umbraco.Core/Cache/AppCacheExtensions.cs # src/Umbraco.Core/Constants-Cache.cs --- src/Umbraco.Core/Cache/AppCacheExtensions.cs | 6 ++++-- src/Umbraco.Core/Constants-Cache.cs | 15 +++++++++++++++ .../Cache/DefaultRepositoryCachePolicy.cs | 8 ++------ 3 files changed, 21 insertions(+), 8 deletions(-) create mode 100644 src/Umbraco.Core/Constants-Cache.cs 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/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.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) From 5556b0fe0c7822867cf2d381dc48107fe92330b1 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Thu, 22 May 2025 10:13:31 +0200 Subject: [PATCH 45/84] Fixed check for navigation to list after delete of member (#19364) Fixed check for navigation to list after delete of member. --- .../src/views/member/member.delete.controller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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')); } From c609cafa9b7184479db98583005e9f3d8fab46fc Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Thu, 22 May 2025 10:13:31 +0200 Subject: [PATCH 46/84] Fixed check for navigation to list after delete of member (#19364) Fixed check for navigation to list after delete of member. --- .../src/views/member/member.delete.controller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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')); } From d677e948f1b9cf6c477c5e65a9f51f91083cd32b Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Fri, 23 May 2025 09:41:15 +0200 Subject: [PATCH 47/84] Fix issue with requests to delivery API by path where URL segment contains special characters (#19390) Fix issue with requests to delivery API by path where URL segment contains special characters. --- .../Services/RoutingServiceBase.cs | 2 +- .../Umbraco.Cms.Api.Delivery.csproj | 3 + .../DeliveryApi/RequestRoutingServiceTests.cs | 74 +++++++++++++++++++ 3 files changed, 78 insertions(+), 1 deletion(-) create mode 100644 tests/Umbraco.Tests.Integration/Umbraco.Core/DeliveryApi/RequestRoutingServiceTests.cs 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/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); + } +} From 4d8ca457ecf60ae33b6dae8534bc367569a8353c Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Fri, 23 May 2025 12:19:43 +0200 Subject: [PATCH 48/84] Removes unnecessary newlines from rich text as JSON delivery API output (#19391) * Removes unnecessary newlines from rich text as JSON delivery API output. * Fix case from PR feedback. --- .../DeliveryApi/ApiRichTextElementParser.cs | 12 ++-- .../DeliveryApi/RichTextParserTests.cs | 59 ++++++++++++++++++- 2 files changed, 63 insertions(+), 8 deletions(-) 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/tests/Umbraco.Tests.UnitTests/Umbraco.Core/DeliveryApi/RichTextParserTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/DeliveryApi/RichTextParserTests.cs index 2a8da7acb07d..f9f3722a0fea 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; @@ -357,16 +357,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); From b348b84b639896dd90f07ab4c9601c2c495f390d Mon Sep 17 00:00:00 2001 From: Rowan Bottema Date: Tue, 27 May 2025 06:51:30 +0200 Subject: [PATCH 49/84] Lock appropriate tree for media operations (#19422) The MediaService currently locks the ContentTree for GetPagedOfType(s) operations, but it's querying the MediaTree. This ensures we lock the correct tree. --- src/Umbraco.Core/Services/MediaService.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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); } From 4b83a74bdb054374b721e988afb7dd1fbcb92472 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Wed, 28 May 2025 14:33:47 +0200 Subject: [PATCH 50/84] Resolved exception thrown from NuCache locking mechanism on near-simultaneous content publish requests (#19434) * Removed the check before the wait that threw the recursive lock exception. Added additional check to ensure we don't release a lock that's already released. * Removed unnecessary check on releasing the lock. --- src/Umbraco.PublishedCache.NuCache/ContentStore.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) 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; From ebd228c3d76b2367ce45d2194e6fc74f779b3ccb Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Fri, 30 May 2025 09:05:22 +0200 Subject: [PATCH 51/84] Ensure tag operations are case insensitive on insert across database types (#19439) * Ensure tag operations are case insensitve on insert across database types. * Ensure tags provided in a single property are case insensitively distinct when saving the tags and relationships. * Update src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TagRepository.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Handle case sensitivity on insert with tag groups too. --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../Repositories/Implement/TagRepository.cs | 17 +++- .../Repositories/TagRepositoryTest.cs | 93 ++++++++++++++++++- 2 files changed, 104 insertions(+), 6 deletions(-) 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/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()); } From 0bcae3e87bf35ab27f371e40eed231e5ca576b0d Mon Sep 17 00:00:00 2001 From: Sven Geusens Date: Tue, 3 Jun 2025 05:32:03 +0200 Subject: [PATCH 52/84] Fix null member edgecase (#19463) --- .../MemberPartialViewCacheInvalidator.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Umbraco.Web.Website/Cache/PartialViewCacheInvalidators/MemberPartialViewCacheInvalidator.cs b/src/Umbraco.Web.Website/Cache/PartialViewCacheInvalidators/MemberPartialViewCacheInvalidator.cs index 63f52ce53e31..8ee4be4c00ee 100644 --- a/src/Umbraco.Web.Website/Cache/PartialViewCacheInvalidators/MemberPartialViewCacheInvalidator.cs +++ b/src/Umbraco.Web.Website/Cache/PartialViewCacheInvalidators/MemberPartialViewCacheInvalidator.cs @@ -31,5 +31,8 @@ public void ClearPartialViewCacheItems(IEnumerable 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-*"); } } From 97cc3ca5817551a408f08c92bbe6ac30c5bf2cf2 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Thu, 5 Jun 2025 06:24:08 +0200 Subject: [PATCH 53/84] Bumped version to 13.9.0. --- version.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.json b/version.json index d6effe648ee1..d532b9300753 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.9.0-rc", + "version": "13.9.0", "assemblyVersion": { "precision": "build" }, From 1d6e7f15b97d5c191329d7d2a7143f9ce3ccaf24 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Thu, 5 Jun 2025 07:47:57 +0200 Subject: [PATCH 54/84] Bumped version to 13.9.1. --- version.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.json b/version.json index d6effe648ee1..9a6a74071cbe 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.9.0-rc", + "version": "13.9.1", "assemblyVersion": { "precision": "build" }, From 28570b43e80ccebb2b3d2e4da2b99837cdbba27e Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Tue, 10 Jun 2025 08:19:52 +0200 Subject: [PATCH 55/84] Fix issues with removal of user logins on change to external login provider configuration (13) (#19511) * Ensure to delete related tokens when removing logins for removed external login providers. Ensure to avoid removing logins for members. * Applied suggestions from code review. * Removed unnecessary <= check. --- .../Implement/ExternalLoginRepository.cs | 36 +++++++++++++------ 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ExternalLoginRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ExternalLoginRepository.cs index f111cd5f0f78..bb96c7c8c8ea 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ExternalLoginRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ExternalLoginRepository.cs @@ -57,10 +57,17 @@ public void DeleteUserLogins(Guid userOrMemberKey) => Database.Delete("WHERE userOrMemberKey=@userOrMemberKey", new { userOrMemberKey }); /// - public void DeleteUserLoginsForRemovedProviders(IEnumerable currentLoginProviders) => - Database.Execute(Sql() - .Delete() - .WhereNotIn(x => x.LoginProvider, currentLoginProviders)); + 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) @@ -100,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) { @@ -116,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) { From f6dbe0f33e10ba4ea591f91df2c24b56314fafb7 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Tue, 10 Jun 2025 08:33:39 +0200 Subject: [PATCH 56/84] Bumped version to 13.9.2. --- version.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.json b/version.json index 9a6a74071cbe..26f06acb1e9e 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.9.1", + "version": "13.9.2", "assemblyVersion": { "precision": "build" }, From 1e66fb6ab382af74b719c29b01403e5762bc9e3b Mon Sep 17 00:00:00 2001 From: Laura Neto <12862535+lauraneto@users.noreply.github.com> Date: Fri, 13 Jun 2025 07:15:19 +0200 Subject: [PATCH 57/84] Add a warning to the `CreateContentFromBlueprint` method xml docs (#19542) Add a remark to `ContentService.CreateContentFromBlueprint` --- src/Umbraco.Core/Services/ContentService.cs | 1 + src/Umbraco.Core/Services/IContentService.cs | 3 +++ 2 files changed, 4 insertions(+) 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/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); /// From 4ad18dc963e45ed256a4515d487213788e171eb6 Mon Sep 17 00:00:00 2001 From: Steve <114907079+steveatkiss@users.noreply.github.com> Date: Mon, 23 Jun 2025 10:34:51 +0100 Subject: [PATCH 58/84] Preserve word boundaries when indexing RTE content with
tags (#19540) * Preserve word boundaries when indexing RTE content with
tags Replace
tags with spaces before HTML stripping to prevent word concatenation in Examine index. Fixes issue where "John Smith
Company ABC" was indexed as "John SmithCompany ABC" instead of "John Smith Company ABC". - Add regex to replace
variants with spaces in RichTextPropertyIndexValueFactory - Handles
,
with spaces and attributes - Maintains existing StripHtml() behavior for all other HTML tags * Added unit test with test cases for expected index values * Regex tweak to avoid matches on etc * Tweaked tests as per PR feedback * Update tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/RichTextPropertyIndexValueFactoryTests.cs Updated tests Co-authored-by: Kenn Jacobsen * Tweaked test expectations --------- Co-authored-by: Emma Garland Co-authored-by: Emma L Garland Co-authored-by: Kenn Jacobsen --- .../RichTextPropertyIndexValueFactory.cs | 29 ++++++- .../RichTextPropertyIndexValueFactoryTests.cs | 82 +++++++++++++++++++ 2 files changed, 110 insertions(+), 1 deletion(-) create mode 100644 tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/RichTextPropertyIndexValueFactoryTests.cs 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/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()); + } +} From b4144564c836ec6929111ce2a12eb1f67b42d61e Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Tue, 24 Jun 2025 08:39:17 +0200 Subject: [PATCH 59/84] Merge commit from fork --- .../Controllers/AuthenticationController.cs | 19 +++++++++++++++---- .../Controllers/BackOfficeController.cs | 5 +++++ .../Extensions/HttpContextExtensions.cs | 11 +++++++++++ 3 files changed, 31 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs b/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs index ca9cfedf3615..4b46bca7a1e1 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs @@ -131,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(); } /// @@ -417,6 +422,8 @@ public async Task IsAuthenticated() [Authorize(Policy = AuthorizationPolicies.DenyLocalLoginIfConfigured)] public async Task> PostLogin(LoginModel loginModel) { + HttpContext.EndPasswordResetFlowSession(); + // 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); @@ -490,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 @@ -646,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 f6d7a76a488c..9a019c537a8d 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs @@ -402,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/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; } From 1f5c21c63186fa4a478b8e0a2d0bd162d16854a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Brynjar=20=C3=9Eorsteinsson?= <85184333+Brynjarth@users.noreply.github.com> Date: Tue, 1 Jul 2025 06:11:00 +0000 Subject: [PATCH 60/84] Fix pagination in Content Delivery API Index Helper (#19606) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Refactor descendant enumeration in DeliveryApiContentIndexHelper Improved loop condition to allow for processing of more than 10.000 descendants for indexing. * Add failing test for original issue. * Renamed variable for clarity. --------- Co-authored-by: Brynjar Þorsteinsson Co-authored-by: Andy Butland --- .../Examine/DeliveryApiContentIndexHelper.cs | 21 ++- .../DeliveryApiContentIndexHelperTests.cs | 120 ++++++++++++++++++ 2 files changed, 134 insertions(+), 7 deletions(-) create mode 100644 tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Examine/DeliveryApiContentIndexHelperTests.cs 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/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; + } +} From 990e379ea85911ef196ae524a224e93a779bc6b1 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Tue, 1 Jul 2025 14:11:04 +0200 Subject: [PATCH 61/84] Ensures that null values aren't used to create a CompositeStringStringKey (#19646) Ensures that null values aren't used to create a CompositeStringStringKey. --- src/Umbraco.PublishedCache.NuCache/Property.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.PublishedCache.NuCache/Property.cs b/src/Umbraco.PublishedCache.NuCache/Property.cs index 09716ef43a35..41532d4944c1 100644 --- a/src/Umbraco.PublishedCache.NuCache/Property.cs +++ b/src/Umbraco.PublishedCache.NuCache/Property.cs @@ -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 From a60ccd389ba4ef0c7bb94de710d19dbf49f050d9 Mon Sep 17 00:00:00 2001 From: kows Date: Mon, 7 Jul 2025 10:51:50 +0200 Subject: [PATCH 62/84] #16772 partial fix backoffice redirect after login (#19663) * #16772 partial fix backoffice redirect after login * #16772 partial fix backoffice OpenId redirect after login --- src/Umbraco.Web.UI.Login/src/auth.element.ts | 2 +- .../src/components/external-login-provider.element.ts | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Login/src/auth.element.ts b/src/Umbraco.Web.UI.Login/src/auth.element.ts index e195f3986a21..cbd23839ffc7 100644 --- a/src/Umbraco.Web.UI.Login/src/auth.element.ts +++ b/src/Umbraco.Web.UI.Login/src/auth.element.ts @@ -99,7 +99,7 @@ export default class UmbAuthElement extends LitElement { @property({attribute: 'return-url'}) set returnPath(value: string) { - umbAuthContext.returnPath = value; + umbAuthContext.returnPath = `${value}${encodeURIComponent(window.location.hash)}`; } 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; } From 13a2cd71c4408d89193f9e5358381b4690872839 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Mon, 7 Jul 2025 14:01:36 +0200 Subject: [PATCH 63/84] Clear member cache by older user name when member user name is updated. (#19672) * Clear member cache by older user name when member user name is updated. * Added unit test. --- .../Cache/DistributedCacheExtensions.cs | 21 +++++- .../Implement/MemberCacheRefresher.cs | 9 +++ src/Umbraco.Core/Constants-Entities.cs | 14 ++++ .../Handlers/PublicAccessHandler.cs | 11 ++-- src/Umbraco.Core/Models/MemberGroup.cs | 2 +- src/Umbraco.Core/Services/MemberService.cs | 17 ++++- .../Cache/DistributedCacheExtensionsTests.cs | 64 +++++++++++++++++++ 7 files changed, 126 insertions(+), 12 deletions(-) create mode 100644 src/Umbraco.Core/Constants-Entities.cs create mode 100644 tests/Umbraco.Tests.UnitTests/Umbraco.Core/Cache/DistributedCacheExtensionsTests.cs 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/Refreshers/Implement/MemberCacheRefresher.cs b/src/Umbraco.Core/Cache/Refreshers/Implement/MemberCacheRefresher.cs index fa33832f0039..99d6004ec6d9 100644 --- a/src/Umbraco.Core/Cache/Refreshers/Implement/MemberCacheRefresher.cs +++ b/src/Umbraco.Core/Cache/Refreshers/Implement/MemberCacheRefresher.cs @@ -70,6 +70,8 @@ public JsonPayload(int id, string? username, bool removed) public string? Username { get; } + public string? PreviousUsername { get; set; } + public bool Removed { get; } } @@ -121,6 +123,13 @@ private void ClearCache(params JsonPayload[] payloads) // 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.PreviousUsername)); + memberCache.Result?.Clear(RepositoryCacheKeys.GetKey(CacheKeys.MemberUserNameCachePrefix + p.PreviousUsername)); + } } } } 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/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/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/Services/MemberService.cs b/src/Umbraco.Core/Services/MemberService.cs index ba21a371ac05..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); 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(); + } +} From 53cc663bde23c250ad0d01fd2a5d40f3b9c55583 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Tue, 8 Jul 2025 15:40:53 +0200 Subject: [PATCH 64/84] Register no-op implementation of IMemberPartialViewCacheInvalidator in headless setups (#19666) * Register no-op implementation of IMemberPartialViewCacheInvalidator in headless setups. * Tidied usings. --- .../NoopMemberPartialViewCacheInvalidator.cs | 9 +++++++++ src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs | 8 +++++++- 2 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 src/Umbraco.Core/Cache/PartialViewCacheInvalidators/NoopMemberPartialViewCacheInvalidator.cs 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/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(); } } } From 2748fdfc4858f6752182d340b4b7aac3b5623f7f Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Fri, 11 Jul 2025 15:51:05 +0200 Subject: [PATCH 65/84] Adds variation by the header name Accept-Language to the delivery API output cache policy (#19709) * Adds variation by the header name Accept-Language to the develivery API output cache policy * Removed obsolete constructor (not necessary as the class is internal). * Introduce contants for header names. --- .../Caching/DeliveryApiOutputCachePolicy.cs | 12 +++++-- .../Content/ByRouteContentApiController.cs | 2 +- .../UmbracoBuilderExtensions.cs | 13 +++++-- .../SwaggerContentDocumentationFilter.cs | 8 ++--- .../Filters/SwaggerDocumentationFilterBase.cs | 4 +-- .../Services/ApiAccessService.cs | 4 +-- .../Services/RequestPreviewService.cs | 2 +- .../Services/RequestStartItemProvider.cs | 2 +- src/Umbraco.Core/Constants-DeliveryApi.cs | 34 +++++++++++++++++-- 9 files changed, 62 insertions(+), 19 deletions(-) diff --git a/src/Umbraco.Cms.Api.Delivery/Caching/DeliveryApiOutputCachePolicy.cs b/src/Umbraco.Cms.Api.Delivery/Caching/DeliveryApiOutputCachePolicy.cs index da1580554cbb..2c626d0bfb4a 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) { @@ -20,6 +25,7 @@ ValueTask IOutputCachePolicy.CacheRequestAsync(OutputCacheContext context, Cance context.EnableOutputCaching = requestPreviewService.IsPreview() is false; 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 f60fa442651b..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( 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/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 f891aee6894f..25689125f538 100644 --- a/src/Umbraco.Cms.Api.Delivery/Services/RequestPreviewService.cs +++ b/src/Umbraco.Cms.Api.Delivery/Services/RequestPreviewService.cs @@ -11,5 +11,5 @@ public RequestPreviewService(IHttpContextAccessor httpContextAccessor) } /// - public bool IsPreview() => string.Equals(GetHeaderValue("Preview"), "true", StringComparison.OrdinalIgnoreCase); + 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.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"; + + + } } } From ce40103c4f22c74ba438fc564021df0774de8461 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Tue, 15 Jul 2025 13:57:48 +0200 Subject: [PATCH 66/84] Add support for programmatic creation of property types providing the data type key (#19720) * Add support for programmatic creation of property types providing the data type key. * Add integration tests --------- Co-authored-by: kjac --- .../Implement/ContentTypeRepository.cs | 6 +- .../Implement/ContentTypeRepositoryBase.cs | 81 +++++++++++++------ .../Implement/MediaTypeRepository.cs | 6 +- .../Implement/MemberTypeRepository.cs | 8 +- .../Testing/UmbracoIntegrationTest.cs | 2 + .../Repositories/DocumentRepositoryTest.cs | 2 +- .../Repositories/MediaRepositoryTest.cs | 2 +- .../Repositories/MediaTypeRepositoryTest.cs | 3 +- .../Repositories/MemberTypeRepositoryTest.cs | 3 +- .../Repositories/TemplateRepositoryTest.cs | 2 +- .../Services/ContentTypeServiceTests.cs | 59 ++++++++++++++ 11 files changed, 136 insertions(+), 38 deletions(-) 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/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/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/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.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/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/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) From 417f15197eadcec2f77ecce69607d7af8bb2c263 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Wed, 16 Jul 2025 10:45:17 +0200 Subject: [PATCH 67/84] Parse update date before sorting in media list view (#19711) * Parse update date before sorting in media list view. * Moved function placement. --- .../common/directives/components/umbmediagrid.directive.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) 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 = { From ebd0017f6e89bfd1151df059277f15d8fe7d40a0 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Mon, 21 Jul 2025 11:53:33 +0200 Subject: [PATCH 68/84] Bumped version to 13.9.3. --- version.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.json b/version.json index 26f06acb1e9e..cec88476b5e0 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.9.2", + "version": "13.9.3", "assemblyVersion": { "precision": "build" }, From 67abecc2524fb6f0c502135c7a0181a41bba437f Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Mon, 21 Jul 2025 12:24:51 +0200 Subject: [PATCH 69/84] Add defensive coding to the member application initializer (#19760) --- ...izeMemberApplicationNotificationHandler.cs | 69 +++++++++++++------ 1 file changed, 48 insertions(+), 21 deletions(-) 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) From 59ad07209b0238a7648f1e52e7f068c7f451812e Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Tue, 22 Jul 2025 09:50:05 +0200 Subject: [PATCH 70/84] Retrieve only user external logins when invalidate following removal of backoffice external user login (#19766) * Retrieve only user external logins when invalidate following removal of backoffice external user login. * Improved variable name. --- .../Repositories/Implement/UserRepository.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserRepository.cs index 385735911209..611da2dcacf3 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserRepository.cs @@ -1085,19 +1085,20 @@ private IEnumerable PerformGetNextUsers(int id, bool approvedOnly, int co /// public void InvalidateSessionsForRemovedProviders(IEnumerable currentLoginProviders) { - // Get all the user or member keys associated with the removed providers. + // 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 userAndMemberKeysAssociatedWithRemovedProviders = Database.Fetch(idsQuery); - if (userAndMemberKeysAssociatedWithRemovedProviders.Count == 0) + List userKeysAssociatedWithRemovedProviders = Database.Fetch(idsQuery); + if (userKeysAssociatedWithRemovedProviders.Count == 0) { return; } - // Filter for actual users and convert to integer IDs. - var userIdsAssociatedWithRemovedProviders = userAndMemberKeysAssociatedWithRemovedProviders + // Convert to user integer IDs. + var userIdsAssociatedWithRemovedProviders = userKeysAssociatedWithRemovedProviders .Select(ConvertUserKeyToUserId) .Where(x => x.HasValue) .Select(x => x!.Value) @@ -1119,7 +1120,6 @@ public void InvalidateSessionsForRemovedProviders(IEnumerable currentLog // 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. - // Note also that umbracoExternalLogin contains members too, as proper GUIDs, so we need to ignore them. IntExtensions.TryParseFromGuid(userOrMemberKey, out int? userId) ? userId : null; #endregion From 22f748161a1eb7e00c3b230026b1be63ceb325a4 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Tue, 22 Jul 2025 10:11:54 +0200 Subject: [PATCH 71/84] Bumped version to 13.11.0-rc. --- version.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.json b/version.json index 7ab800f4baf0..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.10.0-rc", + "version": "13.11.0-rc", "assemblyVersion": { "precision": "build" }, From 509a2e18d09aa92fd5428068c9764f3db029a96b Mon Sep 17 00:00:00 2001 From: Aaron Date: Wed, 23 Jul 2025 10:54:22 +0100 Subject: [PATCH 72/84] The filter param should be urlencoded (#19774) --- .../src/common/resources/content.resource.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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); From 0d94f8bfafb7a6c248ce50654c01d026837dae1b Mon Sep 17 00:00:00 2001 From: Sven Geusens Date: Fri, 25 Jul 2025 13:07:20 +0200 Subject: [PATCH 73/84] Fix issue with use of EF Core scopes within notification handlers (take 2 - handling scopes with a base parent) (#19797) * Add integration tests that shows the problem * Fix the problem and add explenation * Improved comments slightly to help when we come back here! Moved tests alongside existing ones related to scopes. Removed long running attribute from tests (they are quite fast). * Fixed casing in comment. --------- Co-authored-by: Andy Butland --- .../Scoping/EFCoreScope.cs | 8 +- src/Umbraco.Core/Scoping/CoreScope.cs | 2 + .../Scoping/NestedScopeTests.cs | 222 ++++++++++++++++++ 3 files changed, 231 insertions(+), 1 deletion(-) create mode 100644 tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Scoping/NestedScopeTests.cs 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/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/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(); + } + } + } + } +} From 9f37db18d11c8ba4e3ecdeb35291af30ebee7cd0 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Tue, 29 Jul 2025 05:10:52 +0200 Subject: [PATCH 74/84] Merge commit from fork Co-authored-by: kjac --- .../Caching/DeliveryApiOutputCachePolicy.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Cms.Api.Delivery/Caching/DeliveryApiOutputCachePolicy.cs b/src/Umbraco.Cms.Api.Delivery/Caching/DeliveryApiOutputCachePolicy.cs index da1580554cbb..0f318c960289 100644 --- a/src/Umbraco.Cms.Api.Delivery/Caching/DeliveryApiOutputCachePolicy.cs +++ b/src/Umbraco.Cms.Api.Delivery/Caching/DeliveryApiOutputCachePolicy.cs @@ -18,7 +18,12 @@ 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; return ValueTask.CompletedTask; From b622e7e4fb7d040d990a87887098fffe1b894109 Mon Sep 17 00:00:00 2001 From: Laura Neto <12862535+lauraneto@users.noreply.github.com> Date: Fri, 1 Aug 2025 11:50:14 +0200 Subject: [PATCH 75/84] Content picker search with start node configured not taking user start nodes into account (#19800) * Fix users being able to see nodes they don't have access to when using the picker search * Readability and naming improvements * Additional fixes * Adjust tests * Additional fixes * Small improvement * Replaced the root ids with constants * Update src/Umbraco.Web.BackOffice/Trees/MemberTreeController.cs Co-authored-by: Andy Butland --------- Co-authored-by: Andy Butland --- .../BackOfficeExamineSearcher.cs | 198 +++++++++++++----- .../Trees/ContentTreeController.cs | 17 +- .../Trees/MediaTreeController.cs | 9 +- .../Trees/MemberTreeController.cs | 8 +- .../BackOfficeExamineSearcherTests.cs | 4 +- 5 files changed, 172 insertions(+), 64 deletions(-) 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.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/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) { From 214f3fbc646c42122795cc9d2d0b899d3f56745b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Corn=C3=A9=20Hoskam?= Date: Mon, 4 Aug 2025 11:40:00 +0200 Subject: [PATCH 76/84] Umbraco Engage UmbracoUrlAlias Fix - Fixes #19654 (#19827) * Fixes #19654 Adds the propertyAlias to the VariationContext so that products implementing the GetSegment method are aware which propertyAlias it's being called for * Re-implement original variation context for backwards compatibility * Fixes hidden overload method Ensures the `GetSegment` method overload is not hidden when a null `propertyAlias` is passed. * Resolve backward compatibility issues. * Improved comments. --------- Co-authored-by: Andy Butland --- .../PublishedValueFallback.cs | 8 ++-- .../PublishedContent/VariationContext.cs | 12 ++++-- .../VariationContextAccessorExtensions.cs | 42 +++++++++++++++---- .../Property.cs | 10 ++--- 4 files changed, 51 insertions(+), 21 deletions(-) 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.PublishedCache.NuCache/Property.cs b/src/Umbraco.PublishedCache.NuCache/Property.cs index 41532d4944c1..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. @@ -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); From 784e09e24081e20d37c81c770dd2bfa531c8d73e Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Wed, 6 Aug 2025 10:34:08 +0200 Subject: [PATCH 77/84] Use a regex to filter our invalid culture codes rather than relying on the culture being installed on the operating system (#19821) * Use a regex to filter our invalid culture codes rather than relying on the culture being installed on the operating system. * Update to more restrictive regex Co-authored-by: Nuklon --------- Co-authored-by: Nuklon --- .../Controllers/PreviewController.cs | 30 ++++++++++------ .../Controllers/PreviewControllerTests.cs | 34 +++++++++++++++++++ 2 files changed, 53 insertions(+), 11 deletions(-) create mode 100644 tests/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Controllers/PreviewControllerTests.cs diff --git a/src/Umbraco.Web.BackOffice/Controllers/PreviewController.cs b/src/Umbraco.Web.BackOffice/Controllers/PreviewController.cs index 3af0628313b7..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) @@ -261,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/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."); + } + } +} From c2890e15c33427df7f8a53161ab506854653dc83 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Thu, 14 Aug 2025 07:39:22 +0100 Subject: [PATCH 78/84] Bumped version to 13.10.0 --- version.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.json b/version.json index 7ab800f4baf0..4c679a19e1de 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.10.0-rc", + "version": "13.10.0", "assemblyVersion": { "precision": "build" }, From 85aa6d3b57b2e93016f984939708a22e758bd5c4 Mon Sep 17 00:00:00 2001 From: Tarik <52907282+wpplumber@users.noreply.github.com> Date: Fri, 15 Aug 2025 10:06:51 +0100 Subject: [PATCH 79/84] Add Arabic (ar) backoffice translation (#19896) * feat: Add Arabic (ar) backoffice translation * Make Arabic language general until having special words for other countries. * Corrected the language header --------- Co-authored-by: Andy Butland --- .../EmbeddedResources/Lang/ar.xml | 3880 +++++++++++++++++ 1 file changed, 3880 insertions(+) create mode 100644 src/Umbraco.Core/EmbeddedResources/Lang/ar.xml 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، ما إذا كان واجهة برمجة التطبيقات للتوصيل ممكّنة، وتسمح بالوصول العام، وما إذا كنت في وضع التصحيح.
  • + +قد نغير ما نرسله على المستوى التفصيلي في المستقبل. إذا قمت باختيار "تفصيلي"، فأنت توافق على جمع المعلومات المجهولة الحالية والمستقبلية.]]>
    + +
    From 45f7b7ad989d1d204be9f47beb21f8403755c3a7 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Mon, 15 Sep 2025 19:53:17 +0200 Subject: [PATCH 80/84] Retain original backoffice location on login after timeout (#19984) Retain original backoffice location on login after timeout. --- src/Umbraco.Web.UI.Login/src/auth.element.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Login/src/auth.element.ts b/src/Umbraco.Web.UI.Login/src/auth.element.ts index cbd23839ffc7..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}${encodeURIComponent(window.location.hash)}`; + if (value) { + umbAuthContext.returnPath = `${value}${encodeURIComponent(window.location.hash)}`; + } else { + umbAuthContext.returnPath = value; + } } get returnPath() { return umbAuthContext.returnPath; From 60f84f737882cc9466e243a610e44a3a30d378d8 Mon Sep 17 00:00:00 2001 From: kjac Date: Mon, 15 Sep 2025 19:54:00 +0200 Subject: [PATCH 81/84] Support querystring and anchor for local links in Delivery API output --- .../Models/DeliveryApi/ApiContentRoute.cs | 2 + .../Models/DeliveryApi/IApiContentRoute.cs | 2 + .../DeliveryApi/ApiRichTextMarkupParser.cs | 2 +- .../DeliveryApi/ApiRichTextParserBase.cs | 4 +- .../DeliveryApi/OpenApiContractTest.cs | 4 ++ .../DeliveryApi/RichTextParserTests.cs | 42 +++++++++++++++++-- 6 files changed, 51 insertions(+), 5 deletions(-) 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..46b6940be6b6 100644 --- a/src/Umbraco.Core/Models/DeliveryApi/IApiContentRoute.cs +++ b/src/Umbraco.Core/Models/DeliveryApi/IApiContentRoute.cs @@ -4,5 +4,7 @@ public interface IApiContentRoute { string Path { get; } + public string? QueryString { get; set; } + IApiContentStartItem StartItem { get; } } 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/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.UnitTests/Umbraco.Core/DeliveryApi/RichTextParserTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/DeliveryApi/RichTextParserTests.cs index f9f3722a0fea..d78fce702de1 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/DeliveryApi/RichTextParserTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/DeliveryApi/RichTextParserTests.cs @@ -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() { @@ -465,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() { @@ -485,6 +519,8 @@ public void ParseMarkup_InvalidLocalLinkYieldsEmptyLink(string href) } [TestCase("

    ")] + [TestCase("

    ")] + [TestCase("

    ")] [TestCase("

    ")] public void ParseMarkup_CanHandleNonLocalReferences(string html) { From a282002c30abac813f8b38b952499a0fe6a90d4f Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Tue, 16 Sep 2025 08:25:22 +0200 Subject: [PATCH 82/84] Support querystring and anchor for local links in Delivery API output (#20142) * Support querystring and anchor for local links in Delivery API output * Add default implementation for backwards compat * Add default implementation for backwards compat (also on the interface) * Fix default implementation --- .../Models/DeliveryApi/ApiContentRoute.cs | 2 + .../Models/DeliveryApi/IApiContentRoute.cs | 5 +++ .../DeliveryApi/ApiRichTextMarkupParser.cs | 2 +- .../DeliveryApi/ApiRichTextParserBase.cs | 4 +- .../DeliveryApi/OpenApiContractTest.cs | 4 ++ .../DeliveryApi/RichTextParserTests.cs | 42 +++++++++++++++++-- 6 files changed, 54 insertions(+), 5 deletions(-) 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.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/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.UnitTests/Umbraco.Core/DeliveryApi/RichTextParserTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/DeliveryApi/RichTextParserTests.cs index f9f3722a0fea..d78fce702de1 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/DeliveryApi/RichTextParserTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/DeliveryApi/RichTextParserTests.cs @@ -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() { @@ -465,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() { @@ -485,6 +519,8 @@ public void ParseMarkup_InvalidLocalLinkYieldsEmptyLink(string href) } [TestCase("

    ")] + [TestCase("

    ")] + [TestCase("

    ")] [TestCase("

    ")] public void ParseMarkup_CanHandleNonLocalReferences(string html) { From e119186e3452746a03266428a5d8ad0d3e2b6b88 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Tue, 16 Sep 2025 11:58:07 +0200 Subject: [PATCH 83/84] Avoid throwing an exception on getting references when migrating content with changed data types (#20079) * Avoid throwing an exception on getting references when migrating content with changed data types. * Revert and handle exception at the consumer side * Clean up --------- Co-authored-by: Kenn Jacobsen --- .../BlockEditorPropertyValueEditor.cs | 45 +++++++++---------- 1 file changed, 22 insertions(+), 23 deletions(-) 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; + } + } } From 301be352ae24d6760d2f0855cf82fdecaf37b4aa Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Wed, 17 Sep 2025 06:51:10 +0200 Subject: [PATCH 84/84] Make notification service able to handle segmented content (#20160) --- .../Services/NotificationService.cs | 82 +++++++++---------- 1 file changed, 38 insertions(+), 44 deletions(-) diff --git a/src/Umbraco.Core/Services/NotificationService.cs b/src/Umbraco.Core/Services/NotificationService.cs index 1c370fe5f0fc..4c7edead6757 100644 --- a/src/Umbraco.Core/Services/NotificationService.cs +++ b/src/Umbraco.Core/Services/NotificationService.cs @@ -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";