From 52c21b0fcaf5b1b2c7c55d7e9d9d61638eceb1e3 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Wed, 20 Mar 2024 13:20:40 +0100 Subject: [PATCH 001/113] Updates JSON schema for Umbraco 10 with latest references for Forms and Deploy (#15918) --- src/JsonSchema/JsonSchema.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/JsonSchema/JsonSchema.csproj b/src/JsonSchema/JsonSchema.csproj index 9ef072ccdf..b5af8ae7c1 100644 --- a/src/JsonSchema/JsonSchema.csproj +++ b/src/JsonSchema/JsonSchema.csproj @@ -13,7 +13,7 @@ - - + + From 6379f2fd35f1b3e0d7c87a94eefd7bd59891324c Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Tue, 16 Apr 2024 08:55:40 +0200 Subject: [PATCH 002/113] Ported over #15928 changes for 13.3 RC (#16023) * Ported over #15928 changes for 13.3 RC * Use GetOrAdd() * Lock dictionary initialization --------- Co-authored-by: Jason Elkin --- .../Property.cs | 185 +++++++++--------- 1 file changed, 91 insertions(+), 94 deletions(-) diff --git a/src/Umbraco.PublishedCache.NuCache/Property.cs b/src/Umbraco.PublishedCache.NuCache/Property.cs index 2892a04f90..596bae2090 100644 --- a/src/Umbraco.PublishedCache.NuCache/Property.cs +++ b/src/Umbraco.PublishedCache.NuCache/Property.cs @@ -1,3 +1,4 @@ +using System.Collections.Concurrent; using System.Xml.Serialization; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Collections; @@ -19,7 +20,6 @@ internal class Property : PublishedPropertyBase private readonly bool _isMember; private readonly bool _isPreviewing; - private readonly object _locko = new(); private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor; // the invariant-neutral source and inter values @@ -33,7 +33,8 @@ internal class Property : PublishedPropertyBase private object? _interValue; // the variant source and inter values - private Dictionary? _sourceValues; + private readonly object _locko = new(); + private ConcurrentDictionary? _sourceValues; private string? _valuesCacheKey; @@ -66,12 +67,9 @@ internal class Property : PublishedPropertyBase } else { - if (_sourceValues == null) - { - _sourceValues = new Dictionary(); - } + EnsureSourceValuesInitialized(); - _sourceValues[new CompositeStringStringKey(sourceValue.Culture, sourceValue.Segment)] + _sourceValues![new CompositeStringStringKey(sourceValue.Culture, sourceValue.Segment)] = new SourceInterValue { Culture = sourceValue.Culture, @@ -125,30 +123,27 @@ internal class Property : PublishedPropertyBase return hasValue.Value; } - lock (_locko) + value = GetInterValue(culture, segment); + hasValue = PropertyType.IsValue(value, PropertyValueLevel.Inter); + if (hasValue.HasValue) { - value = GetInterValue(culture, segment); - hasValue = PropertyType.IsValue(value, PropertyValueLevel.Inter); - if (hasValue.HasValue) - { - return hasValue.Value; - } - - CacheValue cacheValues = GetCacheValues(PropertyType.CacheLevel).For(culture, segment); - - // initial reference cache level always is .Content - const PropertyCacheLevel initialCacheLevel = PropertyCacheLevel.Element; - - if (!cacheValues.ObjectInitialized) - { - cacheValues.ObjectValue = - PropertyType.ConvertInterToObject(_content, initialCacheLevel, value, _isPreviewing); - cacheValues.ObjectInitialized = true; - } - - value = cacheValues.ObjectValue; - return PropertyType.IsValue(value, PropertyValueLevel.Object) ?? false; + return hasValue.Value; } + + CacheValue cacheValues = GetCacheValues(PropertyType.CacheLevel).For(culture, segment); + + // initial reference cache level always is .Content + const PropertyCacheLevel initialCacheLevel = PropertyCacheLevel.Element; + + if (!cacheValues.ObjectInitialized) + { + cacheValues.ObjectValue = + PropertyType.ConvertInterToObject(_content, initialCacheLevel, value, _isPreviewing); + cacheValues.ObjectInitialized = true; + } + + value = cacheValues.ObjectValue; + return PropertyType.IsValue(value, PropertyValueLevel.Object) ?? false; } public override object? GetSourceValue(string? culture = null, string? segment = null) @@ -160,19 +155,16 @@ internal class Property : PublishedPropertyBase return _sourceValue; } - lock (_locko) + if (_sourceValues == null) { - if (_sourceValues == null) - { - return null; - } - - return _sourceValues.TryGetValue( - new CompositeStringStringKey(culture, segment), - out SourceInterValue? sourceValue) - ? sourceValue.SourceValue - : null; + return null; } + + return _sourceValues.TryGetValue( + new CompositeStringStringKey(culture, segment), + out SourceInterValue? sourceValue) + ? sourceValue.SourceValue + : null; } private CacheValues GetCacheValues(PropertyCacheLevel cacheLevel) @@ -227,7 +219,6 @@ internal class Property : PublishedPropertyBase return (CacheValues)cache.Get(ValuesCacheKey, () => new CacheValues())!; } - // this is always invoked from within a lock, so does not require its own lock private object? GetInterValue(string? culture, string? segment) { if (culture == string.Empty && segment == string.Empty) @@ -242,21 +233,17 @@ internal class Property : PublishedPropertyBase return _interValue; } - if (_sourceValues == null) - { - _sourceValues = new Dictionary(); - } + EnsureSourceValuesInitialized(); var k = new CompositeStringStringKey(culture, segment); - if (!_sourceValues.TryGetValue(k, out SourceInterValue? vvalue)) - { - _sourceValues[k] = vvalue = new SourceInterValue + + SourceInterValue vvalue = _sourceValues!.GetOrAdd(k, _ => + new SourceInterValue { Culture = culture, Segment = segment, SourceValue = GetSourceValue(culture, segment), - }; - } + }); if (vvalue.InterInitialized) { @@ -273,23 +260,20 @@ internal class Property : PublishedPropertyBase _content.VariationContextAccessor.ContextualizeVariation(_variations, _content.Id, ref culture, ref segment); object? value; - lock (_locko) + CacheValue cacheValues = GetCacheValues(PropertyType.CacheLevel).For(culture, segment); + + // initial reference cache level always is .Content + const PropertyCacheLevel initialCacheLevel = PropertyCacheLevel.Element; + + if (cacheValues.ObjectInitialized) { - CacheValue cacheValues = GetCacheValues(PropertyType.CacheLevel).For(culture, segment); - - // initial reference cache level always is .Content - const PropertyCacheLevel initialCacheLevel = PropertyCacheLevel.Element; - - if (cacheValues.ObjectInitialized) - { - return cacheValues.ObjectValue; - } - - cacheValues.ObjectValue = PropertyType.ConvertInterToObject(_content, initialCacheLevel, GetInterValue(culture, segment), _isPreviewing); - cacheValues.ObjectInitialized = true; - value = cacheValues.ObjectValue; + return cacheValues.ObjectValue; } + cacheValues.ObjectValue = PropertyType.ConvertInterToObject(_content, initialCacheLevel, GetInterValue(culture, segment), _isPreviewing); + cacheValues.ObjectInitialized = true; + value = cacheValues.ObjectValue; + return value; } @@ -298,22 +282,19 @@ internal class Property : PublishedPropertyBase { _content.VariationContextAccessor.ContextualizeVariation(_variations, _content.Id, ref culture, ref segment); - lock (_locko) + CacheValue cacheValues = GetCacheValues(PropertyType.CacheLevel).For(culture, segment); + + // initial reference cache level always is .Content + const PropertyCacheLevel initialCacheLevel = PropertyCacheLevel.Element; + + if (cacheValues.XPathInitialized) { - CacheValue cacheValues = GetCacheValues(PropertyType.CacheLevel).For(culture, segment); - - // initial reference cache level always is .Content - const PropertyCacheLevel initialCacheLevel = PropertyCacheLevel.Element; - - if (cacheValues.XPathInitialized) - { - return cacheValues.XPathValue; - } - - cacheValues.XPathValue = PropertyType.ConvertInterToXPath(_content, initialCacheLevel, GetInterValue(culture, segment), _isPreviewing); - cacheValues.XPathInitialized = true; return cacheValues.XPathValue; } + + cacheValues.XPathValue = PropertyType.ConvertInterToXPath(_content, initialCacheLevel, GetInterValue(culture, segment), _isPreviewing); + cacheValues.XPathInitialized = true; + return cacheValues.XPathValue; } public override object? GetDeliveryApiValue(bool expanding, string? culture = null, string? segment = null) @@ -321,18 +302,16 @@ internal class Property : PublishedPropertyBase _content.VariationContextAccessor.ContextualizeVariation(_variations, _content.Id, ref culture, ref segment); object? value; - lock (_locko) - { - CacheValue cacheValues = GetCacheValues(expanding ? PropertyType.DeliveryApiCacheLevelForExpansion : PropertyType.DeliveryApiCacheLevel).For(culture, segment); + CacheValue cacheValues = GetCacheValues(expanding ? PropertyType.DeliveryApiCacheLevelForExpansion : PropertyType.DeliveryApiCacheLevel).For(culture, segment); - // initial reference cache level always is .Content - const PropertyCacheLevel initialCacheLevel = PropertyCacheLevel.Element; - object? GetDeliveryApiObject() => PropertyType.ConvertInterToDeliveryApiObject(_content, initialCacheLevel, GetInterValue(culture, segment), _isPreviewing, expanding); - value = expanding - ? GetDeliveryApiExpandedObject(cacheValues, GetDeliveryApiObject) - : GetDeliveryApiDefaultObject(cacheValues, GetDeliveryApiObject); - } + // initial reference cache level always is .Content + const PropertyCacheLevel initialCacheLevel = PropertyCacheLevel.Element; + + object? GetDeliveryApiObject() => PropertyType.ConvertInterToDeliveryApiObject(_content, initialCacheLevel, GetInterValue(culture, segment), _isPreviewing, expanding); + value = expanding + ? GetDeliveryApiExpandedObject(cacheValues, GetDeliveryApiObject) + : GetDeliveryApiDefaultObject(cacheValues, GetDeliveryApiObject); return value; } @@ -382,9 +361,9 @@ internal class Property : PublishedPropertyBase private class CacheValues : CacheValue { - private Dictionary? _values; + private readonly object _locko = new(); + private ConcurrentDictionary? _values; - // this is always invoked from within a lock, so does not require its own lock public CacheValue For(string? culture, string? segment) { if (culture == string.Empty && segment == string.Empty) @@ -394,14 +373,15 @@ internal class Property : PublishedPropertyBase if (_values == null) { - _values = new Dictionary(); + lock (_locko) + { + _values ??= InitializeConcurrentDictionary(); + } } var k = new CompositeStringStringKey(culture, segment); - if (!_values.TryGetValue(k, out CacheValue? value)) - { - _values[k] = value = new CacheValue(); - } + + CacheValue value = _values.GetOrAdd(k, _ => new CacheValue()); return value; } @@ -431,5 +411,22 @@ internal class Property : PublishedPropertyBase public object? InterValue { get; set; } } + private static ConcurrentDictionary InitializeConcurrentDictionary() + where TKey : notnull + => new(-1, 5); + + private void EnsureSourceValuesInitialized() + { + if (_sourceValues is not null) + { + return; + } + + lock (_locko) + { + _sourceValues ??= InitializeConcurrentDictionary(); + } + } + #endregion } From a6a76d1815f30f5bae569f42076a95ef5d6aa6c4 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Tue, 16 Apr 2024 12:03:44 +0200 Subject: [PATCH 003/113] Make the API content response builder extendable (#16056) * Make the API content response builder extendable * DeliveryApiJsonTypeResolver needs to be extendable too --- .../Json/DeliveryApiJsonTypeResolver.cs | 35 +++++++++++++------ .../DeliveryApi/ApiContentResponseBuilder.cs | 11 ++++-- 2 files changed, 32 insertions(+), 14 deletions(-) diff --git a/src/Umbraco.Cms.Api.Delivery/Json/DeliveryApiJsonTypeResolver.cs b/src/Umbraco.Cms.Api.Delivery/Json/DeliveryApiJsonTypeResolver.cs index 10f052485a..b22e7c9341 100644 --- a/src/Umbraco.Cms.Api.Delivery/Json/DeliveryApiJsonTypeResolver.cs +++ b/src/Umbraco.Cms.Api.Delivery/Json/DeliveryApiJsonTypeResolver.cs @@ -12,23 +12,36 @@ public class DeliveryApiJsonTypeResolver : DefaultJsonTypeInfoResolver { JsonTypeInfo jsonTypeInfo = base.GetTypeInfo(type, options); - if (jsonTypeInfo.Type == typeof(IApiContent)) + Type[] derivedTypes = GetDerivedTypes(jsonTypeInfo); + if (derivedTypes.Length > 0) { - ConfigureJsonPolymorphismOptions(jsonTypeInfo, typeof(ApiContent)); - } - else if (jsonTypeInfo.Type == typeof(IApiContentResponse)) - { - ConfigureJsonPolymorphismOptions(jsonTypeInfo, typeof(ApiContentResponse)); - } - else if (jsonTypeInfo.Type == typeof(IRichTextElement)) - { - ConfigureJsonPolymorphismOptions(jsonTypeInfo, typeof(RichTextRootElement), typeof(RichTextGenericElement), typeof(RichTextTextElement)); + ConfigureJsonPolymorphismOptions(jsonTypeInfo, derivedTypes); } return jsonTypeInfo; } - private void ConfigureJsonPolymorphismOptions(JsonTypeInfo jsonTypeInfo, params Type[] derivedTypes) + protected virtual Type[] GetDerivedTypes(JsonTypeInfo jsonTypeInfo) + { + if (jsonTypeInfo.Type == typeof(IApiContent)) + { + return new[] { typeof(ApiContent) }; + } + + if (jsonTypeInfo.Type == typeof(IApiContentResponse)) + { + return new[] { typeof(ApiContentResponse) }; + } + + if (jsonTypeInfo.Type == typeof(IRichTextElement)) + { + return new[] { typeof(RichTextRootElement), typeof(RichTextGenericElement), typeof(RichTextTextElement) }; + } + + return Array.Empty(); + } + + protected void ConfigureJsonPolymorphismOptions(JsonTypeInfo jsonTypeInfo, params Type[] derivedTypes) { jsonTypeInfo.PolymorphismOptions = new JsonPolymorphismOptions { diff --git a/src/Umbraco.Core/DeliveryApi/ApiContentResponseBuilder.cs b/src/Umbraco.Core/DeliveryApi/ApiContentResponseBuilder.cs index a551115a1e..68bb01c012 100644 --- a/src/Umbraco.Core/DeliveryApi/ApiContentResponseBuilder.cs +++ b/src/Umbraco.Core/DeliveryApi/ApiContentResponseBuilder.cs @@ -1,11 +1,10 @@ using Umbraco.Cms.Core.Models.DeliveryApi; using Umbraco.Cms.Core.Models.PublishedContent; -using Umbraco.Cms.Core.Routing; using Umbraco.Extensions; namespace Umbraco.Cms.Core.DeliveryApi; -public sealed class ApiContentResponseBuilder : ApiContentBuilderBase, IApiContentResponseBuilder +public class ApiContentResponseBuilder : ApiContentBuilderBase, IApiContentResponseBuilder { private readonly IApiContentRouteBuilder _apiContentRouteBuilder; @@ -14,6 +13,12 @@ public sealed class ApiContentResponseBuilder : ApiContentBuilderBase _apiContentRouteBuilder = apiContentRouteBuilder; protected override IApiContentResponse Create(IPublishedContent content, string name, IApiContentRoute route, IDictionary properties) + { + IDictionary cultures = GetCultures(content); + return new ApiContentResponse(content.Key, name, content.ContentType.Alias, content.CreateDate, content.UpdateDate, route, properties, cultures); + } + + protected virtual IDictionary GetCultures(IPublishedContent content) { var routesByCulture = new Dictionary(); @@ -35,6 +40,6 @@ public sealed class ApiContentResponseBuilder : ApiContentBuilderBase Date: Thu, 18 Apr 2024 09:10:22 +0200 Subject: [PATCH 004/113] bump rc to regular --- version.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.json b/version.json index f70819e8b4..6ab0f37af9 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.3.0-rc", + "version": "13.3.0", "assemblyVersion": { "precision": "build" }, From a27a4dcd84f84910888578d66f605b52247db2e6 Mon Sep 17 00:00:00 2001 From: Sven Geusens Date: Thu, 18 Apr 2024 15:36:44 +0200 Subject: [PATCH 005/113] Bump to next minor --- version.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.json b/version.json index 6ab0f37af9..b9fa941e03 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.3.0", + "version": "13.4.0", "assemblyVersion": { "precision": "build" }, From 09803501722d17b34f7d991f43cb5479655e22f8 Mon Sep 17 00:00:00 2001 From: Mole Date: Mon, 22 Apr 2024 12:56:36 +0200 Subject: [PATCH 006/113] Add blocks in RTE telemetry (#16104) * Add blocks telemetry * Use constants and update tests * V13: Add property type information to telemetry (#16109) * Add property type counts to telemetry * Use constants and fix tests * Update description --- src/Umbraco.Core/Constants-Telemetry.cs | 5 +++ .../EmbeddedResources/Lang/en.xml | 2 +- .../EmbeddedResources/Lang/en_us.xml | 2 +- .../UmbracoBuilder.TelemetryProviders.cs | 1 + .../BlocksInRichTextTelemetryProvider.cs | 42 +++++++++++++++++++ .../PropertyEditorTelemetryProvider.cs | 8 ++++ .../Telemetry/TelemetryServiceTests.cs | 5 +++ 7 files changed, 63 insertions(+), 2 deletions(-) create mode 100644 src/Umbraco.Infrastructure/Telemetry/Providers/BlocksInRichTextTelemetryProvider.cs diff --git a/src/Umbraco.Core/Constants-Telemetry.cs b/src/Umbraco.Core/Constants-Telemetry.cs index b5c1e15c94..4ea1182226 100644 --- a/src/Umbraco.Core/Constants-Telemetry.cs +++ b/src/Umbraco.Core/Constants-Telemetry.cs @@ -36,5 +36,10 @@ public static partial class Constants public static string WebhookTotal = $"{WebhookPrefix}Total"; public static string WebhookCustomHeaders = $"{WebhookPrefix}CustomHeaders"; public static string WebhookCustomEvent = $"{WebhookPrefix}CustomEvent"; + public static string RichTextEditorCount = "RichTextEditorCount"; + public static string RichTextBlockCount = "RichTextBlockCount"; + public static string TotalPropertyCount = "TotalPropertyCount"; + public static string HighestPropertyCount = "HighestPropertyCount"; + public static string TotalCompositions = "TotalCompositions"; } } diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/en.xml b/src/Umbraco.Core/EmbeddedResources/Lang/en.xml index 5559768c67..4995967993 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/en.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/en.xml @@ -3077,7 +3077,7 @@ To manage your website, simply open the Umbraco backoffice and start adding cont
  • Anonymized site ID, Umbraco version, and packages installed.
  • -
  • Number of: Root nodes, Content nodes, Macros, Media, Document Types, Templates, Languages, Domains, User Group, Users, Members, Backoffice external login providers, Webhooks, and Property Editors in use.
  • +
  • Number of: Root nodes, Content nodes, Macros, Media, Document Types, Property Types, Compositions, Templates, Languages, Domains, User Group, Users, Members, Backoffice external login providers, Webhooks, rich text datatypes, blocks used in rich text datatypes, and Property Editors in use.
  • System information: Webserver, server OS, server framework, server OS language, and database provider.
  • Configuration settings: Modelsbuilder mode, if custom Umbraco path exists, ASP environment, whether the delivery API is enabled, and allows public access, and if you are in debug mode.
  • diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml b/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml index 35dfc458ce..39b20009f4 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml @@ -3096,7 +3096,7 @@ To manage your website, simply open the Umbraco backoffice and start adding cont We will send:
    • Anonymized site ID, Umbraco version, and packages installed.
    • -
    • Number of: Root nodes, Content nodes, Macros, Media, Document Types, Templates, Languages, Domains, User Group, Users, Members, Backoffice external login providers, Webhooks, and Property Editors in use.
    • +
    • Number of: Root nodes, Content nodes, Macros, Media, Document Types, Property Types, Compositions, Templates, Languages, Domains, User Group, Users, Members, Backoffice external login providers, Webhooks, rich text datatypes, blocks used in rich text datatypes, and Property Editors in use.
    • System information: Webserver, server OS, server framework, server OS language, and database provider.
    • Configuration settings: Modelsbuilder mode, if custom Umbraco path exists, ASP environment, whether the delivery API is enabled, and allows public access, and if you are in debug mode.
    diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.TelemetryProviders.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.TelemetryProviders.cs index 69ee653c19..e18a8fbdf8 100644 --- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.TelemetryProviders.cs +++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.TelemetryProviders.cs @@ -21,6 +21,7 @@ public static class UmbracoBuilder_TelemetryProviders builder.Services.AddTransient(); builder.Services.AddTransient(); builder.Services.AddTransient(); + builder.Services.AddTransient(); return builder; } } diff --git a/src/Umbraco.Infrastructure/Telemetry/Providers/BlocksInRichTextTelemetryProvider.cs b/src/Umbraco.Infrastructure/Telemetry/Providers/BlocksInRichTextTelemetryProvider.cs new file mode 100644 index 0000000000..2af4a7f6ad --- /dev/null +++ b/src/Umbraco.Infrastructure/Telemetry/Providers/BlocksInRichTextTelemetryProvider.cs @@ -0,0 +1,42 @@ +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Infrastructure.Telemetry.Interfaces; + +namespace Umbraco.Cms.Infrastructure.Telemetry.Providers; + +public class BlocksInRichTextTelemetryProvider : IDetailedTelemetryProvider +{ + private readonly IDataTypeService _dataTypeService; + + public BlocksInRichTextTelemetryProvider(IDataTypeService dataTypeService) + { + _dataTypeService = dataTypeService; + } + + public IEnumerable GetInformation() + { + IEnumerable richTextDataTypes = _dataTypeService.GetByEditorAlias(Constants.PropertyEditors.Aliases.TinyMce).ToArray(); + int registeredBlocks = 0; + yield return new UsageInformation(Constants.Telemetry.RichTextEditorCount, richTextDataTypes.Count()); + + foreach (IDataType richTextDataType in richTextDataTypes) + { + if (richTextDataType.Configuration is not RichTextConfiguration richTextConfiguration) + { + // Might be some custom data type, skip it + continue; + } + + if (richTextConfiguration.Blocks is null) + { + continue; + } + + registeredBlocks += richTextConfiguration.Blocks.Length; + } + + yield return new UsageInformation(Constants.Telemetry.RichTextBlockCount, registeredBlocks); + } +} diff --git a/src/Umbraco.Infrastructure/Telemetry/Providers/PropertyEditorTelemetryProvider.cs b/src/Umbraco.Infrastructure/Telemetry/Providers/PropertyEditorTelemetryProvider.cs index 1c8e0af1ab..8bd735ec1c 100644 --- a/src/Umbraco.Infrastructure/Telemetry/Providers/PropertyEditorTelemetryProvider.cs +++ b/src/Umbraco.Infrastructure/Telemetry/Providers/PropertyEditorTelemetryProvider.cs @@ -16,11 +16,19 @@ public class PropertyEditorTelemetryProvider : IDetailedTelemetryProvider { IEnumerable contentTypes = _contentTypeService.GetAll(); var propertyTypes = new HashSet(); + var propertyTypeCounts = new List(); + var totalCompositions = 0; + foreach (IContentType contentType in contentTypes) { propertyTypes.UnionWith(contentType.PropertyTypes.Select(x => x.PropertyEditorAlias)); + propertyTypeCounts.Add(contentType.CompositionPropertyTypes.Count()); + totalCompositions += contentType.CompositionAliases().Count(); } yield return new UsageInformation(Constants.Telemetry.Properties, propertyTypes); + yield return new UsageInformation(Constants.Telemetry.TotalPropertyCount, propertyTypeCounts.Sum()); + yield return new UsageInformation(Constants.Telemetry.HighestPropertyCount, propertyTypeCounts.Count > 0 ? propertyTypeCounts.Max() : 0); + yield return new UsageInformation(Constants.Telemetry.TotalCompositions, totalCompositions); } } diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Core/Telemetry/TelemetryServiceTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Core/Telemetry/TelemetryServiceTests.cs index e5691c15cb..67b2f63c19 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Core/Telemetry/TelemetryServiceTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Core/Telemetry/TelemetryServiceTests.cs @@ -58,6 +58,11 @@ public class TelemetryServiceTests : UmbracoIntegrationTest Constants.Telemetry.WebhookTotal, Constants.Telemetry.WebhookCustomHeaders, Constants.Telemetry.WebhookCustomEvent, + Constants.Telemetry.RichTextEditorCount, + Constants.Telemetry.RichTextBlockCount, + Constants.Telemetry.TotalPropertyCount, + Constants.Telemetry.HighestPropertyCount, + Constants.Telemetry.TotalCompositions, }; // Add the default webhook events. From 119fde2033e7c72d7d5e16cbc7a5539ec38fb0d0 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Wed, 24 Apr 2024 13:44:20 +0200 Subject: [PATCH 007/113] V10: Fix for fallback file upload (#14892) (#15868) * Fix for fallback file upload (#14892) * Added check for file type * Removed unneeded null checks and fixed tabs * Cleaning * Cleanups, cleanups, and removal of unneeded null checks * Reverted removal of relationshipservice * Revert null check removals (too risky) --------- Co-authored-by: Ambert van Unen Co-authored-by: Laura Neto <12862535+lauraneto@users.noreply.github.com> (cherry picked from commit 0b5d1f8aa60ca92f63d68e21cc1787a379e33895) * Fix up formatting --------- Co-authored-by: Ambert van Unen --- .../Controllers/MediaController.cs | 147 ++++++++++-------- 1 file changed, 82 insertions(+), 65 deletions(-) diff --git a/src/Umbraco.Web.BackOffice/Controllers/MediaController.cs b/src/Umbraco.Web.BackOffice/Controllers/MediaController.cs index 807061e5aa..c81cd9cdb4 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/MediaController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/MediaController.cs @@ -189,7 +189,7 @@ public class MediaController : ContentControllerBase if (mapped is not null) { - //remove the listview app if it exists + // remove the listview app if it exists mapped.ContentApps = mapped.ContentApps.Where(x => x.Alias != "umbListView").ToList(); } @@ -205,7 +205,7 @@ public class MediaController : ContentControllerBase var apps = new List { ListViewContentAppFactory.CreateContentApp(_dataTypeService, _propertyEditors, "recycleBin", "media", - Constants.DataTypes.DefaultMediaListView) + Constants.DataTypes.DefaultMediaListView) }; apps[0].Active = true; var display = new MediaItemDisplay @@ -238,7 +238,8 @@ public class MediaController : ContentControllerBase if (foundMedia == null) { HandleContentNotFound(id); - //HandleContentNotFound will throw an exception + + // HandleContentNotFound will throw an exception return null; } @@ -306,8 +307,8 @@ public class MediaController : ContentControllerBase public PagedResult> GetChildFolders(int id, int pageNumber = 1, int pageSize = 1000) { - //Suggested convention for folder mediatypes - we can make this more or less complicated as long as we document it... - //if you create a media type, which has an alias that ends with ...Folder then its a folder: ex: "secureFolder", "bannerFolder", "Folder" + // Suggested convention for folder mediatypes - we can make this more or less complicated as long as we document it... + // if you create a media type, which has an alias that ends with ...Folder then its a folder: ex: "secureFolder", "bannerFolder", "Folder" var folderTypes = _mediaTypeService .GetAll() .Where(x => x.Alias.EndsWith("Folder")) @@ -320,7 +321,8 @@ public class MediaController : ContentControllerBase } IEnumerable children = _mediaService.GetPagedChildren(id, pageNumber - 1, pageSize, out long total, - //lookup these content types + + // lookup these content types _sqlContext.Query().Where(x => folderTypes.Contains(x.ContentTypeId)), Ordering.By("Name")); @@ -336,6 +338,7 @@ public class MediaController : ContentControllerBase /// [FilterAllowedOutgoingMedia(typeof(IEnumerable>))] public IEnumerable> GetRootMedia() => + // TODO: Add permissions check! _mediaService.GetRootMedia()? .Select(_umbracoMapper.Map>).WhereNotNull() ?? @@ -357,7 +360,7 @@ public class MediaController : ContentControllerBase return HandleContentNotFound(id); } - //if the current item is in the recycle bin + // if the current item is in the recycle bin if (foundMedia.Trashed == false) { Attempt moveResult = _mediaService.MoveToRecycleBin(foundMedia, @@ -389,8 +392,10 @@ public class MediaController : ContentControllerBase { // Authorize... var requirement = new MediaPermissionsResourceRequirement(); - AuthorizationResult authorizationResult = await _authorizationService.AuthorizeAsync(User, - new MediaPermissionsResource(_mediaService.GetById(move.Id)), requirement); + AuthorizationResult authorizationResult = await _authorizationService.AuthorizeAsync( + User, + new MediaPermissionsResource(_mediaService.GetById(move.Id)), + requirement); if (!authorizationResult.Succeeded) { return Forbid(); @@ -403,18 +408,20 @@ public class MediaController : ContentControllerBase return convertToActionResult.Convert(); } - var destinationParentID = move.ParentId; - var sourceParentID = toMove?.ParentId; + var destinationParentId = move.ParentId; + var sourceParentId = toMove?.ParentId; var moveResult = toMove is null ? false : _mediaService.Move(toMove, move.ParentId, _backofficeSecurityAccessor.BackOfficeSecurity?.GetUserId().Result ?? -1); - if (sourceParentID == destinationParentID) + if (sourceParentId == destinationParentId) { - return ValidationProblem(new SimpleNotificationModel(new BackOfficeNotification("", - _localizedTextService.Localize("media", "moveToSameFolderFailed"), NotificationStyle.Error))); + return ValidationProblem(new SimpleNotificationModel(new BackOfficeNotification( + string.Empty, + _localizedTextService.Localize("media", "moveToSameFolderFailed"), + NotificationStyle.Error))); } if (moveResult == false) @@ -435,9 +442,9 @@ public class MediaController : ContentControllerBase public ActionResult? PostSave( [ModelBinder(typeof(MediaItemBinder))] MediaItemSave contentItem) { - //Recent versions of IE/Edge may send in the full client side file path instead of just the file name. - //To ensure similar behavior across all browsers no matter what they do - we strip the FileName property of all - //uploaded files to being *only* the actual file name (as it should be). + // Recent versions of IE/Edge may send in the full client side file path instead of just the file name. + // To ensure similar behavior across all browsers no matter what they do - we strip the FileName property of all + // uploaded files to being *only* the actual file name (as it should be). if (contentItem.UploadedFiles != null && contentItem.UploadedFiles.Any()) { foreach (ContentPropertyFile file in contentItem.UploadedFiles) @@ -446,14 +453,14 @@ public class MediaController : ContentControllerBase } } - //If we've reached here it means: + // If we've reached here it means: // * Our model has been bound // * and validated // * any file attachments have been saved to their temporary location for us to use // * we have a reference to the DTO object and the persisted object // * Permissions are valid - //Don't update the name if it is empty + // Don't update the name if it is empty if (contentItem.Name.IsNullOrWhiteSpace() == false && contentItem.PersistedContent is not null) { contentItem.PersistedContent.Name = contentItem.Name; @@ -466,14 +473,14 @@ public class MediaController : ContentControllerBase (save, property, v) => property?.SetValue(v), //set prop val null); // media are all invariant - //we will continue to save if model state is invalid, however we cannot save if critical data is missing. - //TODO: Allowing media to be saved when it is invalid is odd - media doesn't have a publish phase so suddenly invalid data is allowed to be 'live' + // we will continue to save if model state is invalid, however we cannot save if critical data is missing. + // TODO: Allowing media to be saved when it is invalid is odd - media doesn't have a publish phase so suddenly invalid data is allowed to be 'live' if (!ModelState.IsValid) { - //check for critical data validation issues, we can't continue saving if this data is invalid + // check for critical data validation issues, we can't continue saving if this data is invalid if (!RequiredForPersistenceAttribute.HasRequiredValuesForPersistence(contentItem)) { - //ok, so the absolute mandatory data is invalid and it's new, we cannot actually continue! + // ok, so the absolute mandatory data is invalid and it's new, we cannot actually continue! // add the model state to the outgoing object and throw validation response MediaItemDisplay? forDisplay = _umbracoMapper.Map(contentItem.PersistedContent); return ValidationProblem(forDisplay, ModelState); @@ -485,20 +492,20 @@ public class MediaController : ContentControllerBase return null; } - //save the item + // save the item Attempt saveStatus = _mediaService.Save(contentItem.PersistedContent, _backofficeSecurityAccessor.BackOfficeSecurity?.GetUserId().Result ?? -1); - //return the updated model + // return the updated model MediaItemDisplay? display = _umbracoMapper.Map(contentItem.PersistedContent); - //lastly, if it is not valid, add the model state to the outgoing object and throw a 403 + // lastly, if it is not valid, add the model state to the outgoing object and throw a 403 if (!ModelState.IsValid) { return ValidationProblem(display, ModelState, StatusCodes.Status403Forbidden); } - //put the correct msgs in + // put the correct msgs in switch (contentItem.Action) { case ContentSaveAction.Save: @@ -513,7 +520,7 @@ public class MediaController : ContentControllerBase { AddCancelMessage(display); - //If the item is new and the operation was cancelled, we need to return a different + // If the item is new and the operation was cancelled, we need to return a different // status code so the UI can handle it since it won't be able to redirect since there // is no Id to redirect to! if (saveStatus.Result?.Result == OperationResultType.FailedCancelledByEvent && @@ -554,7 +561,7 @@ public class MediaController : ContentControllerBase return NotFound(); } - //if there's nothing to sort just return ok + // if there's nothing to sort just return ok if (sorted.IdSortOrder?.Length == 0) { return Ok(); @@ -595,7 +602,7 @@ public class MediaController : ContentControllerBase public async Task> PostAddFolder(PostedFolder folder) { ActionResult? parentIdResult = await GetParentIdAsIntAsync(folder.ParentId, true); - if (!(parentIdResult?.Result is null)) + if (parentIdResult?.Result is not null) { return new ActionResult(parentIdResult.Result); } @@ -632,15 +639,15 @@ public class MediaController : ContentControllerBase //ensure it exists Directory.CreateDirectory(root); - //must have a file + // must have a file if (file is null || file.Count == 0) { return NotFound("No file was uploaded"); } - //get the string json from the request + // get the string json from the request ActionResult? parentIdResult = await GetParentIdAsIntAsync(currentFolder, true); - if (!(parentIdResult?.Result is null)) + if (parentIdResult?.Result is not null) { return parentIdResult.Result; } @@ -653,7 +660,7 @@ public class MediaController : ContentControllerBase var tempFiles = new PostedFiles(); - //in case we pass a path with a folder in it, we will create it and upload media to it. + // in case we pass a path with a folder in it, we will create it and upload media to it. if (!string.IsNullOrEmpty(path)) { if (!IsFolderCreationAllowedHere(parentId.Value)) @@ -669,16 +676,16 @@ public class MediaController : ContentControllerBase var folderName = folders[i]; IMedia? folderMediaItem; - //if uploading directly to media root and not a subfolder + // if uploading directly to media root and not a subfolder if (parentId == Constants.System.Root) { - //look for matching folder + // look for matching folder folderMediaItem = _mediaService.GetRootMedia()?.FirstOrDefault(x => x.Name == folderName && x.ContentType.Alias == Constants.Conventions.MediaTypes.Folder); if (folderMediaItem == null) { - //if null, create a folder + // if null, create a folder folderMediaItem = _mediaService.CreateMedia(folderName, -1, Constants.Conventions.MediaTypes.Folder); _mediaService.Save(folderMediaItem); @@ -686,10 +693,10 @@ public class MediaController : ContentControllerBase } else { - //get current parent + // get current parent IMedia? mediaRoot = _mediaService.GetById(parentId.Value); - //if the media root is null, something went wrong, we'll abort + // if the media root is null, something went wrong, we'll abort if (mediaRoot == null) { return Problem( @@ -697,7 +704,7 @@ public class MediaController : ContentControllerBase " returned null"); } - //look for matching folder + // look for matching folder folderMediaItem = FindInChildren(mediaRoot.Id, folderName, Constants.Conventions.MediaTypes.Folder); if (folderMediaItem == null) @@ -709,7 +716,7 @@ public class MediaController : ContentControllerBase } } - //set the media root to the folder id so uploaded files will end there. + // set the media root to the folder id so uploaded files will end there. parentId = folderMediaItem.Id; } } @@ -749,7 +756,7 @@ public class MediaController : ContentControllerBase } } - //Only set the permission-based mediaType if we only allow 1 specific file under this parent. + // Only set the permission-based mediaType if we only allow 1 specific file under this parent. if (allowedContentTypes.Count == 1 && mediaTypeItem != null) { mediaTypeAlias = mediaTypeItem.Alias; @@ -762,7 +769,7 @@ public class MediaController : ContentControllerBase allowedContentTypes.UnionWith(typesAllowedAtRoot); } - //get the files + // get the files foreach (IFormFile formFile in file) { var fileName = formFile.FileName.Trim(Constants.CharArrays.DoubleQuote).TrimEnd(); @@ -821,6 +828,11 @@ public class MediaController : ContentControllerBase continue; } + if (allowedContentTypes.Any(x => x.Alias == mediaTypeItem.Alias) == false) + { + continue; + } + mediaTypeAlias = mediaTypeItem.Alias; break; } @@ -866,8 +878,8 @@ public class MediaController : ContentControllerBase IMedia createdMediaItem = _mediaService.CreateMedia(mediaItemName, parentId.Value, mediaTypeAlias, _backofficeSecurityAccessor.BackOfficeSecurity?.CurrentUser?.Id ?? -1); - createdMediaItem.SetValue(_mediaFileManager, _mediaUrlGenerators, _shortStringHelper, - _contentTypeBaseServiceProvider, Constants.Conventions.Media.File, fileName, stream); + createdMediaItem.SetValue(_mediaFileManager, _mediaUrlGenerators, _shortStringHelper, + _contentTypeBaseServiceProvider, Constants.Conventions.Media.File, fileName, stream); Attempt saveResult = _mediaService.Save(createdMediaItem, _backofficeSecurityAccessor.BackOfficeSecurity?.CurrentUser?.Id ?? -1); @@ -878,13 +890,13 @@ public class MediaController : ContentControllerBase } } - //Different response if this is a 'blueimp' request + // Different response if this is a 'blueimp' request if (HttpContext.Request.Query.Any(x => x.Key == "origin")) { KeyValuePair origin = HttpContext.Request.Query.First(x => x.Key == "origin"); if (origin.Value == "blueimp") { - return new JsonResult(tempFiles); //Don't output the angular xsrf stuff, blue imp doesn't like that + return new JsonResult(tempFiles); // Don't output the angular xsrf stuff, blue imp doesn't like that } } @@ -923,7 +935,11 @@ public class MediaController : ContentControllerBase var total = long.MaxValue; while (page * pageSize < total) { - IEnumerable children = _mediaService.GetPagedChildren(mediaId, page++, pageSize, out total, + IEnumerable children = _mediaService.GetPagedChildren( + mediaId, + page++, + pageSize, + out total, _sqlContext.Query().Where(x => x.Name == nameToFind)); IMedia? match = children.FirstOrDefault(c => c.ContentType.Alias == contentTypeAlias); if (match != null) @@ -946,14 +962,13 @@ public class MediaController : ContentControllerBase /// private async Task?> GetParentIdAsIntAsync(string? parentId, bool validatePermissions) { - // test for udi if (UdiParser.TryParse(parentId, out GuidUdi? parentUdi)) { parentId = parentUdi?.Guid.ToString(); } - //if it's not an INT then we'll check for GUID + // if it's not an INT then we'll check for GUID if (int.TryParse(parentId, NumberStyles.Integer, CultureInfo.InvariantCulture, out int intParentId) == false) { // if a guid then try to look up the entity @@ -977,7 +992,7 @@ public class MediaController : ContentControllerBase } // Authorize... - //ensure the user has access to this folder by parent id! + // ensure the user has access to this folder by parent id! if (validatePermissions) { var requirement = new MediaPermissionsResourceRequirement(); @@ -1018,14 +1033,14 @@ public class MediaController : ContentControllerBase if (model.ParentId < 0) { - //cannot move if the content item is not allowed at the root unless there are - //none allowed at root (in which case all should be allowed at root) + // cannot move if the content item is not allowed at the root unless there are + // none allowed at root (in which case all should be allowed at root) IMediaTypeService mediaTypeService = _mediaTypeService; if (toMove.ContentType.AllowedAsRoot == false && mediaTypeService.GetAll().Any(ct => ct.AllowedAsRoot)) { var notificationModel = new SimpleNotificationModel(); notificationModel.AddErrorNotification(_localizedTextService.Localize("moveOrCopy", "notAllowedAtRoot"), - ""); + string.Empty); return ValidationProblem(notificationModel); } } @@ -1037,7 +1052,7 @@ public class MediaController : ContentControllerBase return NotFound(); } - //check if the item is allowed under this one + // check if the item is allowed under this one IMediaType? parentContentType = _mediaTypeService.Get(parent.ContentTypeId); if (parentContentType?.AllowedContentTypes?.Select(x => x.Id).ToArray() .Any(x => x.Value == toMove.ContentType.Id) == false) @@ -1049,12 +1064,12 @@ public class MediaController : ContentControllerBase } // Check on paths - if (string.Format(",{0},", parent.Path) - .IndexOf(string.Format(",{0},", toMove.Id), StringComparison.Ordinal) > -1) + if ($",{parent.Path}," + .IndexOf($",{toMove.Id},", StringComparison.Ordinal) > -1) { var notificationModel = new SimpleNotificationModel(); notificationModel.AddErrorNotification(_localizedTextService.Localize("moveOrCopy", "notAllowedByPath"), - ""); + string.Empty); return ValidationProblem(notificationModel); } } @@ -1110,7 +1125,8 @@ public class MediaController : ContentControllerBase /// Returns the child media objects - using the entity INT id /// [FilterAllowedOutgoingMedia(typeof(IEnumerable>), "Items")] - public PagedResult> GetChildren(int id, + public PagedResult> GetChildren( + int id, int pageNumber = 0, int pageSize = 0, string orderBy = "SortOrder", @@ -1118,7 +1134,7 @@ public class MediaController : ContentControllerBase bool orderBySystemField = true, string filter = "") { - //if a request is made for the root node data but the user's start node is not the default, then + // if a request is made for the root node data but the user's start node is not the default, then // we need to return their start nodes if (id == Constants.System.Root && UserStartNodes.Length > 0 && UserStartNodes.Contains(Constants.System.Root) == false) @@ -1148,7 +1164,6 @@ public class MediaController : ContentControllerBase } // else proceed as usual - long totalChildren; List children; if (pageNumber > 0 && pageSize > 0) @@ -1156,7 +1171,7 @@ public class MediaController : ContentControllerBase IQuery? queryFilter = null; if (filter.IsNullOrWhiteSpace() == false) { - //add the default text filter + // add the default text filter queryFilter = _sqlContext.Query() .Where(x => x.Name != null) .Where(x => x.Name!.Contains(filter)); @@ -1164,14 +1179,16 @@ public class MediaController : ContentControllerBase children = _mediaService .GetPagedChildren( - id, pageNumber - 1, pageSize, + id, + pageNumber - 1, + pageSize, out totalChildren, queryFilter, Ordering.By(orderBy, orderDirection, isCustomField: !orderBySystemField)).ToList(); } else { - //better to not use this without paging where possible, currently only the sort dialog does + // better to not use this without paging where possible, currently only the sort dialog does children = _mediaService.GetPagedChildren(id, 0, int.MaxValue, out var total).ToList(); totalChildren = children.Count; } @@ -1184,7 +1201,7 @@ public class MediaController : ContentControllerBase var pagedResult = new PagedResult>(totalChildren, pageNumber, pageSize) { Items = children - .Select(_umbracoMapper.Map>).WhereNotNull() + .Select(_umbracoMapper.Map>).WhereNotNull() }; return pagedResult; From cae106bfe8fa11c080fc90ba4354a0791e47f9a2 Mon Sep 17 00:00:00 2001 From: Joshua Daniel Pratt Nielsen Date: Sat, 20 Apr 2024 01:32:55 +0200 Subject: [PATCH 008/113] Fix logic for retrieving lastKnownElement --- .../forms/umbfocuslock.directive.js | 27 ++++++++++--------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbfocuslock.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbfocuslock.directive.js index 69c11a11cc..3d412d34e1 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbfocuslock.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbfocuslock.directive.js @@ -88,26 +88,27 @@ // If an infinite editor is being closed then we reset the focus to the element that triggered the the overlay if(closingEditor){ - // If there is only one editor open, search for the "editor-info" inside it and set focus on it // This is relevant when a property editor has been selected and the editor where we selected it from // is closed taking us back to the first layer // Otherwise set it to the last element in the lastKnownFocusedElements array - if(infiniteEditors && infiniteEditors.length === 1){ - var editorInfo = infiniteEditors[0].querySelector('.editor-info'); - if(infiniteEditors && infiniteEditors.length === 1 && editorInfo !== null) { - lastKnownElement = editorInfo; - // Clear the array - clearLastKnownFocusedElements(); - } + var editorInfo = (infiniteEditors && infiniteEditors.length === 1) + ? infiniteEditors[0].querySelector('.editor-info') + : null; + + if(editorInfo !== null){ + lastKnownElement = editorInfo; + + // Clear the array + clearLastKnownFocusedElements(); } - else { - var lastItemIndex = $rootScope.lastKnownFocusableElements.length - 1; - lastKnownElement = $rootScope.lastKnownFocusableElements[lastItemIndex]; + else{ + var lastIndex = $rootScope.lastKnownFocusableElements.length - 1; + lastKnownElement = $rootScope.lastKnownFocusableElements[lastIndex]; - // Remove the last item from the array so we always set the correct lastKnowFocus for each layer - $rootScope.lastKnownFocusableElements.splice(lastItemIndex, 1); + // Remove the last item from the array so we always set the correct lastKnowFocus for each layer + $rootScope.lastKnownFocusableElements.splice(lastIndex, 1); } // Update the lastknowelement variable here From 599ec18ecc6b2890d96654c19a516f704aef1cf6 Mon Sep 17 00:00:00 2001 From: Lars-Erik Date: Thu, 4 Apr 2024 13:44:20 +0200 Subject: [PATCH 009/113] Implementors using Umbraco.Tests.Integration won't have to override GetLocalizedTextService (cherry picked from commit b0016687eb583a549da8992f5fba92e269b4cbfa) (cherry picked from commit 2bb56f1b81a84df3ef03ba0170f741cea6007c28) --- .../DependencyInjection/UmbracoBuilderExtensions.cs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/tests/Umbraco.Tests.Integration/DependencyInjection/UmbracoBuilderExtensions.cs b/tests/Umbraco.Tests.Integration/DependencyInjection/UmbracoBuilderExtensions.cs index dac260b985..bae9c7efbe 100644 --- a/tests/Umbraco.Tests.Integration/DependencyInjection/UmbracoBuilderExtensions.cs +++ b/tests/Umbraco.Tests.Integration/DependencyInjection/UmbracoBuilderExtensions.cs @@ -123,7 +123,18 @@ public static class UmbracoBuilderExtensions var currFolder = new DirectoryInfo(srcFolder); - var uiProject = currFolder.GetDirectories("Umbraco.Web.UI", SearchOption.TopDirectoryOnly).First(); + if (!currFolder.Exists) + { + currFolder = new DirectoryInfo(Path.GetTempPath()); + } + + var uiProject = currFolder.GetDirectories("Umbraco.Web.UI", SearchOption.TopDirectoryOnly).FirstOrDefault(); + if (uiProject == null) + { + uiProject = new DirectoryInfo(Path.Combine(Path.GetTempPath(), "Umbraco.Web.UI")); + uiProject.Create(); + } + var mainLangFolder = new DirectoryInfo(Path.Combine(uiProject.FullName, globalSettings.Value.UmbracoPath.TrimStart("~/"), "config", "lang")); return new LocalizedTextServiceFileSources( From 5b46c718e69e902ff7f82a68bd780d51cd2c0caf Mon Sep 17 00:00:00 2001 From: Joshua Daniel Pratt Nielsen Date: Sat, 20 Apr 2024 01:32:55 +0200 Subject: [PATCH 010/113] Fix logic for retrieving lastKnownElement (cherry picked from commit cae106bfe8fa11c080fc90ba4354a0791e47f9a2) --- .../forms/umbfocuslock.directive.js | 27 ++++++++++--------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbfocuslock.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbfocuslock.directive.js index 69c11a11cc..3d412d34e1 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbfocuslock.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbfocuslock.directive.js @@ -88,26 +88,27 @@ // If an infinite editor is being closed then we reset the focus to the element that triggered the the overlay if(closingEditor){ - // If there is only one editor open, search for the "editor-info" inside it and set focus on it // This is relevant when a property editor has been selected and the editor where we selected it from // is closed taking us back to the first layer // Otherwise set it to the last element in the lastKnownFocusedElements array - if(infiniteEditors && infiniteEditors.length === 1){ - var editorInfo = infiniteEditors[0].querySelector('.editor-info'); - if(infiniteEditors && infiniteEditors.length === 1 && editorInfo !== null) { - lastKnownElement = editorInfo; - // Clear the array - clearLastKnownFocusedElements(); - } + var editorInfo = (infiniteEditors && infiniteEditors.length === 1) + ? infiniteEditors[0].querySelector('.editor-info') + : null; + + if(editorInfo !== null){ + lastKnownElement = editorInfo; + + // Clear the array + clearLastKnownFocusedElements(); } - else { - var lastItemIndex = $rootScope.lastKnownFocusableElements.length - 1; - lastKnownElement = $rootScope.lastKnownFocusableElements[lastItemIndex]; + else{ + var lastIndex = $rootScope.lastKnownFocusableElements.length - 1; + lastKnownElement = $rootScope.lastKnownFocusableElements[lastIndex]; - // Remove the last item from the array so we always set the correct lastKnowFocus for each layer - $rootScope.lastKnownFocusableElements.splice(lastItemIndex, 1); + // Remove the last item from the array so we always set the correct lastKnowFocus for each layer + $rootScope.lastKnownFocusableElements.splice(lastIndex, 1); } // Update the lastknowelement variable here From b6031dea1a86f15e0271d711aa14806ec5b1758a Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Fri, 3 May 2024 09:27:20 +0200 Subject: [PATCH 011/113] bump version --- version.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.json b/version.json index 6ab0f37af9..fcf4a18f98 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.3.0", + "version": "13.3.1", "assemblyVersion": { "precision": "build" }, From edb516f71ae323a34f4268c7628b769f1ade627b Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Fri, 3 May 2024 09:28:43 +0200 Subject: [PATCH 012/113] Bump version --- version.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.json b/version.json index 20b8b28182..500ce474e2 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": "12.3.9", + "version": "12.3.10", "assemblyVersion": { "precision": "build" }, From fee222daff2321b3f8070945ec5de41953fa3a53 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Fri, 3 May 2024 09:29:37 +0200 Subject: [PATCH 013/113] Bump version --- version.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.json b/version.json index 547407d05f..f49d971518 100644 --- a/version.json +++ b/version.json @@ -1,6 +1,6 @@ { "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json", - "version": "10.8.5", + "version": "10.8.6", "assemblyVersion": { "precision": "build" }, From 23d0a6b9b2a237008c005cb17e2552e23651de4c Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Mon, 6 May 2024 09:40:52 +0200 Subject: [PATCH 014/113] Since v13 properties can sometimes be of type IRichTextEditorIntermediateValue - this was unexpected in the XPath navigator code (#16121) --- src/Umbraco.Core/Xml/XPath/NavigableNavigator.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Umbraco.Core/Xml/XPath/NavigableNavigator.cs b/src/Umbraco.Core/Xml/XPath/NavigableNavigator.cs index 020f753165..b21666b2b6 100644 --- a/src/Umbraco.Core/Xml/XPath/NavigableNavigator.cs +++ b/src/Umbraco.Core/Xml/XPath/NavigableNavigator.cs @@ -15,6 +15,7 @@ using System.Diagnostics; using System.Globalization; using System.Xml; using System.Xml.XPath; +using Umbraco.Cms.Core.PropertyEditors; namespace Umbraco.Cms.Core.Xml.XPath; @@ -641,6 +642,11 @@ public class NavigableNavigator : XPathNavigator return true; } + if (valueForXPath is IRichTextEditorIntermediateValue) + { + return false; + } + throw new InvalidOperationException("XPathValue must be an XPathNavigator or a string."); } From cfcdc9c98d94c61271ff07c769e8ff23ec3089f6 Mon Sep 17 00:00:00 2001 From: Rasmus John Pedersen Date: Mon, 6 May 2024 13:14:11 +0200 Subject: [PATCH 015/113] Webhook log improvements (#16200) * fix: include all headers in webhook log * feat: return webhook log status from server * feat: make webhook logs deep linkable * feat: add webhook log pagination * feat: improve webhook request/response body preview --- src/Umbraco.Core/Models/WebhookLog.cs | 2 ++ .../Services/WebhookLogFactory.cs | 5 ++-- .../Factories/WebhookLogFactory.cs | 4 ++- .../Mapping/WebhookMapDefinition.cs | 1 + .../Models/WebhookLogViewModel.cs | 3 +++ .../src/views/webhooks/logs.controller.js | 26 +++++++++++++++---- .../src/views/webhooks/logs.html | 23 +++++++++++----- .../webhooks/overlays/details.controller.js | 18 ++++++++++++- .../src/views/webhooks/overlays/details.html | 14 +++++----- .../src/views/webhooks/overview.controller.js | 10 +++---- 10 files changed, 80 insertions(+), 26 deletions(-) diff --git a/src/Umbraco.Core/Models/WebhookLog.cs b/src/Umbraco.Core/Models/WebhookLog.cs index aba3b15713..5340b0f738 100644 --- a/src/Umbraco.Core/Models/WebhookLog.cs +++ b/src/Umbraco.Core/Models/WebhookLog.cs @@ -27,4 +27,6 @@ public class WebhookLog public string ResponseBody { get; set; } = string.Empty; public bool ExceptionOccured { get; set; } + + public bool IsSuccessStatusCode { get; set; } } diff --git a/src/Umbraco.Core/Services/WebhookLogFactory.cs b/src/Umbraco.Core/Services/WebhookLogFactory.cs index 2c80f15e27..3c260c017f 100644 --- a/src/Umbraco.Core/Services/WebhookLogFactory.cs +++ b/src/Umbraco.Core/Services/WebhookLogFactory.cs @@ -16,7 +16,7 @@ public class WebhookLogFactory : IWebhookLogFactory Url = webhook.Url, WebhookKey = webhook.Key, RetryCount = retryCount, - RequestHeaders = requestMessage.Headers.ToString(), + RequestHeaders = $"{requestMessage.Content?.Headers}{requestMessage.Headers}", RequestBody = await requestMessage.Content?.ReadAsStringAsync(cancellationToken)!, ExceptionOccured = exception is not null, }; @@ -24,7 +24,8 @@ public class WebhookLogFactory : IWebhookLogFactory if (httpResponseMessage is not null) { log.StatusCode = MapStatusCodeToMessage(httpResponseMessage.StatusCode); - log.ResponseHeaders = httpResponseMessage.Headers.ToString(); + log.IsSuccessStatusCode = httpResponseMessage.IsSuccessStatusCode; + log.ResponseHeaders = $"{httpResponseMessage.Content.Headers}{httpResponseMessage.Headers}"; log.ResponseBody = await httpResponseMessage.Content.ReadAsStringAsync(cancellationToken); } else if (exception is HttpRequestException httpRequestException) diff --git a/src/Umbraco.Infrastructure/Persistence/Factories/WebhookLogFactory.cs b/src/Umbraco.Infrastructure/Persistence/Factories/WebhookLogFactory.cs index f060525624..886ee58eaf 100644 --- a/src/Umbraco.Infrastructure/Persistence/Factories/WebhookLogFactory.cs +++ b/src/Umbraco.Infrastructure/Persistence/Factories/WebhookLogFactory.cs @@ -1,4 +1,5 @@ -using Umbraco.Cms.Core.Models; +using System.Text.RegularExpressions; +using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Webhooks; using Umbraco.Cms.Infrastructure.Persistence.Dtos; @@ -33,6 +34,7 @@ internal static class WebhookLogFactory ResponseBody = dto.ResponseBody, RetryCount = dto.RetryCount, StatusCode = dto.StatusCode, + IsSuccessStatusCode = Regex.IsMatch(dto.StatusCode, "^.*\\(2(\\d{2})\\)$"), Key = dto.Key, Id = dto.Id, Url = dto.Url, diff --git a/src/Umbraco.Web.BackOffice/Mapping/WebhookMapDefinition.cs b/src/Umbraco.Web.BackOffice/Mapping/WebhookMapDefinition.cs index 1d1558026c..017004e266 100644 --- a/src/Umbraco.Web.BackOffice/Mapping/WebhookMapDefinition.cs +++ b/src/Umbraco.Web.BackOffice/Mapping/WebhookMapDefinition.cs @@ -67,6 +67,7 @@ public class WebhookMapDefinition : IMapDefinition target.Url = source.Url; target.RequestHeaders = source.RequestHeaders; target.WebhookKey = source.WebhookKey; + target.IsSuccessStatusCode = source.IsSuccessStatusCode; if (_hostingEnvironment.IsDebugMode) { diff --git a/src/Umbraco.Web.Common/Models/WebhookLogViewModel.cs b/src/Umbraco.Web.Common/Models/WebhookLogViewModel.cs index 0b3ff552a7..4518c77637 100644 --- a/src/Umbraco.Web.Common/Models/WebhookLogViewModel.cs +++ b/src/Umbraco.Web.Common/Models/WebhookLogViewModel.cs @@ -14,6 +14,9 @@ public class WebhookLogViewModel [DataMember(Name = "statusCode")] public string StatusCode { get; set; } = string.Empty; + [DataMember(Name = "isSuccessStatusCode")] + public bool IsSuccessStatusCode { get; set; } + [DataMember(Name = "date")] public DateTime Date { get; set; } diff --git a/src/Umbraco.Web.UI.Client/src/views/webhooks/logs.controller.js b/src/Umbraco.Web.UI.Client/src/views/webhooks/logs.controller.js index c4d032b806..5afa4fd00d 100644 --- a/src/Umbraco.Web.UI.Client/src/views/webhooks/logs.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/webhooks/logs.controller.js @@ -5,9 +5,13 @@ const vm = this; + vm.pagination = { + pageNumber: 1, + pageSize: 25 + }; + vm.logs = []; vm.openLogOverlay = openLogOverlay; - vm.isChecked = isChecked; function init() { vm.loading = true; @@ -22,9 +26,14 @@ } function loadLogs() { - return webhooksResource.getLogs() + const take = vm.pagination.pageSize; + const skip = (vm.pagination.pageNumber - 1) * take; + + return webhooksResource.getLogs(skip, take) .then(data => { vm.logs = data.items; + vm.pagination.totalPages = Math.ceil(data.totalItems/vm.pagination.pageSize); + vm.logs.forEach(log => { formatDatesToLocal(log); }); @@ -54,9 +63,16 @@ editorService.open(dialog); } - function isChecked(log) { - return log.statusCode === "OK (200)"; - } + vm.previousPage = () => vm.goToPage(vm.pagination.pageNumber - 1); + vm.nextPage = () => vm.goToPage(vm.pagination.pageNumber + 1); + + vm.goToPage = (pageNumber) => { + vm.pagination.pageNumber = pageNumber; + vm.loading = true; + loadLogs().then(() => { + vm.loading = false; + }); + }; init(); } diff --git a/src/Umbraco.Web.UI.Client/src/views/webhooks/logs.html b/src/Umbraco.Web.UI.Client/src/views/webhooks/logs.html index 3a92c04a56..c6857f1eb1 100644 --- a/src/Umbraco.Web.UI.Client/src/views/webhooks/logs.html +++ b/src/Umbraco.Web.UI.Client/src/views/webhooks/logs.html @@ -3,7 +3,6 @@ - Webhook key Date Url Event @@ -14,13 +13,13 @@ + ng-if="log.isSuccessStatusCode" + checked="true" + size="m" + title="{{ log.statusCode }}"> - + - {{ log.webhookKey }} {{ log.formattedLogDate }} {{ log.url }} {{ log.eventAlias }} @@ -28,4 +27,16 @@ + + + There are no items show in the list. + + + + diff --git a/src/Umbraco.Web.UI.Client/src/views/webhooks/overlays/details.controller.js b/src/Umbraco.Web.UI.Client/src/views/webhooks/overlays/details.controller.js index 39d25c0205..b7940ac657 100644 --- a/src/Umbraco.Web.UI.Client/src/views/webhooks/overlays/details.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/webhooks/overlays/details.controller.js @@ -5,6 +5,7 @@ vm.close = close; vm.formatData = formatData; + vm.detectLanguage = detectLanguage; function formatData(data) { @@ -12,7 +13,7 @@ if (data.detectIsJson()) { try { - obj = Utilities.fromJson(data) + obj = JSON.stringify(Utilities.fromJson(data), null, 2); } catch (err) { obj = data; } @@ -21,6 +22,21 @@ return obj; } + function detectLanguage(headers, defaultLanguage) { + const matches = headers.match(/^Content-Type:\s*(?[a-z\/+.-]+)(\;?.*?)$/mi) + if (matches) { + const contentType = matches.groups["type"]; + if (contentType === "application/json") + return "JSON"; + if (contentType === "text/html") + return "HTML"; + if (contentType === "application/xml" || contentType === "text/xml") + return "XML"; + } + + return defaultLanguage || "TEXT"; + } + function close() { if ($scope.model && $scope.model.close) { $scope.model.close(); diff --git a/src/Umbraco.Web.UI.Client/src/views/webhooks/overlays/details.html b/src/Umbraco.Web.UI.Client/src/views/webhooks/overlays/details.html index 1c07398dba..8654f8f47b 100644 --- a/src/Umbraco.Web.UI.Client/src/views/webhooks/overlays/details.html +++ b/src/Umbraco.Web.UI.Client/src/views/webhooks/overlays/details.html @@ -21,9 +21,9 @@
    + ng-if="model.log.isSuccessStatusCode"> + ng-if="!model.log.isSuccessStatusCode">
    {{model.log.statusCode}}
    @@ -45,6 +45,10 @@
    {{model.log.retryCount}}
    + +
    {{model.log.webhookKey}}
    +
    + @@ -53,16 +57,14 @@
    {{model.log.requestHeaders}}
    - {{vm.formatData(model.log.requestBody) | json}} - + {{vm.formatData(model.log.requestBody)}}
    {{model.log.responseHeaders}}
    - {{vm.formatData(model.log.responseBody) | json}} - + {{vm.formatData(model.log.responseBody)}}
    diff --git a/src/Umbraco.Web.UI.Client/src/views/webhooks/overview.controller.js b/src/Umbraco.Web.UI.Client/src/views/webhooks/overview.controller.js index a8dcf8658b..7dc99f84a9 100644 --- a/src/Umbraco.Web.UI.Client/src/views/webhooks/overview.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/webhooks/overview.controller.js @@ -10,7 +10,7 @@ vm.page.name = ""; vm.page.navigation = []; - let webhookUri = $routeParams.method; + let webhookUri = $routeParams.id; onInit(); @@ -33,8 +33,8 @@ { "name": vm.page.labels.webhooks, "icon": "icon-webhook", - "view": "views/webhooks/webhooks.html", - "active": webhookUri === 'overview', + "view": !webhookUri ? "views/webhooks/webhooks.html" : null, + "active": !webhookUri, "alias": "umbWebhooks", "action": function () { $location.path("/settings/webhooks/overview"); @@ -43,11 +43,11 @@ { "name": vm.page.labels.logs, "icon": "icon-box-alt", - "view": "views/webhooks/logs.html", + "view": webhookUri === 'logs' ? "views/webhooks/logs.html" : null, "active": webhookUri === 'logs', "alias": "umbWebhookLogs", "action": function () { - $location.path("/settings/webhooks/overview"); + $location.path("/settings/webhooks/overview/logs"); } } ]; From ba9ddd11da66b6ecdf72fdbfc660234d63843bb8 Mon Sep 17 00:00:00 2001 From: Mole Date: Fri, 10 May 2024 11:36:12 +0200 Subject: [PATCH 016/113] V13: Optimize custom MVC routing (#16218) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Introduce EagerMatcherPolicy to conditionally bypass content routing * Ensure that the candidate we disable dynamic routing for is valid * Skip Umbraco endpoints * Simplify logic a bit * Move install logic to matcher * Ensure that dynamic routing is still skipped when in upgrade state * Fixup comments * Reduce nesting a bit * Don't show maintenance page when statically routed controllers are hít * Remove excess check, since installer requests are statically routed --- src/Umbraco.Core/Constants-Web.cs | 1 + .../UmbracoBuilderExtensions.cs | 1 + .../UmbracoApplicationBuilder.Website.cs | 3 +- .../Routing/EagerMatcherPolicy.cs | 229 ++++++++++++++++++ .../Routing/UmbracoRouteValueTransformer.cs | 28 --- 5 files changed, 233 insertions(+), 29 deletions(-) create mode 100644 src/Umbraco.Web.Website/Routing/EagerMatcherPolicy.cs diff --git a/src/Umbraco.Core/Constants-Web.cs b/src/Umbraco.Core/Constants-Web.cs index 0c39c1b1b0..1364abac5e 100644 --- a/src/Umbraco.Core/Constants-Web.cs +++ b/src/Umbraco.Core/Constants-Web.cs @@ -62,6 +62,7 @@ public static partial class Constants public const string ControllerToken = "controller"; public const string ActionToken = "action"; public const string AreaToken = "area"; + public const string DynamicRoutePattern = "/{**umbracoSlug}"; } public static class RoutePath diff --git a/src/Umbraco.Web.Website/DependencyInjection/UmbracoBuilderExtensions.cs b/src/Umbraco.Web.Website/DependencyInjection/UmbracoBuilderExtensions.cs index dc98c5b813..f182bda7b6 100644 --- a/src/Umbraco.Web.Website/DependencyInjection/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.Web.Website/DependencyInjection/UmbracoBuilderExtensions.cs @@ -64,6 +64,7 @@ public static partial class UmbracoBuilderExtensions builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); + builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); diff --git a/src/Umbraco.Web.Website/Extensions/UmbracoApplicationBuilder.Website.cs b/src/Umbraco.Web.Website/Extensions/UmbracoApplicationBuilder.Website.cs index 549c0844ff..e527724add 100644 --- a/src/Umbraco.Web.Website/Extensions/UmbracoApplicationBuilder.Website.cs +++ b/src/Umbraco.Web.Website/Extensions/UmbracoApplicationBuilder.Website.cs @@ -1,5 +1,6 @@ using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; +using Umbraco.Cms.Core; using Umbraco.Cms.Web.Common.ApplicationBuilder; using Umbraco.Cms.Web.Common.Middleware; using Umbraco.Cms.Web.Website.Routing; @@ -39,7 +40,7 @@ public static class UmbracoApplicationBuilderExtensions FrontEndRoutes surfaceRoutes = builder.ApplicationServices.GetRequiredService(); surfaceRoutes.CreateRoutes(builder.EndpointRouteBuilder); - builder.EndpointRouteBuilder.MapDynamicControllerRoute("/{**slug}"); + builder.EndpointRouteBuilder.MapDynamicControllerRoute(Constants.Web.Routing.DynamicRoutePattern); return builder; } diff --git a/src/Umbraco.Web.Website/Routing/EagerMatcherPolicy.cs b/src/Umbraco.Web.Website/Routing/EagerMatcherPolicy.cs new file mode 100644 index 0000000000..3fe0814a15 --- /dev/null +++ b/src/Umbraco.Web.Website/Routing/EagerMatcherPolicy.cs @@ -0,0 +1,229 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Controllers; +using Microsoft.AspNetCore.Routing; +using Microsoft.AspNetCore.Routing.Matching; +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Routing; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Web.Common.Controllers; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Web.Website.Routing; + + +/** + * A matcher policy that discards the catch-all (slug) route if there are any other valid routes with a lower order. + * + * The purpose of this is to skip our expensive if it's not required, + * for instance if there's a statically routed endpoint registered before the dynamic route, + * for more information see: https://github.com/umbraco/Umbraco-CMS/issues/16015. + * The core reason why this is necessary is that ALL routes get evaluated: + * " + * all routes get evaluated, they get to produce candidates and then the best candidate is selected. + * Since you have a dynamic route, it needs to run to produce the final endpoints and + * then those are ranked in along with the rest of the candidates to choose the final endpoint. + * " + * From: https://github.com/dotnet/aspnetcore/issues/45175#issuecomment-1322497958 + * + * This also handles rerouting under install/upgrade states. + */ + +internal class EagerMatcherPolicy : MatcherPolicy, IEndpointSelectorPolicy +{ + private readonly IRuntimeState _runtimeState; + private readonly EndpointDataSource _endpointDataSource; + private readonly UmbracoRequestPaths _umbracoRequestPaths; + private GlobalSettings _globalSettings; + private readonly Lazy _installEndpoint; + private readonly Lazy _renderEndpoint; + + public EagerMatcherPolicy( + IRuntimeState runtimeState, + EndpointDataSource endpointDataSource, + UmbracoRequestPaths umbracoRequestPaths, + IOptionsMonitor globalSettings) + { + _runtimeState = runtimeState; + _endpointDataSource = endpointDataSource; + _umbracoRequestPaths = umbracoRequestPaths; + _globalSettings = globalSettings.CurrentValue; + globalSettings.OnChange(settings => _globalSettings = settings); + _installEndpoint = new Lazy(GetInstallEndpoint); + _renderEndpoint = new Lazy(GetRenderEndpoint); + } + + // We want this to run as the very first policy, so we can discard the UmbracoRouteValueTransformer before the framework runs it. + public override int Order => int.MinValue + 10; + + // We know we don't have to run this matcher against the backoffice endpoints. + public bool AppliesToEndpoints(IReadOnlyList endpoints) => true; + + public async Task ApplyAsync(HttpContext httpContext, CandidateSet candidates) + { + if (_runtimeState.Level != RuntimeLevel.Run) + { + var handled = await HandleInstallUpgrade(httpContext, candidates); + if (handled) + { + return; + } + } + + // If there's only one candidate, we don't need to do anything. + if (candidates.Count < 2) + { + return; + } + + // If there are multiple candidates, we want to discard the catch-all (slug) + // IF there is any candidates with a lower order. Since this will be a statically routed endpoint registered before the dynamic route. + // Which means that we don't have to run our UmbracoRouteValueTransformer to route dynamically (expensive). + var lowestOrder = int.MaxValue; + int? dynamicId = null; + RouteEndpoint? dynamicEndpoint = null; + for (var i = 0; i < candidates.Count; i++) + { + CandidateState candidate = candidates[i]; + + // If it's not a RouteEndpoint there's not much we can do to count it in the order. + if (candidate.Endpoint is not RouteEndpoint routeEndpoint) + { + continue; + } + + if (routeEndpoint.Order < lowestOrder) + { + // We have to ensure that the route is valid for the current request method. + // This is because attribute routing will always have an order of 0. + // This means that you could attribute route a POST to /example, but also have an umbraco page at /example + // This would then result in a 404, because we'd see the attribute route with order 0, and always consider that the lowest order + // We'd then disable the dynamic endpoint since another endpoint has a lower order, and end up with only 1 invalid endpoint. + // (IsValidCandidate does not take this into account since the candidate itself is still valid) + HttpMethodMetadata? methodMetaData = routeEndpoint.Metadata.GetMetadata(); + if (methodMetaData?.HttpMethods.Contains(httpContext.Request.Method) is false) + { + continue; + } + + lowestOrder = routeEndpoint.Order; + } + + // We only want to consider our dynamic route, this way it's still possible to register your own custom route before ours. + if (routeEndpoint.DisplayName != Constants.Web.Routing.DynamicRoutePattern) + { + continue; + } + + dynamicEndpoint = routeEndpoint; + dynamicId = i; + } + + // Invalidate the dynamic route if another route has a lower order. + // This means that if you register your static route after the dynamic route, the dynamic route will take precedence + // This more closely resembles the existing behaviour. + if (dynamicEndpoint is not null && dynamicId is not null && dynamicEndpoint.Order > lowestOrder) + { + candidates.SetValidity(dynamicId.Value, false); + } + } + + /// + /// Replaces the first endpoint candidate with the specified endpoint, invalidating all other candidates, + /// guaranteeing that the specified endpoint will be hit. + /// + /// The candidate set to manipulate. + /// The target endpoint that will be hit. + /// + private static void SetEndpoint(CandidateSet candidates, Endpoint endpoint, RouteValueDictionary routeValueDictionary) + { + candidates.ReplaceEndpoint(0, endpoint, routeValueDictionary); + + for (int i = 1; i < candidates.Count; i++) + { + candidates.SetValidity(1, false); + } + } + + private Endpoint GetInstallEndpoint() + { + Endpoint endpoint = _endpointDataSource.Endpoints.First(x => + { + ControllerActionDescriptor? descriptor = x.Metadata.GetMetadata(); + return descriptor?.ControllerTypeInfo.Name == "InstallController" + && descriptor.ActionName == "Index"; + }); + + return endpoint; + } + + private Endpoint GetRenderEndpoint() + { + Endpoint endpoint = _endpointDataSource.Endpoints.First(x => + { + ControllerActionDescriptor? descriptor = x.Metadata.GetMetadata(); + return descriptor?.ControllerTypeInfo == typeof(RenderController) + && descriptor.ActionName == nameof(RenderController.Index); + }); + + return endpoint; + } + + private Task HandleInstallUpgrade(HttpContext httpContext, CandidateSet candidates) + { + if (_runtimeState.Level != RuntimeLevel.Upgrade) + { + // We need to let the installer API requests through + // Currently we do this with a check for the installer path + // Ideally we should do this in a more robust way, for instance with a dedicated attribute we can then check for. + if (_umbracoRequestPaths.IsInstallerRequest(httpContext.Request.Path)) + { + return Task.FromResult(true); + } + + SetEndpoint(candidates, _installEndpoint.Value, new RouteValueDictionary + { + [Constants.Web.Routing.ControllerToken] = "Install", + [Constants.Web.Routing.ActionToken] = "Index", + [Constants.Web.Routing.AreaToken] = Constants.Web.Mvc.InstallArea, + }); + + return Task.FromResult(true); + } + + // Check if maintenance page should be shown + // Current behaviour is that statically routed endpoints still work in upgrade state + // This means that IF there is a static route, we should not show the maintenance page. + // And instead carry on as we normally would. + var hasStaticRoute = false; + for (var i = 0; i < candidates.Count; i++) + { + CandidateState candidate = candidates[i]; + IDynamicEndpointMetadata? dynamicEndpointMetadata = candidate.Endpoint.Metadata.GetMetadata(); + if (dynamicEndpointMetadata is null || dynamicEndpointMetadata.IsDynamic is false) + { + hasStaticRoute = true; + break; + } + } + + if (_runtimeState.Level != RuntimeLevel.Upgrade + || _globalSettings.ShowMaintenancePageWhenInUpgradeState is false + || hasStaticRoute) + { + return Task.FromResult(false); + } + + // Otherwise we'll re-route to the render controller (this will in turn show the maintenance page through a filter) + // With this approach however this could really just be a plain old endpoint instead of a filter. + SetEndpoint(candidates, _renderEndpoint.Value, new RouteValueDictionary + { + [Constants.Web.Routing.ControllerToken] = ControllerExtensions.GetControllerName(), + [Constants.Web.Routing.ActionToken] = nameof(RenderController.Index), + }); + + return Task.FromResult(true); + + } +} diff --git a/src/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformer.cs b/src/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformer.cs index 2afba1e0bb..ee4195221c 100644 --- a/src/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformer.cs +++ b/src/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformer.cs @@ -139,23 +139,6 @@ public class UmbracoRouteValueTransformer : DynamicRouteValueTransformer public override async ValueTask TransformAsync( HttpContext httpContext, RouteValueDictionary values) { - // If we aren't running, then we have nothing to route. We allow the frontend to continue while in upgrade mode. - if (_runtime.Level != RuntimeLevel.Run && _runtime.Level != RuntimeLevel.Upgrade) - { - if (_runtime.Level == RuntimeLevel.Install) - { - return new RouteValueDictionary() - { - //TODO figure out constants - [ControllerToken] = "Install", - [ActionToken] = "Index", - [AreaToken] = Constants.Web.Mvc.InstallArea, - }; - } - - return null!; - } - // will be null for any client side requests like JS, etc... if (!_umbracoContextAccessor.TryGetUmbracoContext(out IUmbracoContext? umbracoContext)) { @@ -172,17 +155,6 @@ public class UmbracoRouteValueTransformer : DynamicRouteValueTransformer return null!; } - // Check if the maintenance page should be shown - if (_runtime.Level == RuntimeLevel.Upgrade && _globalSettings.ShowMaintenancePageWhenInUpgradeState) - { - return new RouteValueDictionary - { - // Redirects to the RenderController who handles maintenance page in a filter, instead of having a dedicated controller - [ControllerToken] = ControllerExtensions.GetControllerName(), - [ActionToken] = nameof(RenderController.Index), - }; - } - // Check if there is no existing content and return the no content controller if (!umbracoContext.Content?.HasContent() ?? false) { From 18765465ae7ce5608d5e2e3b5c4a0d53030c6d1c Mon Sep 17 00:00:00 2001 From: Mole Date: Fri, 10 May 2024 11:36:12 +0200 Subject: [PATCH 017/113] V13: Optimize custom MVC routing (#16218) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Introduce EagerMatcherPolicy to conditionally bypass content routing * Ensure that the candidate we disable dynamic routing for is valid * Skip Umbraco endpoints * Simplify logic a bit * Move install logic to matcher * Ensure that dynamic routing is still skipped when in upgrade state * Fixup comments * Reduce nesting a bit * Don't show maintenance page when statically routed controllers are hít * Remove excess check, since installer requests are statically routed (cherry picked from commit ba9ddd11da66b6ecdf72fdbfc660234d63843bb8) --- src/Umbraco.Core/Constants-Web.cs | 1 + .../UmbracoBuilderExtensions.cs | 1 + .../UmbracoApplicationBuilder.Website.cs | 3 +- .../Routing/EagerMatcherPolicy.cs | 229 ++++++++++++++++++ .../Routing/UmbracoRouteValueTransformer.cs | 28 --- 5 files changed, 233 insertions(+), 29 deletions(-) create mode 100644 src/Umbraco.Web.Website/Routing/EagerMatcherPolicy.cs diff --git a/src/Umbraco.Core/Constants-Web.cs b/src/Umbraco.Core/Constants-Web.cs index 0c39c1b1b0..1364abac5e 100644 --- a/src/Umbraco.Core/Constants-Web.cs +++ b/src/Umbraco.Core/Constants-Web.cs @@ -62,6 +62,7 @@ public static partial class Constants public const string ControllerToken = "controller"; public const string ActionToken = "action"; public const string AreaToken = "area"; + public const string DynamicRoutePattern = "/{**umbracoSlug}"; } public static class RoutePath diff --git a/src/Umbraco.Web.Website/DependencyInjection/UmbracoBuilderExtensions.cs b/src/Umbraco.Web.Website/DependencyInjection/UmbracoBuilderExtensions.cs index dc98c5b813..f182bda7b6 100644 --- a/src/Umbraco.Web.Website/DependencyInjection/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.Web.Website/DependencyInjection/UmbracoBuilderExtensions.cs @@ -64,6 +64,7 @@ public static partial class UmbracoBuilderExtensions builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); + builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); diff --git a/src/Umbraco.Web.Website/Extensions/UmbracoApplicationBuilder.Website.cs b/src/Umbraco.Web.Website/Extensions/UmbracoApplicationBuilder.Website.cs index 549c0844ff..e527724add 100644 --- a/src/Umbraco.Web.Website/Extensions/UmbracoApplicationBuilder.Website.cs +++ b/src/Umbraco.Web.Website/Extensions/UmbracoApplicationBuilder.Website.cs @@ -1,5 +1,6 @@ using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; +using Umbraco.Cms.Core; using Umbraco.Cms.Web.Common.ApplicationBuilder; using Umbraco.Cms.Web.Common.Middleware; using Umbraco.Cms.Web.Website.Routing; @@ -39,7 +40,7 @@ public static class UmbracoApplicationBuilderExtensions FrontEndRoutes surfaceRoutes = builder.ApplicationServices.GetRequiredService(); surfaceRoutes.CreateRoutes(builder.EndpointRouteBuilder); - builder.EndpointRouteBuilder.MapDynamicControllerRoute("/{**slug}"); + builder.EndpointRouteBuilder.MapDynamicControllerRoute(Constants.Web.Routing.DynamicRoutePattern); return builder; } diff --git a/src/Umbraco.Web.Website/Routing/EagerMatcherPolicy.cs b/src/Umbraco.Web.Website/Routing/EagerMatcherPolicy.cs new file mode 100644 index 0000000000..3fe0814a15 --- /dev/null +++ b/src/Umbraco.Web.Website/Routing/EagerMatcherPolicy.cs @@ -0,0 +1,229 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Controllers; +using Microsoft.AspNetCore.Routing; +using Microsoft.AspNetCore.Routing.Matching; +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Routing; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Web.Common.Controllers; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Web.Website.Routing; + + +/** + * A matcher policy that discards the catch-all (slug) route if there are any other valid routes with a lower order. + * + * The purpose of this is to skip our expensive if it's not required, + * for instance if there's a statically routed endpoint registered before the dynamic route, + * for more information see: https://github.com/umbraco/Umbraco-CMS/issues/16015. + * The core reason why this is necessary is that ALL routes get evaluated: + * " + * all routes get evaluated, they get to produce candidates and then the best candidate is selected. + * Since you have a dynamic route, it needs to run to produce the final endpoints and + * then those are ranked in along with the rest of the candidates to choose the final endpoint. + * " + * From: https://github.com/dotnet/aspnetcore/issues/45175#issuecomment-1322497958 + * + * This also handles rerouting under install/upgrade states. + */ + +internal class EagerMatcherPolicy : MatcherPolicy, IEndpointSelectorPolicy +{ + private readonly IRuntimeState _runtimeState; + private readonly EndpointDataSource _endpointDataSource; + private readonly UmbracoRequestPaths _umbracoRequestPaths; + private GlobalSettings _globalSettings; + private readonly Lazy _installEndpoint; + private readonly Lazy _renderEndpoint; + + public EagerMatcherPolicy( + IRuntimeState runtimeState, + EndpointDataSource endpointDataSource, + UmbracoRequestPaths umbracoRequestPaths, + IOptionsMonitor globalSettings) + { + _runtimeState = runtimeState; + _endpointDataSource = endpointDataSource; + _umbracoRequestPaths = umbracoRequestPaths; + _globalSettings = globalSettings.CurrentValue; + globalSettings.OnChange(settings => _globalSettings = settings); + _installEndpoint = new Lazy(GetInstallEndpoint); + _renderEndpoint = new Lazy(GetRenderEndpoint); + } + + // We want this to run as the very first policy, so we can discard the UmbracoRouteValueTransformer before the framework runs it. + public override int Order => int.MinValue + 10; + + // We know we don't have to run this matcher against the backoffice endpoints. + public bool AppliesToEndpoints(IReadOnlyList endpoints) => true; + + public async Task ApplyAsync(HttpContext httpContext, CandidateSet candidates) + { + if (_runtimeState.Level != RuntimeLevel.Run) + { + var handled = await HandleInstallUpgrade(httpContext, candidates); + if (handled) + { + return; + } + } + + // If there's only one candidate, we don't need to do anything. + if (candidates.Count < 2) + { + return; + } + + // If there are multiple candidates, we want to discard the catch-all (slug) + // IF there is any candidates with a lower order. Since this will be a statically routed endpoint registered before the dynamic route. + // Which means that we don't have to run our UmbracoRouteValueTransformer to route dynamically (expensive). + var lowestOrder = int.MaxValue; + int? dynamicId = null; + RouteEndpoint? dynamicEndpoint = null; + for (var i = 0; i < candidates.Count; i++) + { + CandidateState candidate = candidates[i]; + + // If it's not a RouteEndpoint there's not much we can do to count it in the order. + if (candidate.Endpoint is not RouteEndpoint routeEndpoint) + { + continue; + } + + if (routeEndpoint.Order < lowestOrder) + { + // We have to ensure that the route is valid for the current request method. + // This is because attribute routing will always have an order of 0. + // This means that you could attribute route a POST to /example, but also have an umbraco page at /example + // This would then result in a 404, because we'd see the attribute route with order 0, and always consider that the lowest order + // We'd then disable the dynamic endpoint since another endpoint has a lower order, and end up with only 1 invalid endpoint. + // (IsValidCandidate does not take this into account since the candidate itself is still valid) + HttpMethodMetadata? methodMetaData = routeEndpoint.Metadata.GetMetadata(); + if (methodMetaData?.HttpMethods.Contains(httpContext.Request.Method) is false) + { + continue; + } + + lowestOrder = routeEndpoint.Order; + } + + // We only want to consider our dynamic route, this way it's still possible to register your own custom route before ours. + if (routeEndpoint.DisplayName != Constants.Web.Routing.DynamicRoutePattern) + { + continue; + } + + dynamicEndpoint = routeEndpoint; + dynamicId = i; + } + + // Invalidate the dynamic route if another route has a lower order. + // This means that if you register your static route after the dynamic route, the dynamic route will take precedence + // This more closely resembles the existing behaviour. + if (dynamicEndpoint is not null && dynamicId is not null && dynamicEndpoint.Order > lowestOrder) + { + candidates.SetValidity(dynamicId.Value, false); + } + } + + /// + /// Replaces the first endpoint candidate with the specified endpoint, invalidating all other candidates, + /// guaranteeing that the specified endpoint will be hit. + /// + /// The candidate set to manipulate. + /// The target endpoint that will be hit. + /// + private static void SetEndpoint(CandidateSet candidates, Endpoint endpoint, RouteValueDictionary routeValueDictionary) + { + candidates.ReplaceEndpoint(0, endpoint, routeValueDictionary); + + for (int i = 1; i < candidates.Count; i++) + { + candidates.SetValidity(1, false); + } + } + + private Endpoint GetInstallEndpoint() + { + Endpoint endpoint = _endpointDataSource.Endpoints.First(x => + { + ControllerActionDescriptor? descriptor = x.Metadata.GetMetadata(); + return descriptor?.ControllerTypeInfo.Name == "InstallController" + && descriptor.ActionName == "Index"; + }); + + return endpoint; + } + + private Endpoint GetRenderEndpoint() + { + Endpoint endpoint = _endpointDataSource.Endpoints.First(x => + { + ControllerActionDescriptor? descriptor = x.Metadata.GetMetadata(); + return descriptor?.ControllerTypeInfo == typeof(RenderController) + && descriptor.ActionName == nameof(RenderController.Index); + }); + + return endpoint; + } + + private Task HandleInstallUpgrade(HttpContext httpContext, CandidateSet candidates) + { + if (_runtimeState.Level != RuntimeLevel.Upgrade) + { + // We need to let the installer API requests through + // Currently we do this with a check for the installer path + // Ideally we should do this in a more robust way, for instance with a dedicated attribute we can then check for. + if (_umbracoRequestPaths.IsInstallerRequest(httpContext.Request.Path)) + { + return Task.FromResult(true); + } + + SetEndpoint(candidates, _installEndpoint.Value, new RouteValueDictionary + { + [Constants.Web.Routing.ControllerToken] = "Install", + [Constants.Web.Routing.ActionToken] = "Index", + [Constants.Web.Routing.AreaToken] = Constants.Web.Mvc.InstallArea, + }); + + return Task.FromResult(true); + } + + // Check if maintenance page should be shown + // Current behaviour is that statically routed endpoints still work in upgrade state + // This means that IF there is a static route, we should not show the maintenance page. + // And instead carry on as we normally would. + var hasStaticRoute = false; + for (var i = 0; i < candidates.Count; i++) + { + CandidateState candidate = candidates[i]; + IDynamicEndpointMetadata? dynamicEndpointMetadata = candidate.Endpoint.Metadata.GetMetadata(); + if (dynamicEndpointMetadata is null || dynamicEndpointMetadata.IsDynamic is false) + { + hasStaticRoute = true; + break; + } + } + + if (_runtimeState.Level != RuntimeLevel.Upgrade + || _globalSettings.ShowMaintenancePageWhenInUpgradeState is false + || hasStaticRoute) + { + return Task.FromResult(false); + } + + // Otherwise we'll re-route to the render controller (this will in turn show the maintenance page through a filter) + // With this approach however this could really just be a plain old endpoint instead of a filter. + SetEndpoint(candidates, _renderEndpoint.Value, new RouteValueDictionary + { + [Constants.Web.Routing.ControllerToken] = ControllerExtensions.GetControllerName(), + [Constants.Web.Routing.ActionToken] = nameof(RenderController.Index), + }); + + return Task.FromResult(true); + + } +} diff --git a/src/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformer.cs b/src/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformer.cs index 2afba1e0bb..ee4195221c 100644 --- a/src/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformer.cs +++ b/src/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformer.cs @@ -139,23 +139,6 @@ public class UmbracoRouteValueTransformer : DynamicRouteValueTransformer public override async ValueTask TransformAsync( HttpContext httpContext, RouteValueDictionary values) { - // If we aren't running, then we have nothing to route. We allow the frontend to continue while in upgrade mode. - if (_runtime.Level != RuntimeLevel.Run && _runtime.Level != RuntimeLevel.Upgrade) - { - if (_runtime.Level == RuntimeLevel.Install) - { - return new RouteValueDictionary() - { - //TODO figure out constants - [ControllerToken] = "Install", - [ActionToken] = "Index", - [AreaToken] = Constants.Web.Mvc.InstallArea, - }; - } - - return null!; - } - // will be null for any client side requests like JS, etc... if (!_umbracoContextAccessor.TryGetUmbracoContext(out IUmbracoContext? umbracoContext)) { @@ -172,17 +155,6 @@ public class UmbracoRouteValueTransformer : DynamicRouteValueTransformer return null!; } - // Check if the maintenance page should be shown - if (_runtime.Level == RuntimeLevel.Upgrade && _globalSettings.ShowMaintenancePageWhenInUpgradeState) - { - return new RouteValueDictionary - { - // Redirects to the RenderController who handles maintenance page in a filter, instead of having a dedicated controller - [ControllerToken] = ControllerExtensions.GetControllerName(), - [ActionToken] = nameof(RenderController.Index), - }; - } - // Check if there is no existing content and return the no content controller if (!umbracoContext.Content?.HasContent() ?? false) { From ab32bac5d96695eebe4f333a9bd6fa8d8b820f71 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Mon, 13 May 2024 15:44:07 +0200 Subject: [PATCH 018/113] Property source level variation should only be applied when configured (#16270) --- .../Property.cs | 15 ++++++++----- .../PublishedContentVarianceTests.cs | 22 +++++++++++++++++++ 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.PublishedCache.NuCache/Property.cs b/src/Umbraco.PublishedCache.NuCache/Property.cs index 596bae2090..ed9f7277ef 100644 --- a/src/Umbraco.PublishedCache.NuCache/Property.cs +++ b/src/Umbraco.PublishedCache.NuCache/Property.cs @@ -25,7 +25,7 @@ internal class Property : PublishedPropertyBase // the invariant-neutral source and inter values private readonly object? _sourceValue; private readonly ContentVariation _variations; - private bool _sourceValueIsInvariant; + private readonly ContentVariation _sourceVariations; // the variant and non-variant object values private CacheValues? _cacheValues; @@ -88,7 +88,7 @@ internal class Property : PublishedPropertyBase // this variable is used for contextualizing the variation level when calculating property values. // it must be set to the union of variance (the combination of content type and property type variance). _variations = propertyType.Variations | content.ContentType.Variations; - _sourceValueIsInvariant = propertyType.Variations is ContentVariation.Nothing; + _sourceVariations = propertyType.Variations; } // clone for previewing as draft a published content that is published and has no draft @@ -104,7 +104,7 @@ internal class Property : PublishedPropertyBase _isMember = origin._isMember; _publishedSnapshotAccessor = origin._publishedSnapshotAccessor; _variations = origin._variations; - _sourceValueIsInvariant = origin._sourceValueIsInvariant; + _sourceVariations = origin._sourceVariations; } // used to cache the CacheValues of this property @@ -148,9 +148,14 @@ internal class Property : PublishedPropertyBase public override object? GetSourceValue(string? culture = null, string? segment = null) { - _content.VariationContextAccessor.ContextualizeVariation(_variations, _content.Id, ref culture, ref segment); + _content.VariationContextAccessor.ContextualizeVariation(_sourceVariations, _content.Id, ref culture, ref segment); - if (_sourceValueIsInvariant || (culture == string.Empty && segment == string.Empty)) + // 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. + culture = _sourceVariations.VariesByCulture() ? culture : string.Empty; + segment = _sourceVariations.VariesBySegment() ? segment : string.Empty; + + if (culture == string.Empty && segment == string.Empty) { return _sourceValue; } diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Published/PublishedContentVarianceTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Published/PublishedContentVarianceTests.cs index 7d117b96c5..a4a15b8f22 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Published/PublishedContentVarianceTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Published/PublishedContentVarianceTests.cs @@ -76,6 +76,28 @@ public class PublishedContentVarianceTests Assert.AreEqual(expectedValue, value); } + [TestCase(DaCulture, Segment1, "DaDk property value")] + [TestCase(DaCulture, Segment2, "DaDk property value")] + [TestCase(EnCulture, Segment1, "EnUs property value")] + [TestCase(EnCulture, Segment2, "EnUs property value")] + public void Content_Culture_And_Segment_Variation_Can_Get_Culture_Variant_Property(string culture, string segment, string expectedValue) + { + var content = CreatePublishedContent(ContentVariation.CultureAndSegment, ContentVariation.Culture, variationContextCulture: culture, variationContextSegment: segment); + var value = GetPropertyValue(content); + Assert.AreEqual(expectedValue, value); + } + + [TestCase(DaCulture, Segment1, "Segment1 property value")] + [TestCase(DaCulture, Segment2, "Segment2 property value")] + [TestCase(EnCulture, Segment1, "Segment1 property value")] + [TestCase(EnCulture, Segment2, "Segment2 property value")] + public void Content_Culture_And_Segment_Variation_Can_Get_Segment_Variant_Property(string culture, string segment, string expectedValue) + { + var content = CreatePublishedContent(ContentVariation.CultureAndSegment, ContentVariation.Segment, variationContextCulture: culture, variationContextSegment: segment); + var value = GetPropertyValue(content); + Assert.AreEqual(expectedValue, value); + } + private object? GetPropertyValue(IPublishedContent content) => content.GetProperty(PropertyTypeAlias)!.GetValue(); private IPublishedContent CreatePublishedContent(ContentVariation contentTypeVariation, ContentVariation propertyTypeVariation, string? variationContextCulture = null, string? variationContextSegment = null) From 94cef504a3e10a6bc2b577e7d0ba3cd80af95175 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Mon, 13 May 2024 15:44:07 +0200 Subject: [PATCH 019/113] Property source level variation should only be applied when configured (#16270) (cherry picked from commit ab32bac5d96695eebe4f333a9bd6fa8d8b820f71) --- .../Property.cs | 15 ++++++++----- .../PublishedContentVarianceTests.cs | 22 +++++++++++++++++++ 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.PublishedCache.NuCache/Property.cs b/src/Umbraco.PublishedCache.NuCache/Property.cs index 596bae2090..ed9f7277ef 100644 --- a/src/Umbraco.PublishedCache.NuCache/Property.cs +++ b/src/Umbraco.PublishedCache.NuCache/Property.cs @@ -25,7 +25,7 @@ internal class Property : PublishedPropertyBase // the invariant-neutral source and inter values private readonly object? _sourceValue; private readonly ContentVariation _variations; - private bool _sourceValueIsInvariant; + private readonly ContentVariation _sourceVariations; // the variant and non-variant object values private CacheValues? _cacheValues; @@ -88,7 +88,7 @@ internal class Property : PublishedPropertyBase // this variable is used for contextualizing the variation level when calculating property values. // it must be set to the union of variance (the combination of content type and property type variance). _variations = propertyType.Variations | content.ContentType.Variations; - _sourceValueIsInvariant = propertyType.Variations is ContentVariation.Nothing; + _sourceVariations = propertyType.Variations; } // clone for previewing as draft a published content that is published and has no draft @@ -104,7 +104,7 @@ internal class Property : PublishedPropertyBase _isMember = origin._isMember; _publishedSnapshotAccessor = origin._publishedSnapshotAccessor; _variations = origin._variations; - _sourceValueIsInvariant = origin._sourceValueIsInvariant; + _sourceVariations = origin._sourceVariations; } // used to cache the CacheValues of this property @@ -148,9 +148,14 @@ internal class Property : PublishedPropertyBase public override object? GetSourceValue(string? culture = null, string? segment = null) { - _content.VariationContextAccessor.ContextualizeVariation(_variations, _content.Id, ref culture, ref segment); + _content.VariationContextAccessor.ContextualizeVariation(_sourceVariations, _content.Id, ref culture, ref segment); - if (_sourceValueIsInvariant || (culture == string.Empty && segment == string.Empty)) + // 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. + culture = _sourceVariations.VariesByCulture() ? culture : string.Empty; + segment = _sourceVariations.VariesBySegment() ? segment : string.Empty; + + if (culture == string.Empty && segment == string.Empty) { return _sourceValue; } diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Published/PublishedContentVarianceTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Published/PublishedContentVarianceTests.cs index 7d117b96c5..a4a15b8f22 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Published/PublishedContentVarianceTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Published/PublishedContentVarianceTests.cs @@ -76,6 +76,28 @@ public class PublishedContentVarianceTests Assert.AreEqual(expectedValue, value); } + [TestCase(DaCulture, Segment1, "DaDk property value")] + [TestCase(DaCulture, Segment2, "DaDk property value")] + [TestCase(EnCulture, Segment1, "EnUs property value")] + [TestCase(EnCulture, Segment2, "EnUs property value")] + public void Content_Culture_And_Segment_Variation_Can_Get_Culture_Variant_Property(string culture, string segment, string expectedValue) + { + var content = CreatePublishedContent(ContentVariation.CultureAndSegment, ContentVariation.Culture, variationContextCulture: culture, variationContextSegment: segment); + var value = GetPropertyValue(content); + Assert.AreEqual(expectedValue, value); + } + + [TestCase(DaCulture, Segment1, "Segment1 property value")] + [TestCase(DaCulture, Segment2, "Segment2 property value")] + [TestCase(EnCulture, Segment1, "Segment1 property value")] + [TestCase(EnCulture, Segment2, "Segment2 property value")] + public void Content_Culture_And_Segment_Variation_Can_Get_Segment_Variant_Property(string culture, string segment, string expectedValue) + { + var content = CreatePublishedContent(ContentVariation.CultureAndSegment, ContentVariation.Segment, variationContextCulture: culture, variationContextSegment: segment); + var value = GetPropertyValue(content); + Assert.AreEqual(expectedValue, value); + } + private object? GetPropertyValue(IPublishedContent content) => content.GetProperty(PropertyTypeAlias)!.GetValue(); private IPublishedContent CreatePublishedContent(ContentVariation contentTypeVariation, ContentVariation propertyTypeVariation, string? variationContextCulture = null, string? variationContextSegment = null) From c17d4e1a600098ec524e4126f4395255476bc33f Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Fri, 17 May 2024 08:37:51 +0200 Subject: [PATCH 020/113] Merge pull request from GHSA-j74q-mv2c-rxmp --- src/Umbraco.Core/Routing/WebPath.cs | 24 ++++++ .../Controllers/ImagesController.cs | 3 +- .../Controllers/PreviewController.cs | 4 +- .../Umbraco.Core/Routing/WebPathTests.cs | 83 +++++++++++++++++++ 4 files changed, 111 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Core/Routing/WebPath.cs b/src/Umbraco.Core/Routing/WebPath.cs index 7ecafff8a3..47c6b15871 100644 --- a/src/Umbraco.Core/Routing/WebPath.cs +++ b/src/Umbraco.Core/Routing/WebPath.cs @@ -50,4 +50,28 @@ public class WebPath return sb.ToString(); } + + + /// + /// Determines whether the provided web path is well-formed according to the specified UriKind. + /// + /// The web path to check. This can be null. + /// The kind of Uri (Absolute, Relative, or RelativeOrAbsolute). + /// + /// true if is well-formed; otherwise, false. + /// + public static bool IsWellFormedWebPath(string? webPath, UriKind uriKind) + { + if (string.IsNullOrWhiteSpace(webPath)) + { + return false; + } + + if (webPath.StartsWith("//")) + { + return uriKind is not UriKind.Relative; + } + + return Uri.IsWellFormedUriString(webPath, uriKind); + } } diff --git a/src/Umbraco.Web.BackOffice/Controllers/ImagesController.cs b/src/Umbraco.Web.BackOffice/Controllers/ImagesController.cs index e718696ae3..71fa9625a6 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/ImagesController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/ImagesController.cs @@ -7,6 +7,7 @@ using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Media; using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Routing; using Umbraco.Cms.Web.Common.Attributes; using Umbraco.Cms.Web.Common.DependencyInjection; using Umbraco.Extensions; @@ -123,7 +124,7 @@ public class ImagesController : UmbracoAuthorizedApiController private bool IsAllowed(string encodedImagePath) { - if(Uri.IsWellFormedUriString(encodedImagePath, UriKind.Relative)) + if(WebPath.IsWellFormedWebPath(encodedImagePath, UriKind.Relative)) { return true; } diff --git a/src/Umbraco.Web.BackOffice/Controllers/PreviewController.cs b/src/Umbraco.Web.BackOffice/Controllers/PreviewController.cs index 19ca323d9d..17875c2950 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/PreviewController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/PreviewController.cs @@ -11,6 +11,7 @@ using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Membership; using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Cms.Core.PublishedCache; +using Umbraco.Cms.Core.Routing; using Umbraco.Cms.Core.Security; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Web; @@ -152,8 +153,7 @@ public class PreviewController : Controller // Expire Client-side cookie that determines whether the user has accepted to be in Preview Mode when visiting the website. _cookieManager.ExpireCookie(Constants.Web.AcceptPreviewCookieName); - if (Uri.IsWellFormedUriString(redir, UriKind.Relative) - && redir.StartsWith("//") == false + if (WebPath.IsWellFormedWebPath(redir, UriKind.Relative) && Uri.TryCreate(redir, UriKind.Relative, out Uri? url)) { return Redirect(url.ToString()); diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/WebPathTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/WebPathTests.cs index 098e047981..acfa4ffe6f 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/WebPathTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/WebPathTests.cs @@ -31,4 +31,87 @@ public class WebPathTests [Test] public void Combine_must_handle_null() => Assert.Throws(() => WebPath.Combine(null)); + + + [Test] + [TestCase("ftp://hello.com/", UriKind.Absolute, ExpectedResult = true)] + [TestCase("file:///hello.com/", UriKind.Absolute, ExpectedResult = true)] + [TestCase("ws://hello.com/", UriKind.Absolute, ExpectedResult = true)] + [TestCase("wss://hello.com/", UriKind.Absolute, ExpectedResult = true)] + [TestCase("https://hello.com:8080/", UriKind.Absolute, ExpectedResult = true)] + [TestCase("http://hello.com:8080/", UriKind.Absolute, ExpectedResult = true)] + [TestCase("https://hello.com/path", UriKind.Absolute, ExpectedResult = true)] + [TestCase("http://hello.com/path", UriKind.Absolute, ExpectedResult = true)] + [TestCase("https://hello.com/path?query=param", UriKind.Absolute, ExpectedResult = true)] + [TestCase("http://hello.com/path?query=param", UriKind.Absolute, ExpectedResult = true)] + [TestCase("https://hello.com/path#fragment", UriKind.Absolute, ExpectedResult = true)] + [TestCase("http://hello.com/path#fragment", UriKind.Absolute, ExpectedResult = true)] + [TestCase("https://hello.com/path?query=param#fragment", UriKind.Absolute, ExpectedResult = true)] + [TestCase("http://hello.com/path?query=param#fragment", UriKind.Absolute, ExpectedResult = true)] + [TestCase("https://hello.com:8080/path?query=param#fragment", UriKind.Absolute, ExpectedResult = true)] + [TestCase("http://hello.com:8080/path?query=param#fragment", UriKind.Absolute, ExpectedResult = true)] + [TestCase("//hello.com:8080/path?query=param#fragment", UriKind.Absolute, ExpectedResult = true)] + [TestCase("//hello.com:8080/path", UriKind.Absolute, ExpectedResult = true)] + [TestCase("//hello.com:8080", UriKind.Absolute, ExpectedResult = true)] + [TestCase("//hello.com", UriKind.Absolute, ExpectedResult = true)] + [TestCase("/test/test.jpg", UriKind.Absolute, ExpectedResult = false)] + [TestCase("/test", UriKind.Absolute, ExpectedResult = false)] + [TestCase("test", UriKind.Absolute, ExpectedResult = false)] + [TestCase("", UriKind.Absolute, ExpectedResult = false)] + [TestCase(null, UriKind.Absolute, ExpectedResult = false)] + [TestCase("this is not welformed", UriKind.Absolute, ExpectedResult = false)] + [TestCase("ftp://hello.com/", UriKind.Relative, ExpectedResult = false)] + [TestCase("file:///hello.com/", UriKind.Relative, ExpectedResult = false)] + [TestCase("ws://hello.com/", UriKind.Relative, ExpectedResult = false)] + [TestCase("wss://hello.com/", UriKind.Relative, ExpectedResult = false)] + [TestCase("https://hello.com:8080/", UriKind.Relative, ExpectedResult = false)] + [TestCase("http://hello.com:8080/", UriKind.Relative, ExpectedResult = false)] + [TestCase("https://hello.com/path", UriKind.Relative, ExpectedResult = false)] + [TestCase("http://hello.com/path", UriKind.Relative, ExpectedResult = false)] + [TestCase("https://hello.com/path?query=param", UriKind.Relative, ExpectedResult = false)] + [TestCase("http://hello.com/path?query=param", UriKind.Relative, ExpectedResult = false)] + [TestCase("https://hello.com/path#fragment", UriKind.Relative, ExpectedResult = false)] + [TestCase("http://hello.com/path#fragment", UriKind.Relative, ExpectedResult = false)] + [TestCase("https://hello.com/path?query=param#fragment", UriKind.Relative, ExpectedResult = false)] + [TestCase("http://hello.com/path?query=param#fragment", UriKind.Relative, ExpectedResult = false)] + [TestCase("https://hello.com:8080/path?query=param#fragment", UriKind.Relative, ExpectedResult = false)] + [TestCase("http://hello.com:8080/path?query=param#fragment", UriKind.Relative, ExpectedResult = false)] + [TestCase("//hello.com:8080/path?query=param#fragment", UriKind.Relative, ExpectedResult = false)] + [TestCase("//hello.com:8080/path", UriKind.Relative, ExpectedResult = false)] + [TestCase("//hello.com:8080", UriKind.Relative, ExpectedResult = false)] + [TestCase("//hello.com", UriKind.Relative, ExpectedResult = false)] + [TestCase("/test/test.jpg", UriKind.Relative, ExpectedResult = true)] + [TestCase("/test", UriKind.Relative, ExpectedResult = true)] + [TestCase("test", UriKind.Relative, ExpectedResult = true)] + [TestCase("", UriKind.Relative, ExpectedResult = false)] + [TestCase(null, UriKind.Relative, ExpectedResult = false)] + [TestCase("this is not welformed", UriKind.Relative, ExpectedResult = false)] + [TestCase("ftp://hello.com/", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("file:///hello.com/", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("ws://hello.com/", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("wss://hello.com/", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("https://hello.com:8080/", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("http://hello.com:8080/", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("https://hello.com/path", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("http://hello.com/path", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("https://hello.com/path?query=param", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("http://hello.com/path?query=param", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("https://hello.com/path#fragment", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("http://hello.com/path#fragment", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("https://hello.com/path?query=param#fragment", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("http://hello.com/path?query=param#fragment", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("https://hello.com:8080/path?query=param#fragment", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("http://hello.com:8080/path?query=param#fragment", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("//hello.com:8080/path?query=param#fragment", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("//hello.com:8080/path", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("//hello.com:8080", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("//hello.com", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("/test/test.jpg", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("/test", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("test", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("", UriKind.RelativeOrAbsolute, ExpectedResult = false)] + [TestCase(null, UriKind.RelativeOrAbsolute, ExpectedResult = false)] + [TestCase("this is not welformed", UriKind.RelativeOrAbsolute, ExpectedResult = false)] + public bool IsWellFormedWebPath(string? webPath, UriKind uriKind) => WebPath.IsWellFormedWebPath(webPath, uriKind); + } From d8df405db4ea884bb4b96f088d10d9a2070cf024 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Fri, 17 May 2024 08:37:51 +0200 Subject: [PATCH 021/113] Merge pull request from GHSA-j74q-mv2c-rxmp --- src/Umbraco.Core/Routing/WebPath.cs | 24 ++++++ .../Controllers/ImagesController.cs | 3 +- .../Controllers/PreviewController.cs | 4 +- .../Umbraco.Core/Routing/WebPathTests.cs | 83 +++++++++++++++++++ 4 files changed, 111 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Core/Routing/WebPath.cs b/src/Umbraco.Core/Routing/WebPath.cs index 7ecafff8a3..47c6b15871 100644 --- a/src/Umbraco.Core/Routing/WebPath.cs +++ b/src/Umbraco.Core/Routing/WebPath.cs @@ -50,4 +50,28 @@ public class WebPath return sb.ToString(); } + + + /// + /// Determines whether the provided web path is well-formed according to the specified UriKind. + /// + /// The web path to check. This can be null. + /// The kind of Uri (Absolute, Relative, or RelativeOrAbsolute). + /// + /// true if is well-formed; otherwise, false. + /// + public static bool IsWellFormedWebPath(string? webPath, UriKind uriKind) + { + if (string.IsNullOrWhiteSpace(webPath)) + { + return false; + } + + if (webPath.StartsWith("//")) + { + return uriKind is not UriKind.Relative; + } + + return Uri.IsWellFormedUriString(webPath, uriKind); + } } diff --git a/src/Umbraco.Web.BackOffice/Controllers/ImagesController.cs b/src/Umbraco.Web.BackOffice/Controllers/ImagesController.cs index 90ef6e6cf4..b70661c0af 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/ImagesController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/ImagesController.cs @@ -8,6 +8,7 @@ using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Media; using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Routing; using Umbraco.Cms.Web.Common.Attributes; using Umbraco.Cms.Web.Common.DependencyInjection; using Umbraco.Extensions; @@ -122,7 +123,7 @@ public class ImagesController : UmbracoAuthorizedApiController private bool IsAllowed(string encodedImagePath) { - if(Uri.IsWellFormedUriString(encodedImagePath, UriKind.Relative)) + if(WebPath.IsWellFormedWebPath(encodedImagePath, UriKind.Relative)) { return true; } diff --git a/src/Umbraco.Web.BackOffice/Controllers/PreviewController.cs b/src/Umbraco.Web.BackOffice/Controllers/PreviewController.cs index 19ca323d9d..17875c2950 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/PreviewController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/PreviewController.cs @@ -11,6 +11,7 @@ using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Membership; using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Cms.Core.PublishedCache; +using Umbraco.Cms.Core.Routing; using Umbraco.Cms.Core.Security; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Web; @@ -152,8 +153,7 @@ public class PreviewController : Controller // Expire Client-side cookie that determines whether the user has accepted to be in Preview Mode when visiting the website. _cookieManager.ExpireCookie(Constants.Web.AcceptPreviewCookieName); - if (Uri.IsWellFormedUriString(redir, UriKind.Relative) - && redir.StartsWith("//") == false + if (WebPath.IsWellFormedWebPath(redir, UriKind.Relative) && Uri.TryCreate(redir, UriKind.Relative, out Uri? url)) { return Redirect(url.ToString()); diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/WebPathTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/WebPathTests.cs index 4072e3df8b..72ab9150bc 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/WebPathTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/WebPathTests.cs @@ -30,4 +30,87 @@ public class WebPathTests [Test] public void Combine_must_handle_null() => Assert.Throws(() => WebPath.Combine(null)); + + + [Test] + [TestCase("ftp://hello.com/", UriKind.Absolute, ExpectedResult = true)] + [TestCase("file:///hello.com/", UriKind.Absolute, ExpectedResult = true)] + [TestCase("ws://hello.com/", UriKind.Absolute, ExpectedResult = true)] + [TestCase("wss://hello.com/", UriKind.Absolute, ExpectedResult = true)] + [TestCase("https://hello.com:8080/", UriKind.Absolute, ExpectedResult = true)] + [TestCase("http://hello.com:8080/", UriKind.Absolute, ExpectedResult = true)] + [TestCase("https://hello.com/path", UriKind.Absolute, ExpectedResult = true)] + [TestCase("http://hello.com/path", UriKind.Absolute, ExpectedResult = true)] + [TestCase("https://hello.com/path?query=param", UriKind.Absolute, ExpectedResult = true)] + [TestCase("http://hello.com/path?query=param", UriKind.Absolute, ExpectedResult = true)] + [TestCase("https://hello.com/path#fragment", UriKind.Absolute, ExpectedResult = true)] + [TestCase("http://hello.com/path#fragment", UriKind.Absolute, ExpectedResult = true)] + [TestCase("https://hello.com/path?query=param#fragment", UriKind.Absolute, ExpectedResult = true)] + [TestCase("http://hello.com/path?query=param#fragment", UriKind.Absolute, ExpectedResult = true)] + [TestCase("https://hello.com:8080/path?query=param#fragment", UriKind.Absolute, ExpectedResult = true)] + [TestCase("http://hello.com:8080/path?query=param#fragment", UriKind.Absolute, ExpectedResult = true)] + [TestCase("//hello.com:8080/path?query=param#fragment", UriKind.Absolute, ExpectedResult = true)] + [TestCase("//hello.com:8080/path", UriKind.Absolute, ExpectedResult = true)] + [TestCase("//hello.com:8080", UriKind.Absolute, ExpectedResult = true)] + [TestCase("//hello.com", UriKind.Absolute, ExpectedResult = true)] + [TestCase("/test/test.jpg", UriKind.Absolute, ExpectedResult = false)] + [TestCase("/test", UriKind.Absolute, ExpectedResult = false)] + [TestCase("test", UriKind.Absolute, ExpectedResult = false)] + [TestCase("", UriKind.Absolute, ExpectedResult = false)] + [TestCase(null, UriKind.Absolute, ExpectedResult = false)] + [TestCase("this is not welformed", UriKind.Absolute, ExpectedResult = false)] + [TestCase("ftp://hello.com/", UriKind.Relative, ExpectedResult = false)] + [TestCase("file:///hello.com/", UriKind.Relative, ExpectedResult = false)] + [TestCase("ws://hello.com/", UriKind.Relative, ExpectedResult = false)] + [TestCase("wss://hello.com/", UriKind.Relative, ExpectedResult = false)] + [TestCase("https://hello.com:8080/", UriKind.Relative, ExpectedResult = false)] + [TestCase("http://hello.com:8080/", UriKind.Relative, ExpectedResult = false)] + [TestCase("https://hello.com/path", UriKind.Relative, ExpectedResult = false)] + [TestCase("http://hello.com/path", UriKind.Relative, ExpectedResult = false)] + [TestCase("https://hello.com/path?query=param", UriKind.Relative, ExpectedResult = false)] + [TestCase("http://hello.com/path?query=param", UriKind.Relative, ExpectedResult = false)] + [TestCase("https://hello.com/path#fragment", UriKind.Relative, ExpectedResult = false)] + [TestCase("http://hello.com/path#fragment", UriKind.Relative, ExpectedResult = false)] + [TestCase("https://hello.com/path?query=param#fragment", UriKind.Relative, ExpectedResult = false)] + [TestCase("http://hello.com/path?query=param#fragment", UriKind.Relative, ExpectedResult = false)] + [TestCase("https://hello.com:8080/path?query=param#fragment", UriKind.Relative, ExpectedResult = false)] + [TestCase("http://hello.com:8080/path?query=param#fragment", UriKind.Relative, ExpectedResult = false)] + [TestCase("//hello.com:8080/path?query=param#fragment", UriKind.Relative, ExpectedResult = false)] + [TestCase("//hello.com:8080/path", UriKind.Relative, ExpectedResult = false)] + [TestCase("//hello.com:8080", UriKind.Relative, ExpectedResult = false)] + [TestCase("//hello.com", UriKind.Relative, ExpectedResult = false)] + [TestCase("/test/test.jpg", UriKind.Relative, ExpectedResult = true)] + [TestCase("/test", UriKind.Relative, ExpectedResult = true)] + [TestCase("test", UriKind.Relative, ExpectedResult = true)] + [TestCase("", UriKind.Relative, ExpectedResult = false)] + [TestCase(null, UriKind.Relative, ExpectedResult = false)] + [TestCase("this is not welformed", UriKind.Relative, ExpectedResult = false)] + [TestCase("ftp://hello.com/", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("file:///hello.com/", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("ws://hello.com/", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("wss://hello.com/", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("https://hello.com:8080/", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("http://hello.com:8080/", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("https://hello.com/path", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("http://hello.com/path", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("https://hello.com/path?query=param", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("http://hello.com/path?query=param", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("https://hello.com/path#fragment", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("http://hello.com/path#fragment", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("https://hello.com/path?query=param#fragment", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("http://hello.com/path?query=param#fragment", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("https://hello.com:8080/path?query=param#fragment", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("http://hello.com:8080/path?query=param#fragment", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("//hello.com:8080/path?query=param#fragment", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("//hello.com:8080/path", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("//hello.com:8080", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("//hello.com", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("/test/test.jpg", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("/test", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("test", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("", UriKind.RelativeOrAbsolute, ExpectedResult = false)] + [TestCase(null, UriKind.RelativeOrAbsolute, ExpectedResult = false)] + [TestCase("this is not welformed", UriKind.RelativeOrAbsolute, ExpectedResult = false)] + public bool IsWellFormedWebPath(string? webPath, UriKind uriKind) => WebPath.IsWellFormedWebPath(webPath, uriKind); + } From 5f24de308584b9771240a6db1a34630a5114c450 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Fri, 17 May 2024 08:37:51 +0200 Subject: [PATCH 022/113] Merge pull request from GHSA-j74q-mv2c-rxmp --- src/Umbraco.Core/Routing/WebPath.cs | 24 ++++++ .../Controllers/ImagesController.cs | 3 +- .../Controllers/PreviewController.cs | 4 +- .../Umbraco.Core/Routing/WebPathTests.cs | 83 +++++++++++++++++++ 4 files changed, 111 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Core/Routing/WebPath.cs b/src/Umbraco.Core/Routing/WebPath.cs index 7ecafff8a3..47c6b15871 100644 --- a/src/Umbraco.Core/Routing/WebPath.cs +++ b/src/Umbraco.Core/Routing/WebPath.cs @@ -50,4 +50,28 @@ public class WebPath return sb.ToString(); } + + + /// + /// Determines whether the provided web path is well-formed according to the specified UriKind. + /// + /// The web path to check. This can be null. + /// The kind of Uri (Absolute, Relative, or RelativeOrAbsolute). + /// + /// true if is well-formed; otherwise, false. + /// + public static bool IsWellFormedWebPath(string? webPath, UriKind uriKind) + { + if (string.IsNullOrWhiteSpace(webPath)) + { + return false; + } + + if (webPath.StartsWith("//")) + { + return uriKind is not UriKind.Relative; + } + + return Uri.IsWellFormedUriString(webPath, uriKind); + } } diff --git a/src/Umbraco.Web.BackOffice/Controllers/ImagesController.cs b/src/Umbraco.Web.BackOffice/Controllers/ImagesController.cs index 90ef6e6cf4..b70661c0af 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/ImagesController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/ImagesController.cs @@ -8,6 +8,7 @@ using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Media; using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Routing; using Umbraco.Cms.Web.Common.Attributes; using Umbraco.Cms.Web.Common.DependencyInjection; using Umbraco.Extensions; @@ -122,7 +123,7 @@ public class ImagesController : UmbracoAuthorizedApiController private bool IsAllowed(string encodedImagePath) { - if(Uri.IsWellFormedUriString(encodedImagePath, UriKind.Relative)) + if(WebPath.IsWellFormedWebPath(encodedImagePath, UriKind.Relative)) { return true; } diff --git a/src/Umbraco.Web.BackOffice/Controllers/PreviewController.cs b/src/Umbraco.Web.BackOffice/Controllers/PreviewController.cs index 19ca323d9d..17875c2950 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/PreviewController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/PreviewController.cs @@ -11,6 +11,7 @@ using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Membership; using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Cms.Core.PublishedCache; +using Umbraco.Cms.Core.Routing; using Umbraco.Cms.Core.Security; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Web; @@ -152,8 +153,7 @@ public class PreviewController : Controller // Expire Client-side cookie that determines whether the user has accepted to be in Preview Mode when visiting the website. _cookieManager.ExpireCookie(Constants.Web.AcceptPreviewCookieName); - if (Uri.IsWellFormedUriString(redir, UriKind.Relative) - && redir.StartsWith("//") == false + if (WebPath.IsWellFormedWebPath(redir, UriKind.Relative) && Uri.TryCreate(redir, UriKind.Relative, out Uri? url)) { return Redirect(url.ToString()); diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/WebPathTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/WebPathTests.cs index 4072e3df8b..72ab9150bc 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/WebPathTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/WebPathTests.cs @@ -30,4 +30,87 @@ public class WebPathTests [Test] public void Combine_must_handle_null() => Assert.Throws(() => WebPath.Combine(null)); + + + [Test] + [TestCase("ftp://hello.com/", UriKind.Absolute, ExpectedResult = true)] + [TestCase("file:///hello.com/", UriKind.Absolute, ExpectedResult = true)] + [TestCase("ws://hello.com/", UriKind.Absolute, ExpectedResult = true)] + [TestCase("wss://hello.com/", UriKind.Absolute, ExpectedResult = true)] + [TestCase("https://hello.com:8080/", UriKind.Absolute, ExpectedResult = true)] + [TestCase("http://hello.com:8080/", UriKind.Absolute, ExpectedResult = true)] + [TestCase("https://hello.com/path", UriKind.Absolute, ExpectedResult = true)] + [TestCase("http://hello.com/path", UriKind.Absolute, ExpectedResult = true)] + [TestCase("https://hello.com/path?query=param", UriKind.Absolute, ExpectedResult = true)] + [TestCase("http://hello.com/path?query=param", UriKind.Absolute, ExpectedResult = true)] + [TestCase("https://hello.com/path#fragment", UriKind.Absolute, ExpectedResult = true)] + [TestCase("http://hello.com/path#fragment", UriKind.Absolute, ExpectedResult = true)] + [TestCase("https://hello.com/path?query=param#fragment", UriKind.Absolute, ExpectedResult = true)] + [TestCase("http://hello.com/path?query=param#fragment", UriKind.Absolute, ExpectedResult = true)] + [TestCase("https://hello.com:8080/path?query=param#fragment", UriKind.Absolute, ExpectedResult = true)] + [TestCase("http://hello.com:8080/path?query=param#fragment", UriKind.Absolute, ExpectedResult = true)] + [TestCase("//hello.com:8080/path?query=param#fragment", UriKind.Absolute, ExpectedResult = true)] + [TestCase("//hello.com:8080/path", UriKind.Absolute, ExpectedResult = true)] + [TestCase("//hello.com:8080", UriKind.Absolute, ExpectedResult = true)] + [TestCase("//hello.com", UriKind.Absolute, ExpectedResult = true)] + [TestCase("/test/test.jpg", UriKind.Absolute, ExpectedResult = false)] + [TestCase("/test", UriKind.Absolute, ExpectedResult = false)] + [TestCase("test", UriKind.Absolute, ExpectedResult = false)] + [TestCase("", UriKind.Absolute, ExpectedResult = false)] + [TestCase(null, UriKind.Absolute, ExpectedResult = false)] + [TestCase("this is not welformed", UriKind.Absolute, ExpectedResult = false)] + [TestCase("ftp://hello.com/", UriKind.Relative, ExpectedResult = false)] + [TestCase("file:///hello.com/", UriKind.Relative, ExpectedResult = false)] + [TestCase("ws://hello.com/", UriKind.Relative, ExpectedResult = false)] + [TestCase("wss://hello.com/", UriKind.Relative, ExpectedResult = false)] + [TestCase("https://hello.com:8080/", UriKind.Relative, ExpectedResult = false)] + [TestCase("http://hello.com:8080/", UriKind.Relative, ExpectedResult = false)] + [TestCase("https://hello.com/path", UriKind.Relative, ExpectedResult = false)] + [TestCase("http://hello.com/path", UriKind.Relative, ExpectedResult = false)] + [TestCase("https://hello.com/path?query=param", UriKind.Relative, ExpectedResult = false)] + [TestCase("http://hello.com/path?query=param", UriKind.Relative, ExpectedResult = false)] + [TestCase("https://hello.com/path#fragment", UriKind.Relative, ExpectedResult = false)] + [TestCase("http://hello.com/path#fragment", UriKind.Relative, ExpectedResult = false)] + [TestCase("https://hello.com/path?query=param#fragment", UriKind.Relative, ExpectedResult = false)] + [TestCase("http://hello.com/path?query=param#fragment", UriKind.Relative, ExpectedResult = false)] + [TestCase("https://hello.com:8080/path?query=param#fragment", UriKind.Relative, ExpectedResult = false)] + [TestCase("http://hello.com:8080/path?query=param#fragment", UriKind.Relative, ExpectedResult = false)] + [TestCase("//hello.com:8080/path?query=param#fragment", UriKind.Relative, ExpectedResult = false)] + [TestCase("//hello.com:8080/path", UriKind.Relative, ExpectedResult = false)] + [TestCase("//hello.com:8080", UriKind.Relative, ExpectedResult = false)] + [TestCase("//hello.com", UriKind.Relative, ExpectedResult = false)] + [TestCase("/test/test.jpg", UriKind.Relative, ExpectedResult = true)] + [TestCase("/test", UriKind.Relative, ExpectedResult = true)] + [TestCase("test", UriKind.Relative, ExpectedResult = true)] + [TestCase("", UriKind.Relative, ExpectedResult = false)] + [TestCase(null, UriKind.Relative, ExpectedResult = false)] + [TestCase("this is not welformed", UriKind.Relative, ExpectedResult = false)] + [TestCase("ftp://hello.com/", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("file:///hello.com/", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("ws://hello.com/", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("wss://hello.com/", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("https://hello.com:8080/", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("http://hello.com:8080/", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("https://hello.com/path", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("http://hello.com/path", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("https://hello.com/path?query=param", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("http://hello.com/path?query=param", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("https://hello.com/path#fragment", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("http://hello.com/path#fragment", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("https://hello.com/path?query=param#fragment", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("http://hello.com/path?query=param#fragment", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("https://hello.com:8080/path?query=param#fragment", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("http://hello.com:8080/path?query=param#fragment", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("//hello.com:8080/path?query=param#fragment", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("//hello.com:8080/path", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("//hello.com:8080", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("//hello.com", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("/test/test.jpg", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("/test", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("test", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("", UriKind.RelativeOrAbsolute, ExpectedResult = false)] + [TestCase(null, UriKind.RelativeOrAbsolute, ExpectedResult = false)] + [TestCase("this is not welformed", UriKind.RelativeOrAbsolute, ExpectedResult = false)] + public bool IsWellFormedWebPath(string? webPath, UriKind uriKind) => WebPath.IsWellFormedWebPath(webPath, uriKind); + } From 0f3160f727f33c10141f3b9cb55e9d7b97e805a1 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Wed, 22 May 2024 12:17:04 +0200 Subject: [PATCH 023/113] Move publishing notification after validation (#16331) --- src/Umbraco.Core/Services/ContentService.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs index 839a5afe90..e723bfcdd2 100644 --- a/src/Umbraco.Core/Services/ContentService.cs +++ b/src/Umbraco.Core/Services/ContentService.cs @@ -1487,6 +1487,14 @@ public class ContentService : RepositoryService, IContentService notificationState); if (publishResult.Success) { + // raise Publishing notification + if (scope.Notifications.PublishCancelable( + new ContentPublishingNotification(content, eventMessages).WithState(notificationState))) + { + _logger.LogInformation("Document {ContentName} (id={ContentId}) cannot be published: {Reason}", content.Name, content.Id, "publishing was cancelled"); + return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, eventMessages, content); + } + // note: StrategyPublish flips the PublishedState to Publishing! publishResult = StrategyPublish(content, culturesPublishing, culturesUnpublishing, eventMessages); @@ -3065,14 +3073,6 @@ public class ContentService : RepositoryService, IContentService IReadOnlyCollection allLangs, IDictionary? notificationState) { - // raise Publishing notification - if (scope.Notifications.PublishCancelable( - new ContentPublishingNotification(content, evtMsgs).WithState(notificationState))) - { - _logger.LogInformation("Document {ContentName} (id={ContentId}) cannot be published: {Reason}", content.Name, content.Id, "publishing was cancelled"); - return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, content); - } - var variesByCulture = content.ContentType.VariesByCulture(); // If it's null it's invariant From fd2138c1fd0dfd74e9bd5152a35fff3a00c95ff6 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Wed, 22 May 2024 12:21:56 +0200 Subject: [PATCH 024/113] Bump version --- version.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.json b/version.json index fcf4a18f98..c16578e335 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.3.1", + "version": "13.3.2", "assemblyVersion": { "precision": "build" }, From eb6bb99eafaf21f4bf9d241b7385aa1572b6f32d Mon Sep 17 00:00:00 2001 From: Mole Date: Wed, 22 May 2024 13:29:17 +0200 Subject: [PATCH 025/113] Ensure there is always at least 1 valid candidate (#16344) --- .../Routing/EagerMatcherPolicy.cs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.Website/Routing/EagerMatcherPolicy.cs b/src/Umbraco.Web.Website/Routing/EagerMatcherPolicy.cs index 3fe0814a15..4c68d3428e 100644 --- a/src/Umbraco.Web.Website/Routing/EagerMatcherPolicy.cs +++ b/src/Umbraco.Web.Website/Routing/EagerMatcherPolicy.cs @@ -72,7 +72,8 @@ internal class EagerMatcherPolicy : MatcherPolicy, IEndpointSelectorPolicy } // If there's only one candidate, we don't need to do anything. - if (candidates.Count < 2) + var candidateCount = candidates.Count; + if (candidateCount < 2) { return; } @@ -85,6 +86,14 @@ internal class EagerMatcherPolicy : MatcherPolicy, IEndpointSelectorPolicy RouteEndpoint? dynamicEndpoint = null; for (var i = 0; i < candidates.Count; i++) { + if (candidates.IsValidCandidate(i) is false) + { + // If the candidate is not valid we reduce the candidate count so we can later ensure that there is always + // at least 1 candidate. + candidateCount -= 1; + continue; + } + CandidateState candidate = candidates[i]; // If it's not a RouteEndpoint there's not much we can do to count it in the order. @@ -123,7 +132,7 @@ internal class EagerMatcherPolicy : MatcherPolicy, IEndpointSelectorPolicy // Invalidate the dynamic route if another route has a lower order. // This means that if you register your static route after the dynamic route, the dynamic route will take precedence // This more closely resembles the existing behaviour. - if (dynamicEndpoint is not null && dynamicId is not null && dynamicEndpoint.Order > lowestOrder) + if (dynamicEndpoint is not null && dynamicId is not null && dynamicEndpoint.Order > lowestOrder && candidateCount > 1) { candidates.SetValidity(dynamicId.Value, false); } From 5f082df3ab406fe6c0d424d8eec9a6fc35fdaa43 Mon Sep 17 00:00:00 2001 From: Mole Date: Wed, 22 May 2024 13:29:17 +0200 Subject: [PATCH 026/113] Ensure there is always at least 1 valid candidate (#16344) (cherry picked from commit eb6bb99eafaf21f4bf9d241b7385aa1572b6f32d) --- .../Routing/EagerMatcherPolicy.cs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.Website/Routing/EagerMatcherPolicy.cs b/src/Umbraco.Web.Website/Routing/EagerMatcherPolicy.cs index 3fe0814a15..4c68d3428e 100644 --- a/src/Umbraco.Web.Website/Routing/EagerMatcherPolicy.cs +++ b/src/Umbraco.Web.Website/Routing/EagerMatcherPolicy.cs @@ -72,7 +72,8 @@ internal class EagerMatcherPolicy : MatcherPolicy, IEndpointSelectorPolicy } // If there's only one candidate, we don't need to do anything. - if (candidates.Count < 2) + var candidateCount = candidates.Count; + if (candidateCount < 2) { return; } @@ -85,6 +86,14 @@ internal class EagerMatcherPolicy : MatcherPolicy, IEndpointSelectorPolicy RouteEndpoint? dynamicEndpoint = null; for (var i = 0; i < candidates.Count; i++) { + if (candidates.IsValidCandidate(i) is false) + { + // If the candidate is not valid we reduce the candidate count so we can later ensure that there is always + // at least 1 candidate. + candidateCount -= 1; + continue; + } + CandidateState candidate = candidates[i]; // If it's not a RouteEndpoint there's not much we can do to count it in the order. @@ -123,7 +132,7 @@ internal class EagerMatcherPolicy : MatcherPolicy, IEndpointSelectorPolicy // Invalidate the dynamic route if another route has a lower order. // This means that if you register your static route after the dynamic route, the dynamic route will take precedence // This more closely resembles the existing behaviour. - if (dynamicEndpoint is not null && dynamicId is not null && dynamicEndpoint.Order > lowestOrder) + if (dynamicEndpoint is not null && dynamicId is not null && dynamicEndpoint.Order > lowestOrder && candidateCount > 1) { candidates.SetValidity(dynamicId.Value, false); } From 696a71166cd4d4e01ab7fa7083ae8a59bbb6b6eb Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Thu, 23 May 2024 10:14:37 +0200 Subject: [PATCH 027/113] Ensure ufprt-token requests are handle in the UmbracoRouteValueTransformer (#16347) --- src/Umbraco.Web.Website/Routing/EagerMatcherPolicy.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.Website/Routing/EagerMatcherPolicy.cs b/src/Umbraco.Web.Website/Routing/EagerMatcherPolicy.cs index 4c68d3428e..05290227b3 100644 --- a/src/Umbraco.Web.Website/Routing/EagerMatcherPolicy.cs +++ b/src/Umbraco.Web.Website/Routing/EagerMatcherPolicy.cs @@ -9,6 +9,7 @@ using Umbraco.Cms.Core.Routing; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Web.Common.Controllers; using Umbraco.Extensions; +using HttpRequestExtensions = Umbraco.Extensions.HttpRequestExtensions; namespace Umbraco.Cms.Web.Website.Routing; @@ -71,9 +72,10 @@ internal class EagerMatcherPolicy : MatcherPolicy, IEndpointSelectorPolicy } } - // If there's only one candidate, we don't need to do anything. + // If there's only one candidate, or the request has the ufprt-token, we don't need to do anything . + // The ufprt-token is handled by the the and should not be discarded. var candidateCount = candidates.Count; - if (candidateCount < 2) + if (candidateCount < 2 || string.IsNullOrEmpty(httpContext.Request.GetUfprt()) is false) { return; } From 5795cf119a0b5ab148019ed39d92ecbc5bd7b14c Mon Sep 17 00:00:00 2001 From: Johan Runsten Date: Fri, 12 Apr 2024 23:37:19 +0200 Subject: [PATCH 028/113] Typo when getting query parm --- .../src/components/external-login-provider.element.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 b48943e019..0729f97027 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 @@ -73,7 +73,7 @@ export class UmbExternalLoginProviderElement extends LitElement { @property({attribute: 'external-login-url'}) set externalLoginUrl(value: string) { const tempUrl = new URL(value, window.location.origin); - const searchParams = new URLSearchParams(tempUrl.search); + const searchParams = new URLSearchParams(window.location.search); tempUrl.searchParams.append('redirectUrl', decodeURIComponent(searchParams.get('returnPath') ?? '')); this.#externalLoginUrl = tempUrl.pathname + tempUrl.search; } From 0aaac78cfa119fd4ff23eba9b3e92d0bd8510358 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Mon, 3 Jun 2024 11:23:25 +0200 Subject: [PATCH 029/113] A bunch of minor performance optimizations (#16335) * Do not execute query if no macros found * Request cache the permission lookup * Unbreak change by adding obsolete ctor * Clean up * Wrap indexing for delivery API in a scope * Do not ask options every time for the timeout, instead listen for updates * Lookup content types once instead of one by one * Use TryGetValue instead * Do a distinct on user ids before building index, to avoid issue with more than 2100 parameters * Don't map ContentDto (it's unused) * Introduce request bound block editor element cache --------- Co-authored-by: kjac --- ...ServerEFCoreDistributedLockingMechanism.cs | 22 +++++----- ...SqliteEFCoreDistributedLockingMechanism.cs | 18 ++++---- .../SqlServerDistributedLockingMechanism.cs | 19 ++++---- .../SqliteDistributedLockingMechanism.cs | 18 ++++---- .../ContentEditing/ContentItemDisplay.cs | 1 + src/Umbraco.Core/Services/UserService.cs | 43 +++++++++++++++---- .../BlockEditorElementTypeCache.cs | 32 ++++++++++++++ .../IBlockEditorElementTypeCache.cs | 8 ++++ .../UmbracoBuilder.CoreServices.cs | 3 ++ .../Examine/ContentValueSetBuilder.cs | 4 +- .../DeliveryApiContentIndexValueSetBuilder.cs | 36 +++++++++++++++- .../PropertyEditors/BlockEditorValidator.cs | 5 ++- .../BlockEditorValidatorBase.cs | 11 ++--- .../PropertyEditors/BlockEditorValues.cs | 22 +++++----- .../BlockGridPropertyEditorBase.cs | 7 +-- .../BlockListPropertyEditorBase.cs | 7 +-- .../RichTextEditorBlockValidator.cs | 5 ++- .../PropertyEditors/RichTextPropertyEditor.cs | 11 ++--- .../Templates/HtmlMacroParameterParser.cs | 19 ++++---- .../Mapping/ContentMapDefinition.cs | 13 ++---- .../DataValueEditorReuseTests.cs | 3 +- 21 files changed, 210 insertions(+), 97 deletions(-) create mode 100644 src/Umbraco.Infrastructure/Cache/PropertyEditors/BlockEditorElementTypeCache.cs create mode 100644 src/Umbraco.Infrastructure/Cache/PropertyEditors/IBlockEditorElementTypeCache.cs diff --git a/src/Umbraco.Cms.Persistence.EFCore/Locking/SqlServerEFCoreDistributedLockingMechanism.cs b/src/Umbraco.Cms.Persistence.EFCore/Locking/SqlServerEFCoreDistributedLockingMechanism.cs index 4652c513a3..38cdeef114 100644 --- a/src/Umbraco.Cms.Persistence.EFCore/Locking/SqlServerEFCoreDistributedLockingMechanism.cs +++ b/src/Umbraco.Cms.Persistence.EFCore/Locking/SqlServerEFCoreDistributedLockingMechanism.cs @@ -16,8 +16,8 @@ namespace Umbraco.Cms.Persistence.EFCore.Locking; internal class SqlServerEFCoreDistributedLockingMechanism : IDistributedLockingMechanism where T : DbContext { - private readonly IOptionsMonitor _connectionStrings; - private readonly IOptionsMonitor _globalSettings; + private ConnectionStrings _connectionStrings; + private GlobalSettings _globalSettings; private readonly ILogger> _logger; private readonly Lazy> _scopeAccessor; // Hooray it's a circular dependency. @@ -32,27 +32,29 @@ internal class SqlServerEFCoreDistributedLockingMechanism : IDistributedLocki { _logger = logger; _scopeAccessor = scopeAccessor; - _globalSettings = globalSettings; - _connectionStrings = connectionStrings; + _globalSettings = globalSettings.CurrentValue; + _connectionStrings = connectionStrings.CurrentValue; + globalSettings.OnChange(x=>_globalSettings = x); + connectionStrings.OnChange(x=>_connectionStrings = x); } public bool HasActiveRelatedScope => _scopeAccessor.Value.AmbientScope is not null; /// - public bool Enabled => _connectionStrings.CurrentValue.IsConnectionStringConfigured() && - string.Equals(_connectionStrings.CurrentValue.ProviderName, "Microsoft.Data.SqlClient", StringComparison.InvariantCultureIgnoreCase) && _scopeAccessor.Value.AmbientScope is not null; + public bool Enabled => _connectionStrings.IsConnectionStringConfigured() && + string.Equals(_connectionStrings.ProviderName, "Microsoft.Data.SqlClient", StringComparison.InvariantCultureIgnoreCase) && _scopeAccessor.Value.AmbientScope is not null; /// public IDistributedLock ReadLock(int lockId, TimeSpan? obtainLockTimeout = null) { - obtainLockTimeout ??= _globalSettings.CurrentValue.DistributedLockingReadLockDefaultTimeout; + obtainLockTimeout ??= _globalSettings.DistributedLockingReadLockDefaultTimeout; return new SqlServerDistributedLock(this, lockId, DistributedLockType.ReadLock, obtainLockTimeout.Value); } /// public IDistributedLock WriteLock(int lockId, TimeSpan? obtainLockTimeout = null) { - obtainLockTimeout ??= _globalSettings.CurrentValue.DistributedLockingWriteLockDefaultTimeout; + obtainLockTimeout ??= _globalSettings.DistributedLockingWriteLockDefaultTimeout; return new SqlServerDistributedLock(this, lockId, DistributedLockType.WriteLock, obtainLockTimeout.Value); } @@ -168,9 +170,7 @@ internal class SqlServerEFCoreDistributedLockingMechanism : IDistributedLocki "A transaction with minimum ReadCommitted isolation level is required."); } - await dbContext.Database.ExecuteSqlRawAsync($"SET LOCK_TIMEOUT {(int)_timeout.TotalMilliseconds};"); - - var rowsAffected = await dbContext.Database.ExecuteSqlAsync(@$"UPDATE umbracoLock WITH (REPEATABLEREAD) SET value = (CASE WHEN (value=1) THEN -1 ELSE 1 END) WHERE id={LockId}"); + var rowsAffected = await dbContext.Database.ExecuteSqlAsync(@$"SET LOCK_TIMEOUT {(int)_timeout.TotalMilliseconds};UPDATE umbracoLock WITH (REPEATABLEREAD) SET value = (CASE WHEN (value=1) THEN -1 ELSE 1 END) WHERE id={LockId}"); if (rowsAffected == 0) { diff --git a/src/Umbraco.Cms.Persistence.EFCore/Locking/SqliteEFCoreDistributedLockingMechanism.cs b/src/Umbraco.Cms.Persistence.EFCore/Locking/SqliteEFCoreDistributedLockingMechanism.cs index 8d92ec0e03..23b3d8d410 100644 --- a/src/Umbraco.Cms.Persistence.EFCore/Locking/SqliteEFCoreDistributedLockingMechanism.cs +++ b/src/Umbraco.Cms.Persistence.EFCore/Locking/SqliteEFCoreDistributedLockingMechanism.cs @@ -16,8 +16,8 @@ namespace Umbraco.Cms.Persistence.EFCore.Locking; internal class SqliteEFCoreDistributedLockingMechanism : IDistributedLockingMechanism where T : DbContext { - private readonly IOptionsMonitor _connectionStrings; - private readonly IOptionsMonitor _globalSettings; + private ConnectionStrings _connectionStrings; + private GlobalSettings _globalSettings; private readonly ILogger> _logger; private readonly Lazy> _efCoreScopeAccessor; @@ -29,27 +29,29 @@ internal class SqliteEFCoreDistributedLockingMechanism : IDistributedLockingM { _logger = logger; _efCoreScopeAccessor = efCoreScopeAccessor; - _connectionStrings = connectionStrings; - _globalSettings = globalSettings; + _globalSettings = globalSettings.CurrentValue; + _connectionStrings = connectionStrings.CurrentValue; + globalSettings.OnChange(x=>_globalSettings = x); + connectionStrings.OnChange(x=>_connectionStrings = x); } public bool HasActiveRelatedScope => _efCoreScopeAccessor.Value.AmbientScope is not null; /// - public bool Enabled => _connectionStrings.CurrentValue.IsConnectionStringConfigured() && - string.Equals(_connectionStrings.CurrentValue.ProviderName, "Microsoft.Data.Sqlite", StringComparison.InvariantCultureIgnoreCase) && _efCoreScopeAccessor.Value.AmbientScope is not null; + public bool Enabled => _connectionStrings.IsConnectionStringConfigured() && + string.Equals(_connectionStrings.ProviderName, "Microsoft.Data.Sqlite", StringComparison.InvariantCultureIgnoreCase) && _efCoreScopeAccessor.Value.AmbientScope is not null; // With journal_mode=wal we can always read a snapshot. public IDistributedLock ReadLock(int lockId, TimeSpan? obtainLockTimeout = null) { - obtainLockTimeout ??= _globalSettings.CurrentValue.DistributedLockingReadLockDefaultTimeout; + obtainLockTimeout ??= _globalSettings.DistributedLockingReadLockDefaultTimeout; return new SqliteDistributedLock(this, lockId, DistributedLockType.ReadLock, obtainLockTimeout.Value); } // With journal_mode=wal only a single write transaction can exist at a time. public IDistributedLock WriteLock(int lockId, TimeSpan? obtainLockTimeout = null) { - obtainLockTimeout ??= _globalSettings.CurrentValue.DistributedLockingWriteLockDefaultTimeout; + obtainLockTimeout ??= _globalSettings.DistributedLockingWriteLockDefaultTimeout; return new SqliteDistributedLock(this, lockId, DistributedLockType.WriteLock, obtainLockTimeout.Value); } diff --git a/src/Umbraco.Cms.Persistence.SqlServer/Services/SqlServerDistributedLockingMechanism.cs b/src/Umbraco.Cms.Persistence.SqlServer/Services/SqlServerDistributedLockingMechanism.cs index 77975e8f31..a7f183e57a 100644 --- a/src/Umbraco.Cms.Persistence.SqlServer/Services/SqlServerDistributedLockingMechanism.cs +++ b/src/Umbraco.Cms.Persistence.SqlServer/Services/SqlServerDistributedLockingMechanism.cs @@ -17,8 +17,8 @@ namespace Umbraco.Cms.Persistence.SqlServer.Services; /// public class SqlServerDistributedLockingMechanism : IDistributedLockingMechanism { - private readonly IOptionsMonitor _connectionStrings; - private readonly IOptionsMonitor _globalSettings; + private ConnectionStrings _connectionStrings; + private GlobalSettings _globalSettings; private readonly ILogger _logger; private readonly Lazy _scopeAccessor; // Hooray it's a circular dependency. @@ -33,25 +33,28 @@ public class SqlServerDistributedLockingMechanism : IDistributedLockingMechanism { _logger = logger; _scopeAccessor = scopeAccessor; - _globalSettings = globalSettings; - _connectionStrings = connectionStrings; + _globalSettings = globalSettings.CurrentValue; + _connectionStrings = connectionStrings.CurrentValue; + globalSettings.OnChange(x => _globalSettings = x); + connectionStrings.OnChange(x => _connectionStrings = x); + } /// - public bool Enabled => _connectionStrings.CurrentValue.IsConnectionStringConfigured() && - string.Equals(_connectionStrings.CurrentValue.ProviderName,Constants.ProviderName, StringComparison.InvariantCultureIgnoreCase); + public bool Enabled => _connectionStrings.IsConnectionStringConfigured() && + string.Equals(_connectionStrings.ProviderName,Constants.ProviderName, StringComparison.InvariantCultureIgnoreCase); /// public IDistributedLock ReadLock(int lockId, TimeSpan? obtainLockTimeout = null) { - obtainLockTimeout ??= _globalSettings.CurrentValue.DistributedLockingReadLockDefaultTimeout; + obtainLockTimeout ??= _globalSettings.DistributedLockingReadLockDefaultTimeout; return new SqlServerDistributedLock(this, lockId, DistributedLockType.ReadLock, obtainLockTimeout.Value); } /// public IDistributedLock WriteLock(int lockId, TimeSpan? obtainLockTimeout = null) { - obtainLockTimeout ??= _globalSettings.CurrentValue.DistributedLockingWriteLockDefaultTimeout; + obtainLockTimeout ??= _globalSettings.DistributedLockingWriteLockDefaultTimeout; return new SqlServerDistributedLock(this, lockId, DistributedLockType.WriteLock, obtainLockTimeout.Value); } diff --git a/src/Umbraco.Cms.Persistence.Sqlite/Services/SqliteDistributedLockingMechanism.cs b/src/Umbraco.Cms.Persistence.Sqlite/Services/SqliteDistributedLockingMechanism.cs index 54e30d6fa6..f43a1eff05 100644 --- a/src/Umbraco.Cms.Persistence.Sqlite/Services/SqliteDistributedLockingMechanism.cs +++ b/src/Umbraco.Cms.Persistence.Sqlite/Services/SqliteDistributedLockingMechanism.cs @@ -16,8 +16,8 @@ namespace Umbraco.Cms.Persistence.Sqlite.Services; public class SqliteDistributedLockingMechanism : IDistributedLockingMechanism { - private readonly IOptionsMonitor _connectionStrings; - private readonly IOptionsMonitor _globalSettings; + private ConnectionStrings _connectionStrings; + private GlobalSettings _globalSettings; private readonly ILogger _logger; private readonly Lazy _scopeAccessor; @@ -29,25 +29,27 @@ public class SqliteDistributedLockingMechanism : IDistributedLockingMechanism { _logger = logger; _scopeAccessor = scopeAccessor; - _connectionStrings = connectionStrings; - _globalSettings = globalSettings; + _connectionStrings = connectionStrings.CurrentValue; + _globalSettings = globalSettings.CurrentValue; + globalSettings.OnChange(x=>_globalSettings = x); + connectionStrings.OnChange(x=>_connectionStrings = x); } /// - public bool Enabled => _connectionStrings.CurrentValue.IsConnectionStringConfigured() && - string.Equals(_connectionStrings.CurrentValue.ProviderName, Constants.ProviderName, StringComparison.InvariantCultureIgnoreCase); + public bool Enabled => _connectionStrings.IsConnectionStringConfigured() && + string.Equals(_connectionStrings.ProviderName, Constants.ProviderName, StringComparison.InvariantCultureIgnoreCase); // With journal_mode=wal we can always read a snapshot. public IDistributedLock ReadLock(int lockId, TimeSpan? obtainLockTimeout = null) { - obtainLockTimeout ??= _globalSettings.CurrentValue.DistributedLockingReadLockDefaultTimeout; + obtainLockTimeout ??= _globalSettings.DistributedLockingReadLockDefaultTimeout; return new SqliteDistributedLock(this, lockId, DistributedLockType.ReadLock, obtainLockTimeout.Value); } // With journal_mode=wal only a single write transaction can exist at a time. public IDistributedLock WriteLock(int lockId, TimeSpan? obtainLockTimeout = null) { - obtainLockTimeout ??= _globalSettings.CurrentValue.DistributedLockingWriteLockDefaultTimeout; + obtainLockTimeout ??= _globalSettings.DistributedLockingWriteLockDefaultTimeout; return new SqliteDistributedLock(this, lockId, DistributedLockType.WriteLock, obtainLockTimeout.Value); } diff --git a/src/Umbraco.Core/Models/ContentEditing/ContentItemDisplay.cs b/src/Umbraco.Core/Models/ContentEditing/ContentItemDisplay.cs index d1a8d10970..4e207702a0 100644 --- a/src/Umbraco.Core/Models/ContentEditing/ContentItemDisplay.cs +++ b/src/Umbraco.Core/Models/ContentEditing/ContentItemDisplay.cs @@ -194,6 +194,7 @@ public class ContentItemDisplay : /// This is not used for outgoing model information. /// [IgnoreDataMember] + [Obsolete("No longer used. Will be removed in V15.")] public ContentPropertyCollectionDto? ContentDto { get; set; } /// diff --git a/src/Umbraco.Core/Services/UserService.cs b/src/Umbraco.Core/Services/UserService.cs index 7f839e81d1..e0f65cdd5c 100644 --- a/src/Umbraco.Core/Services/UserService.cs +++ b/src/Umbraco.Core/Services/UserService.cs @@ -1,9 +1,12 @@ using System.Data.Common; using System.Globalization; using System.Linq.Expressions; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models.Membership; using Umbraco.Cms.Core.Notifications; @@ -25,8 +28,11 @@ internal class UserService : RepositoryService, IUserService private readonly ILogger _logger; private readonly IRuntimeState _runtimeState; private readonly IUserGroupRepository _userGroupRepository; + private readonly IRequestCache _requestCache; private readonly IUserRepository _userRepository; + + [Obsolete("Use non-obsolete constructor. This will be removed in Umbraco 15.")] public UserService( ICoreScopeProvider provider, ILoggerFactory loggerFactory, @@ -35,11 +41,26 @@ internal class UserService : RepositoryService, IUserService IUserRepository userRepository, IUserGroupRepository userGroupRepository, IOptions globalSettings) + : this(provider, loggerFactory, eventMessagesFactory, runtimeState, userRepository, userGroupRepository, globalSettings, StaticServiceProvider.Instance.GetRequiredService()) + { + + } + + public UserService( + ICoreScopeProvider provider, + ILoggerFactory loggerFactory, + IEventMessagesFactory eventMessagesFactory, + IRuntimeState runtimeState, + IUserRepository userRepository, + IUserGroupRepository userGroupRepository, + IOptions globalSettings, + IRequestCache requestCache) : base(provider, loggerFactory, eventMessagesFactory) { _runtimeState = runtimeState; _userRepository = userRepository; _userGroupRepository = userGroupRepository; + _requestCache = requestCache; _globalSettings = globalSettings.Value; _logger = loggerFactory.CreateLogger(); } @@ -1125,17 +1146,23 @@ internal class UserService : RepositoryService, IUserService /// Path to check permissions for public EntityPermissionSet GetPermissionsForPath(IUser? user, string? path) { - var nodeIds = path?.GetIdsFromPathReversed(); - - if (nodeIds is null || nodeIds.Length == 0 || user is null) + var result = (EntityPermissionSet?)_requestCache.Get($"{nameof(GetPermissionsForPath)}|{path}|{user?.Id}", () => { - return EntityPermissionSet.Empty(); - } + var nodeIds = path?.GetIdsFromPathReversed(); - // collect all permissions structures for all nodes for all groups belonging to the user - EntityPermission[] groupPermissions = GetPermissionsForPath(user.Groups.ToArray(), nodeIds, true).ToArray(); + if (nodeIds is null || nodeIds.Length == 0 || user is null) + { + return EntityPermissionSet.Empty(); + } + + // collect all permissions structures for all nodes for all groups belonging to the user + EntityPermission[] groupPermissions = GetPermissionsForPath(user.Groups.ToArray(), nodeIds, true).ToArray(); + + return CalculatePermissionsForPathForUser(groupPermissions, nodeIds); + }); + + return result ?? EntityPermissionSet.Empty(); - return CalculatePermissionsForPathForUser(groupPermissions, nodeIds); } /// diff --git a/src/Umbraco.Infrastructure/Cache/PropertyEditors/BlockEditorElementTypeCache.cs b/src/Umbraco.Infrastructure/Cache/PropertyEditors/BlockEditorElementTypeCache.cs new file mode 100644 index 0000000000..5cbf0e6dc2 --- /dev/null +++ b/src/Umbraco.Infrastructure/Cache/PropertyEditors/BlockEditorElementTypeCache.cs @@ -0,0 +1,32 @@ +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Services; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Core.Cache.PropertyEditors; + +internal sealed class BlockEditorElementTypeCache : IBlockEditorElementTypeCache +{ + private readonly IContentTypeService _contentTypeService; + private readonly AppCaches _appCaches; + + public BlockEditorElementTypeCache(IContentTypeService contentTypeService, AppCaches appCaches) + { + _contentTypeService = contentTypeService; + _appCaches = appCaches; + } + + public IEnumerable GetAll(IEnumerable keys) + { + // TODO: make this less dumb; don't fetch all elements, only fetch the items that aren't yet in the cache and amend the cache as more elements are loaded + + const string cacheKey = $"{nameof(BlockEditorElementTypeCache)}_ElementTypes"; + IEnumerable? cachedElements = _appCaches.RequestCache.GetCacheItem>(cacheKey); + if (cachedElements is null) + { + cachedElements = _contentTypeService.GetAllElementTypes(); + _appCaches.RequestCache.Set(cacheKey, cachedElements); + } + + return cachedElements.Where(elementType => keys.Contains(elementType.Key)); + } +} diff --git a/src/Umbraco.Infrastructure/Cache/PropertyEditors/IBlockEditorElementTypeCache.cs b/src/Umbraco.Infrastructure/Cache/PropertyEditors/IBlockEditorElementTypeCache.cs new file mode 100644 index 0000000000..5ab1dc49af --- /dev/null +++ b/src/Umbraco.Infrastructure/Cache/PropertyEditors/IBlockEditorElementTypeCache.cs @@ -0,0 +1,8 @@ +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Core.Cache.PropertyEditors; + +public interface IBlockEditorElementTypeCache +{ + IEnumerable GetAll(IEnumerable keys); +} diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs index 10fbcc1207..77194cef2e 100644 --- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs +++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs @@ -5,6 +5,7 @@ using Microsoft.Extensions.Options; using Serilog; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Cache; +using Umbraco.Cms.Core.Cache.PropertyEditors; using Umbraco.Cms.Core.Configuration; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.DeliveryApi; @@ -235,6 +236,8 @@ public static partial class UmbracoBuilderExtensions builder.AddDeliveryApiCoreServices(); builder.Services.AddTransient(); + builder.Services.AddSingleton(); + return builder; } diff --git a/src/Umbraco.Infrastructure/Examine/ContentValueSetBuilder.cs b/src/Umbraco.Infrastructure/Examine/ContentValueSetBuilder.cs index 860c6199f7..98c28f92df 100644 --- a/src/Umbraco.Infrastructure/Examine/ContentValueSetBuilder.cs +++ b/src/Umbraco.Infrastructure/Examine/ContentValueSetBuilder.cs @@ -128,9 +128,9 @@ public class ContentValueSetBuilder : BaseValueSetBuilder, IContentVal // processing below instead of one by one. using (ICoreScope scope = _scopeProvider.CreateCoreScope()) { - creatorIds = _userService.GetProfilesById(content.Select(x => x.CreatorId).ToArray()) + creatorIds = _userService.GetProfilesById(content.Select(x => x.CreatorId).Distinct().ToArray()) .ToDictionary(x => x.Id, x => x); - writerIds = _userService.GetProfilesById(content.Select(x => x.WriterId).ToArray()) + writerIds = _userService.GetProfilesById(content.Select(x => x.WriterId).Distinct().ToArray()) .ToDictionary(x => x.Id, x => x); scope.Complete(); } diff --git a/src/Umbraco.Infrastructure/Examine/DeliveryApiContentIndexValueSetBuilder.cs b/src/Umbraco.Infrastructure/Examine/DeliveryApiContentIndexValueSetBuilder.cs index e8226d994c..1bbc1d02cf 100644 --- a/src/Umbraco.Infrastructure/Examine/DeliveryApiContentIndexValueSetBuilder.cs +++ b/src/Umbraco.Infrastructure/Examine/DeliveryApiContentIndexValueSetBuilder.cs @@ -7,6 +7,7 @@ using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.DeliveryApi; using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Core.Services; using Umbraco.Extensions; @@ -21,6 +22,7 @@ internal sealed class DeliveryApiContentIndexValueSetBuilder : IDeliveryApiConte private readonly IDeliveryApiContentIndexFieldDefinitionBuilder _deliveryApiContentIndexFieldDefinitionBuilder; private readonly IMemberService _memberService; private readonly IDeliveryApiCompositeIdHandler _deliveryApiCompositeIdHandler; + private readonly ICoreScopeProvider _coreScopeProvider; private DeliveryApiSettings _deliveryApiSettings; [Obsolete("Please use ctor that takes an IDeliveryApiCompositeIdHandler. Scheduled for removal in v15")] @@ -40,8 +42,33 @@ internal sealed class DeliveryApiContentIndexValueSetBuilder : IDeliveryApiConte deliveryApiContentIndexFieldDefinitionBuilder, deliveryApiSettings, memberService, - StaticServiceProvider.Instance.GetRequiredService()) + StaticServiceProvider.Instance.GetRequiredService(), + StaticServiceProvider.Instance.GetRequiredService() + ) { + } + [Obsolete("Please use ctor that takes an IDeliveryApiCompositeIdHandler. Scheduled for removal in v15")] + public DeliveryApiContentIndexValueSetBuilder( + ContentIndexHandlerCollection contentIndexHandlerCollection, + IContentService contentService, + IPublicAccessService publicAccessService, + ILogger logger, + IDeliveryApiContentIndexFieldDefinitionBuilder deliveryApiContentIndexFieldDefinitionBuilder, + IOptionsMonitor deliveryApiSettings, + IMemberService memberService, + IDeliveryApiCompositeIdHandler deliveryApiCompositeIdHandle) + :this( + contentIndexHandlerCollection, + contentService, + publicAccessService, + logger, + deliveryApiContentIndexFieldDefinitionBuilder, + deliveryApiSettings, + memberService, + deliveryApiCompositeIdHandle, + StaticServiceProvider.Instance.GetRequiredService()) + { + } public DeliveryApiContentIndexValueSetBuilder( @@ -52,7 +79,8 @@ internal sealed class DeliveryApiContentIndexValueSetBuilder : IDeliveryApiConte IDeliveryApiContentIndexFieldDefinitionBuilder deliveryApiContentIndexFieldDefinitionBuilder, IOptionsMonitor deliveryApiSettings, IMemberService memberService, - IDeliveryApiCompositeIdHandler deliveryApiCompositeIdHandler) + IDeliveryApiCompositeIdHandler deliveryApiCompositeIdHandler, + ICoreScopeProvider coreScopeProvider) { _contentIndexHandlerCollection = contentIndexHandlerCollection; _publicAccessService = publicAccessService; @@ -60,6 +88,7 @@ internal sealed class DeliveryApiContentIndexValueSetBuilder : IDeliveryApiConte _deliveryApiContentIndexFieldDefinitionBuilder = deliveryApiContentIndexFieldDefinitionBuilder; _memberService = memberService; _deliveryApiCompositeIdHandler = deliveryApiCompositeIdHandler; + _coreScopeProvider = coreScopeProvider; _contentService = contentService; _deliveryApiSettings = deliveryApiSettings.CurrentValue; deliveryApiSettings.OnChange(settings => _deliveryApiSettings = settings); @@ -68,6 +97,7 @@ internal sealed class DeliveryApiContentIndexValueSetBuilder : IDeliveryApiConte /// public IEnumerable GetValueSets(params IContent[] contents) { + using ICoreScope scope = _coreScopeProvider.CreateCoreScope(); FieldDefinitionCollection fieldDefinitions = _deliveryApiContentIndexFieldDefinitionBuilder.Build(); foreach (IContent content in contents.Where(CanIndex)) { @@ -101,6 +131,8 @@ internal sealed class DeliveryApiContentIndexValueSetBuilder : IDeliveryApiConte yield return new ValueSet(_deliveryApiCompositeIdHandler.IndexId(content.Id, indexCulture), IndexTypes.Content, content.ContentType.Alias, indexValues); } + + scope.Complete(); } } diff --git a/src/Umbraco.Infrastructure/PropertyEditors/BlockEditorValidator.cs b/src/Umbraco.Infrastructure/PropertyEditors/BlockEditorValidator.cs index 8e17c6c477..fb58ab042d 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/BlockEditorValidator.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/BlockEditorValidator.cs @@ -1,6 +1,7 @@ // Copyright (c) Umbraco. // See LICENSE for more details. +using Umbraco.Cms.Core.Cache.PropertyEditors; using Umbraco.Cms.Core.Models.Blocks; using Umbraco.Cms.Core.Services; @@ -13,8 +14,8 @@ internal class BlockEditorValidator : BlockEditorValidatorBase public BlockEditorValidator( IPropertyValidationService propertyValidationService, BlockEditorValues blockEditorValues, - IContentTypeService contentTypeService) - : base(propertyValidationService, contentTypeService) + IBlockEditorElementTypeCache elementTypeCache) + : base(propertyValidationService, elementTypeCache) => _blockEditorValues = blockEditorValues; protected override IEnumerable GetElementTypeValidation(object? value) diff --git a/src/Umbraco.Infrastructure/PropertyEditors/BlockEditorValidatorBase.cs b/src/Umbraco.Infrastructure/PropertyEditors/BlockEditorValidatorBase.cs index 977d235229..4d131bc818 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/BlockEditorValidatorBase.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/BlockEditorValidatorBase.cs @@ -1,4 +1,5 @@ -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Cache.PropertyEditors; +using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Blocks; using Umbraco.Cms.Core.Services; @@ -6,11 +7,11 @@ namespace Umbraco.Cms.Core.PropertyEditors; internal abstract class BlockEditorValidatorBase : ComplexEditorValidator { - private readonly IContentTypeService _contentTypeService; + private readonly IBlockEditorElementTypeCache _elementTypeCache; - protected BlockEditorValidatorBase(IPropertyValidationService propertyValidationService, IContentTypeService contentTypeService) + protected BlockEditorValidatorBase(IPropertyValidationService propertyValidationService, IBlockEditorElementTypeCache elementTypeCache) : base(propertyValidationService) - => _contentTypeService = contentTypeService; + => _elementTypeCache = elementTypeCache; protected IEnumerable GetBlockEditorDataValidation(BlockEditorData blockEditorData) { @@ -18,7 +19,7 @@ internal abstract class BlockEditorValidatorBase : ComplexEditorValidator // need to validate that data for each property especially for things like 'required' data to work. // Lookup all element types for all content/settings and then we can populate any empty properties. var allElements = blockEditorData.BlockValue.ContentData.Concat(blockEditorData.BlockValue.SettingsData).ToList(); - var allElementTypes = _contentTypeService.GetAll(allElements.Select(x => x.ContentTypeKey).ToArray()).ToDictionary(x => x.Key); + var allElementTypes = _elementTypeCache.GetAll(allElements.Select(x => x.ContentTypeKey).ToArray()).ToDictionary(x => x.Key); foreach (BlockItemData row in allElements) { diff --git a/src/Umbraco.Infrastructure/PropertyEditors/BlockEditorValues.cs b/src/Umbraco.Infrastructure/PropertyEditors/BlockEditorValues.cs index 3270351838..98dbb85889 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/BlockEditorValues.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/BlockEditorValues.cs @@ -2,9 +2,9 @@ // See LICENSE for more details. using Microsoft.Extensions.Logging; +using Umbraco.Cms.Core.Cache.PropertyEditors; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Blocks; -using Umbraco.Cms.Core.Services; using Umbraco.Extensions; namespace Umbraco.Cms.Core.PropertyEditors; @@ -15,13 +15,13 @@ namespace Umbraco.Cms.Core.PropertyEditors; internal class BlockEditorValues { private readonly BlockEditorDataConverter _dataConverter; - private readonly IContentTypeService _contentTypeService; + private readonly IBlockEditorElementTypeCache _elementTypeCache; private readonly ILogger _logger; - public BlockEditorValues(BlockEditorDataConverter dataConverter, IContentTypeService contentTypeService, ILogger logger) + public BlockEditorValues(BlockEditorDataConverter dataConverter, IBlockEditorElementTypeCache elementTypeCache, ILogger logger) { _dataConverter = dataConverter; - _contentTypeService = contentTypeService; + _elementTypeCache = elementTypeCache; _logger = logger; } @@ -55,10 +55,14 @@ internal class BlockEditorValues var contentTypePropertyTypes = new Dictionary>(); // filter out any content that isn't referenced in the layout references + IEnumerable contentTypeKeys = blockEditorData.BlockValue.ContentData.Select(x => x.ContentTypeKey) + .Union(blockEditorData.BlockValue.SettingsData.Select(x => x.ContentTypeKey)).Distinct(); + IDictionary contentTypesDictionary = _elementTypeCache.GetAll(contentTypeKeys).ToDictionary(x=>x.Key); + foreach (BlockItemData block in blockEditorData.BlockValue.ContentData.Where(x => blockEditorData.References.Any(r => x.Udi is not null && r.ContentUdi == x.Udi))) { - ResolveBlockItemData(block, contentTypePropertyTypes); + ResolveBlockItemData(block, contentTypePropertyTypes, contentTypesDictionary); } // filter out any settings that isn't referenced in the layout references @@ -66,7 +70,7 @@ internal class BlockEditorValues blockEditorData.References.Any(r => r.SettingsUdi is not null && x.Udi is not null && r.SettingsUdi == x.Udi))) { - ResolveBlockItemData(block, contentTypePropertyTypes); + ResolveBlockItemData(block, contentTypePropertyTypes, contentTypesDictionary); } // remove blocks that couldn't be resolved @@ -76,12 +80,10 @@ internal class BlockEditorValues return blockEditorData; } - private IContentType? GetElementType(BlockItemData item) => _contentTypeService.Get(item.ContentTypeKey); - private bool ResolveBlockItemData(BlockItemData block, Dictionary> contentTypePropertyTypes) + private bool ResolveBlockItemData(BlockItemData block, Dictionary> contentTypePropertyTypes, IDictionary contentTypesDictionary) { - IContentType? contentType = GetElementType(block); - if (contentType == null) + if (contentTypesDictionary.TryGetValue(block.ContentTypeKey, out IContentType? contentType) is false) { return false; } diff --git a/src/Umbraco.Infrastructure/PropertyEditors/BlockGridPropertyEditorBase.cs b/src/Umbraco.Infrastructure/PropertyEditors/BlockGridPropertyEditorBase.cs index fe72d83927..8d6e080b74 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/BlockGridPropertyEditorBase.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/BlockGridPropertyEditorBase.cs @@ -5,6 +5,7 @@ using System.ComponentModel.DataAnnotations; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Umbraco.Cms.Core.Cache; +using Umbraco.Cms.Core.Cache.PropertyEditors; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Blocks; @@ -58,12 +59,12 @@ public abstract class BlockGridPropertyEditorBase : DataEditor IShortStringHelper shortStringHelper, IJsonSerializer jsonSerializer, IIOHelper ioHelper, - IContentTypeService contentTypeService, + IBlockEditorElementTypeCache elementTypeCache, IPropertyValidationService propertyValidationService) : base(attribute, propertyEditors, dataValueReferenceFactories, dataTypeConfigurationCache, textService, logger, shortStringHelper, jsonSerializer, ioHelper) { - BlockEditorValues = new BlockEditorValues(new BlockGridEditorDataConverter(jsonSerializer), contentTypeService, logger); - Validators.Add(new BlockEditorValidator(propertyValidationService, BlockEditorValues, contentTypeService)); + BlockEditorValues = new BlockEditorValues(new BlockGridEditorDataConverter(jsonSerializer), elementTypeCache, logger); + Validators.Add(new BlockEditorValidator(propertyValidationService, BlockEditorValues, elementTypeCache)); Validators.Add(new MinMaxValidator(BlockEditorValues, textService)); } diff --git a/src/Umbraco.Infrastructure/PropertyEditors/BlockListPropertyEditorBase.cs b/src/Umbraco.Infrastructure/PropertyEditors/BlockListPropertyEditorBase.cs index 5d2c968c72..690251467c 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/BlockListPropertyEditorBase.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/BlockListPropertyEditorBase.cs @@ -2,6 +2,7 @@ using System.ComponentModel.DataAnnotations; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Umbraco.Cms.Core.Cache; +using Umbraco.Cms.Core.Cache.PropertyEditors; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Blocks; @@ -55,7 +56,7 @@ public abstract class BlockListPropertyEditorBase : DataEditor PropertyEditorCollection propertyEditors, DataValueReferenceFactoryCollection dataValueReferenceFactories, IDataTypeConfigurationCache dataTypeConfigurationCache, - IContentTypeService contentTypeService, + IBlockEditorElementTypeCache elementTypeCache, ILocalizedTextService textService, ILogger logger, IShortStringHelper shortStringHelper, @@ -64,8 +65,8 @@ public abstract class BlockListPropertyEditorBase : DataEditor IPropertyValidationService propertyValidationService) : base(attribute, propertyEditors, dataValueReferenceFactories, dataTypeConfigurationCache, textService, logger, shortStringHelper, jsonSerializer, ioHelper) { - BlockEditorValues = new BlockEditorValues(blockEditorDataConverter, contentTypeService, logger); - Validators.Add(new BlockEditorValidator(propertyValidationService, BlockEditorValues, contentTypeService)); + BlockEditorValues = new BlockEditorValues(blockEditorDataConverter, elementTypeCache, logger); + Validators.Add(new BlockEditorValidator(propertyValidationService, BlockEditorValues, elementTypeCache)); Validators.Add(new MinMaxValidator(BlockEditorValues, textService)); } diff --git a/src/Umbraco.Infrastructure/PropertyEditors/RichTextEditorBlockValidator.cs b/src/Umbraco.Infrastructure/PropertyEditors/RichTextEditorBlockValidator.cs index 01d10e46f0..dc4775f6b7 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/RichTextEditorBlockValidator.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/RichTextEditorBlockValidator.cs @@ -1,4 +1,5 @@ using Microsoft.Extensions.Logging; +using Umbraco.Cms.Core.Cache.PropertyEditors; using Umbraco.Cms.Core.Models.Blocks; using Umbraco.Cms.Core.Serialization; using Umbraco.Cms.Core.Services; @@ -14,10 +15,10 @@ internal class RichTextEditorBlockValidator : BlockEditorValidatorBase public RichTextEditorBlockValidator( IPropertyValidationService propertyValidationService, BlockEditorValues blockEditorValues, - IContentTypeService contentTypeService, + IBlockEditorElementTypeCache elementTypeCache, IJsonSerializer jsonSerializer, ILogger logger) - : base(propertyValidationService, contentTypeService) + : base(propertyValidationService, elementTypeCache) { _blockEditorValues = blockEditorValues; _jsonSerializer = jsonSerializer; diff --git a/src/Umbraco.Infrastructure/PropertyEditors/RichTextPropertyEditor.cs b/src/Umbraco.Infrastructure/PropertyEditors/RichTextPropertyEditor.cs index 2447803277..ee37d8c63b 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/RichTextPropertyEditor.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/RichTextPropertyEditor.cs @@ -5,6 +5,7 @@ using System.Diagnostics.CodeAnalysis; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Umbraco.Cms.Core.Cache; +using Umbraco.Cms.Core.Cache.PropertyEditors; using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Media; @@ -171,7 +172,7 @@ public class RichTextPropertyEditor : DataEditor private readonly IHtmlMacroParameterParser _macroParameterParser; private readonly RichTextEditorPastedImages _pastedImages; private readonly IJsonSerializer _jsonSerializer; - private readonly IContentTypeService _contentTypeService; + private readonly IBlockEditorElementTypeCache _elementTypeCache; private readonly ILogger _logger; public RichTextPropertyValueEditor( @@ -189,7 +190,7 @@ public class RichTextPropertyEditor : DataEditor IIOHelper ioHelper, IHtmlSanitizer htmlSanitizer, IHtmlMacroParameterParser macroParameterParser, - IContentTypeService contentTypeService, + IBlockEditorElementTypeCache elementTypeCache, IPropertyValidationService propertyValidationService, DataValueReferenceFactoryCollection dataValueReferenceFactoryCollection) : base(attribute, propertyEditors, dataTypeReadCache, localizedTextService, logger, shortStringHelper, jsonSerializer, ioHelper, dataValueReferenceFactoryCollection) @@ -200,11 +201,11 @@ public class RichTextPropertyEditor : DataEditor _pastedImages = pastedImages; _htmlSanitizer = htmlSanitizer; _macroParameterParser = macroParameterParser; - _contentTypeService = contentTypeService; + _elementTypeCache = elementTypeCache; _jsonSerializer = jsonSerializer; _logger = logger; - Validators.Add(new RichTextEditorBlockValidator(propertyValidationService, CreateBlockEditorValues(), contentTypeService, jsonSerializer, logger)); + Validators.Add(new RichTextEditorBlockValidator(propertyValidationService, CreateBlockEditorValues(), elementTypeCache, jsonSerializer, logger)); } /// @@ -392,6 +393,6 @@ public class RichTextPropertyEditor : DataEditor } private BlockEditorValues CreateBlockEditorValues() - => new(new RichTextEditorBlockDataConverter(), _contentTypeService, _logger); + => new(new RichTextEditorBlockDataConverter(), _elementTypeCache, _logger); } } diff --git a/src/Umbraco.Infrastructure/Templates/HtmlMacroParameterParser.cs b/src/Umbraco.Infrastructure/Templates/HtmlMacroParameterParser.cs index 6c7445d2da..bb8c4d0d86 100644 --- a/src/Umbraco.Infrastructure/Templates/HtmlMacroParameterParser.cs +++ b/src/Umbraco.Infrastructure/Templates/HtmlMacroParameterParser.cs @@ -55,10 +55,9 @@ public sealed class HtmlMacroParameterParser : IHtmlMacroParameterParser macroAlias, new Dictionary(macroAttributes, StringComparer.OrdinalIgnoreCase)))); - foreach (UmbracoEntityReference umbracoEntityReference in GetUmbracoEntityReferencesFromMacros(foundMacros)) - { - yield return umbracoEntityReference; - } + return foundMacros.Count > 0 + ? GetUmbracoEntityReferencesFromMacros(foundMacros) + : Enumerable.Empty(); } /// @@ -82,10 +81,9 @@ public sealed class HtmlMacroParameterParser : IHtmlMacroParameterParser } } - foreach (UmbracoEntityReference umbracoEntityReference in GetUmbracoEntityReferencesFromMacros(foundMacros)) - { - yield return umbracoEntityReference; - } + return foundMacros.Count > 0 + ? GetUmbracoEntityReferencesFromMacros(foundMacros) + : Enumerable.Empty(); } private IEnumerable GetUmbracoEntityReferencesFromMacros( @@ -96,6 +94,7 @@ public sealed class HtmlMacroParameterParser : IHtmlMacroParameterParser yield break; } + IEnumerable uniqueMacroAliases = macros.Select(f => f.Item1).Distinct(); // TODO: Tracking Macro references @@ -103,7 +102,9 @@ public sealed class HtmlMacroParameterParser : IHtmlMacroParameterParser var foundMacroUmbracoEntityReferences = new List(); // Get all the macro configs in one hit for these unique macro aliases - this is now cached with a custom cache policy - IEnumerable macroConfigs = macroWithAliasService.GetAll(uniqueMacroAliases.WhereNotNull().ToArray()); + IEnumerable macroConfigs = uniqueMacroAliases.Any() + ? macroWithAliasService.GetAll(uniqueMacroAliases.WhereNotNull().ToArray()) + : Enumerable.Empty(); foreach (Tuple> macro in macros) { diff --git a/src/Umbraco.Web.BackOffice/Mapping/ContentMapDefinition.cs b/src/Umbraco.Web.BackOffice/Mapping/ContentMapDefinition.cs index 0bc4e9c1b7..ae48169943 100644 --- a/src/Umbraco.Web.BackOffice/Mapping/ContentMapDefinition.cs +++ b/src/Umbraco.Web.BackOffice/Mapping/ContentMapDefinition.cs @@ -128,7 +128,7 @@ internal class ContentMapDefinition : IMapDefinition target.AdditionalPreviewUrls = source.AdditionalPreviewUrls; } - // Umbraco.Code.MapAll + // Umbraco.Code.MapAll -ContentDto private void Map(ContentItemDisplay source, ContentItemDisplayWithSchedule target, MapperContext context) { foreach (KeyValuePair additionalData in source.AdditionalData) @@ -140,7 +140,6 @@ internal class ContentMapDefinition : IMapDefinition target.AllowedTemplates = source.AllowedTemplates; target.AllowPreview = source.AllowPreview; target.ContentApps = source.ContentApps; - target.ContentDto = source.ContentDto; target.ContentTypeAlias = source.ContentTypeAlias; target.ContentTypeId = source.ContentTypeId; target.ContentTypeKey = source.ContentTypeKey; @@ -207,7 +206,7 @@ internal class ContentMapDefinition : IMapDefinition } } - // Umbraco.Code.MapAll + // Umbraco.Code.MapAll -ContentDto private static void Map(ContentItemDisplayWithSchedule source, ContentItemDisplay target, MapperContext context) { foreach (KeyValuePair additionalData in source.AdditionalData) @@ -219,7 +218,6 @@ internal class ContentMapDefinition : IMapDefinition target.AllowedTemplates = source.AllowedTemplates; target.AllowPreview = source.AllowPreview; target.ContentApps = source.ContentApps; - target.ContentDto = source.ContentDto; target.ContentTypeAlias = source.ContentTypeAlias; target.ContentTypeId = source.ContentTypeId; target.ContentTypeKey = source.ContentTypeKey; @@ -253,7 +251,7 @@ internal class ContentMapDefinition : IMapDefinition private static void Map(IContent source, ContentPropertyCollectionDto target, MapperContext context) => target.Properties = context.MapEnumerable(source.Properties).WhereNotNull(); - // Umbraco.Code.MapAll -AllowPreview -Errors -PersistedContent + // Umbraco.Code.MapAll -AllowPreview -Errors -PersistedContent -ContentDto private void Map(IContent source, ContentItemDisplay target, MapperContext context) where TVariant : ContentVariantDisplay { @@ -300,11 +298,6 @@ internal class ContentMapDefinition : IMapDefinition target.Updater = _commonMapper.GetCreator(source, context); target.Urls = GetUrls(source); target.Variants = _contentVariantMapper.Map(source, context); - - target.ContentDto = new ContentPropertyCollectionDto - { - Properties = context.MapEnumerable(source.Properties).WhereNotNull() - }; } // Umbraco.Code.MapAll -Segment -Language -DisplayName -AdditionalPreviewUrls diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/DataValueEditorReuseTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/DataValueEditorReuseTests.cs index 0874b85987..b04acdbbc4 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/DataValueEditorReuseTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/DataValueEditorReuseTests.cs @@ -2,6 +2,7 @@ using Microsoft.Extensions.Logging; using Moq; using NUnit.Framework; using Umbraco.Cms.Core.Cache; +using Umbraco.Cms.Core.Cache.PropertyEditors; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Models.Blocks; using Umbraco.Cms.Core.PropertyEditors; @@ -44,7 +45,7 @@ public class DataValueEditorReuseTests _propertyEditorCollection, _dataValueReferenceFactories, Mock.Of(), - Mock.Of(), + Mock.Of(), Mock.Of(), Mock.Of>(), Mock.Of(), From 100f2c3bcd3461b7b6c41e8c654ae130d22cc6fa Mon Sep 17 00:00:00 2001 From: Mole Date: Mon, 3 Jun 2024 12:03:40 +0200 Subject: [PATCH 030/113] Add check to ensure that RenderControllers and SurfaceControllers are always routed through the UmbracoRouteValueTransforms (#16540) --- .../Routing/EagerMatcherPolicy.cs | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.Website/Routing/EagerMatcherPolicy.cs b/src/Umbraco.Web.Website/Routing/EagerMatcherPolicy.cs index 05290227b3..bb5cb52a4d 100644 --- a/src/Umbraco.Web.Website/Routing/EagerMatcherPolicy.cs +++ b/src/Umbraco.Web.Website/Routing/EagerMatcherPolicy.cs @@ -1,4 +1,5 @@ -using Microsoft.AspNetCore.Http; +using System.Reflection; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Routing.Matching; @@ -8,8 +9,8 @@ using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Routing; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Web.Common.Controllers; +using Umbraco.Cms.Web.Website.Controllers; using Umbraco.Extensions; -using HttpRequestExtensions = Umbraco.Extensions.HttpRequestExtensions; namespace Umbraco.Cms.Web.Website.Routing; @@ -104,6 +105,18 @@ internal class EagerMatcherPolicy : MatcherPolicy, IEndpointSelectorPolicy continue; } + // We have to ensure that none of the candidates is a render controller or surface controller + // Normally these shouldn't be statically routed, however some people do it. + // So we should probably be friendly and check for it. + // Do not add this to V14. + ControllerActionDescriptor? controllerDescriptor = routeEndpoint.Metadata.GetMetadata(); + TypeInfo? controllerTypeInfo = controllerDescriptor?.ControllerTypeInfo; + if (controllerTypeInfo is not null && + (controllerTypeInfo.IsType() || controllerTypeInfo.IsType())) + { + return; + } + if (routeEndpoint.Order < lowestOrder) { // We have to ensure that the route is valid for the current request method. From c3e7dad236aeab7f824abf2b7ddcca4311794869 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Mon, 3 Jun 2024 12:09:37 +0200 Subject: [PATCH 031/113] bumb to rc version --- version.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.json b/version.json index b9fa941e03..529d4b8c65 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.4.0", + "version": "13.4.0-rc", "assemblyVersion": { "precision": "build" }, From 32912b0c35c045c2f0c1cdad443cef2717d04ac5 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Mon, 3 Jun 2024 13:34:43 +0200 Subject: [PATCH 032/113] Update to query to SqlRaw (#16542) --- .../Locking/SqlServerEFCoreDistributedLockingMechanism.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Cms.Persistence.EFCore/Locking/SqlServerEFCoreDistributedLockingMechanism.cs b/src/Umbraco.Cms.Persistence.EFCore/Locking/SqlServerEFCoreDistributedLockingMechanism.cs index 38cdeef114..f037335837 100644 --- a/src/Umbraco.Cms.Persistence.EFCore/Locking/SqlServerEFCoreDistributedLockingMechanism.cs +++ b/src/Umbraco.Cms.Persistence.EFCore/Locking/SqlServerEFCoreDistributedLockingMechanism.cs @@ -170,7 +170,7 @@ internal class SqlServerEFCoreDistributedLockingMechanism : IDistributedLocki "A transaction with minimum ReadCommitted isolation level is required."); } - var rowsAffected = await dbContext.Database.ExecuteSqlAsync(@$"SET LOCK_TIMEOUT {(int)_timeout.TotalMilliseconds};UPDATE umbracoLock WITH (REPEATABLEREAD) SET value = (CASE WHEN (value=1) THEN -1 ELSE 1 END) WHERE id={LockId}"); + var rowsAffected = await dbContext.Database.ExecuteSqlRawAsync(@$"SET LOCK_TIMEOUT {(int)_timeout.TotalMilliseconds};UPDATE umbracoLock WITH (REPEATABLEREAD) SET value = (CASE WHEN (value=1) THEN -1 ELSE 1 END) WHERE id={LockId}"); if (rowsAffected == 0) { From 3dace4fc9d8fd68b829c49a7746a302b37bd3bea Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Tue, 4 Jun 2024 10:32:37 +0200 Subject: [PATCH 033/113] RTE and media picker should route medias the same way in the Delivery API (#16550) * RTE and media picker should route medias the same way in the Delivery API * Fix failing unit test * Fixed failing tests --- .../DeliveryApi/ApiMediaUrlProvider.cs | 2 +- .../DeliveryApi/ApiRichTextElementParser.cs | 19 ++----------------- .../DeliveryApi/ApiRichTextMarkupParser.cs | 5 ++--- .../DeliveryApi/ApiRichTextParserBase.cs | 11 +++++------ .../DeliveryApi/ApiMediaUrlProviderTests.cs | 2 +- .../DeliveryApi/RichTextParserTests.cs | 11 +++++------ 6 files changed, 16 insertions(+), 34 deletions(-) diff --git a/src/Umbraco.Core/DeliveryApi/ApiMediaUrlProvider.cs b/src/Umbraco.Core/DeliveryApi/ApiMediaUrlProvider.cs index 20373b1d3b..f8ebee826b 100644 --- a/src/Umbraco.Core/DeliveryApi/ApiMediaUrlProvider.cs +++ b/src/Umbraco.Core/DeliveryApi/ApiMediaUrlProvider.cs @@ -17,6 +17,6 @@ public sealed class ApiMediaUrlProvider : IApiMediaUrlProvider throw new ArgumentException("Media URLs can only be generated from Media items.", nameof(media)); } - return _publishedUrlProvider.GetMediaUrl(media, UrlMode.Relative); + return _publishedUrlProvider.GetMediaUrl(media); } } diff --git a/src/Umbraco.Infrastructure/DeliveryApi/ApiRichTextElementParser.cs b/src/Umbraco.Infrastructure/DeliveryApi/ApiRichTextElementParser.cs index eeb279e1b7..eed3b848eb 100644 --- a/src/Umbraco.Infrastructure/DeliveryApi/ApiRichTextElementParser.cs +++ b/src/Umbraco.Infrastructure/DeliveryApi/ApiRichTextElementParser.cs @@ -22,28 +22,13 @@ internal sealed class ApiRichTextElementParser : ApiRichTextParserBase, IApiRich private const string TextNodeName = "#text"; private const string CommentNodeName = "#comment"; - [Obsolete($"Please use the constructor that accepts {nameof(IApiElementBuilder)}. Will be removed in V15.")] public ApiRichTextElementParser( IApiContentRouteBuilder apiContentRouteBuilder, - IPublishedUrlProvider publishedUrlProvider, - IPublishedSnapshotAccessor publishedSnapshotAccessor, - ILogger logger) - : this( - apiContentRouteBuilder, - publishedUrlProvider, - publishedSnapshotAccessor, - StaticServiceProvider.Instance.GetRequiredService(), - logger) - { - } - - public ApiRichTextElementParser( - IApiContentRouteBuilder apiContentRouteBuilder, - IPublishedUrlProvider publishedUrlProvider, + IApiMediaUrlProvider mediaUrlProvider, IPublishedSnapshotAccessor publishedSnapshotAccessor, IApiElementBuilder apiElementBuilder, ILogger logger) - : base(apiContentRouteBuilder, publishedUrlProvider) + : base(apiContentRouteBuilder, mediaUrlProvider) { _publishedSnapshotAccessor = publishedSnapshotAccessor; _apiElementBuilder = apiElementBuilder; diff --git a/src/Umbraco.Infrastructure/DeliveryApi/ApiRichTextMarkupParser.cs b/src/Umbraco.Infrastructure/DeliveryApi/ApiRichTextMarkupParser.cs index f7eeba0f18..42c8829868 100644 --- a/src/Umbraco.Infrastructure/DeliveryApi/ApiRichTextMarkupParser.cs +++ b/src/Umbraco.Infrastructure/DeliveryApi/ApiRichTextMarkupParser.cs @@ -3,7 +3,6 @@ using Microsoft.Extensions.Logging; using Umbraco.Cms.Core; using Umbraco.Cms.Core.DeliveryApi; using Umbraco.Cms.Core.PublishedCache; -using Umbraco.Cms.Core.Routing; using Umbraco.Extensions; namespace Umbraco.Cms.Infrastructure.DeliveryApi; @@ -15,10 +14,10 @@ internal sealed class ApiRichTextMarkupParser : ApiRichTextParserBase, IApiRichT public ApiRichTextMarkupParser( IApiContentRouteBuilder apiContentRouteBuilder, - IPublishedUrlProvider publishedUrlProvider, + IApiMediaUrlProvider mediaUrlProvider, IPublishedSnapshotAccessor publishedSnapshotAccessor, ILogger logger) - : base(apiContentRouteBuilder, publishedUrlProvider) + : base(apiContentRouteBuilder, mediaUrlProvider) { _publishedSnapshotAccessor = publishedSnapshotAccessor; _logger = logger; diff --git a/src/Umbraco.Infrastructure/DeliveryApi/ApiRichTextParserBase.cs b/src/Umbraco.Infrastructure/DeliveryApi/ApiRichTextParserBase.cs index 0509105b05..7723fc835c 100644 --- a/src/Umbraco.Infrastructure/DeliveryApi/ApiRichTextParserBase.cs +++ b/src/Umbraco.Infrastructure/DeliveryApi/ApiRichTextParserBase.cs @@ -4,19 +4,18 @@ using Umbraco.Cms.Core.DeliveryApi; using Umbraco.Cms.Core.Models.DeliveryApi; using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Cms.Core.PublishedCache; -using Umbraco.Cms.Core.Routing; namespace Umbraco.Cms.Infrastructure.DeliveryApi; internal abstract partial class ApiRichTextParserBase { private readonly IApiContentRouteBuilder _apiContentRouteBuilder; - private readonly IPublishedUrlProvider _publishedUrlProvider; + private readonly IApiMediaUrlProvider _apiMediaUrlProvider; - protected ApiRichTextParserBase(IApiContentRouteBuilder apiContentRouteBuilder, IPublishedUrlProvider publishedUrlProvider) + protected ApiRichTextParserBase(IApiContentRouteBuilder apiContentRouteBuilder, IApiMediaUrlProvider apiMediaUrlProvider) { _apiContentRouteBuilder = apiContentRouteBuilder; - _publishedUrlProvider = publishedUrlProvider; + _apiMediaUrlProvider = apiMediaUrlProvider; } protected void ReplaceLocalLinks(IPublishedSnapshot publishedSnapshot, string href, Action handleContentRoute, Action handleMediaUrl, Action handleInvalidLink) @@ -52,7 +51,7 @@ internal abstract partial class ApiRichTextParserBase if (media != null) { handled = true; - handleMediaUrl(_publishedUrlProvider.GetMediaUrl(media, UrlMode.Absolute)); + handleMediaUrl(_apiMediaUrlProvider.GetUrl(media)); } break; @@ -77,7 +76,7 @@ internal abstract partial class ApiRichTextParserBase return; } - handleMediaUrl(_publishedUrlProvider.GetMediaUrl(media, UrlMode.Absolute)); + handleMediaUrl(_apiMediaUrlProvider.GetUrl(media)); } [GeneratedRegex("{localLink:(?umb:.+)}")] diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/DeliveryApi/ApiMediaUrlProviderTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/DeliveryApi/ApiMediaUrlProviderTests.cs index e1900d203f..0dfffbf638 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/DeliveryApi/ApiMediaUrlProviderTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/DeliveryApi/ApiMediaUrlProviderTests.cs @@ -21,7 +21,7 @@ public class ApiMediaUrlProviderTests : PropertyValueConverterTests var publishedUrlProvider = new Mock(); publishedUrlProvider - .Setup(p => p.GetMediaUrl(content.Object, UrlMode.Relative, It.IsAny(), It.IsAny(), It.IsAny())) + .Setup(p => p.GetMediaUrl(content.Object, UrlMode.Default, It.IsAny(), It.IsAny(), It.IsAny())) .Returns(publishedUrl); var apiMediaUrlProvider = new ApiMediaUrlProvider(publishedUrlProvider.Object); diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/DeliveryApi/RichTextParserTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/DeliveryApi/RichTextParserTests.cs index b7712b5346..a2522a5ecd 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/DeliveryApi/RichTextParserTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/DeliveryApi/RichTextParserTests.cs @@ -9,7 +9,6 @@ using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Cms.Core.PropertyEditors; using Umbraco.Cms.Core.PropertyEditors.ValueConverters; using Umbraco.Cms.Core.PublishedCache; -using Umbraco.Cms.Core.Routing; using Umbraco.Cms.Infrastructure.DeliveryApi; namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.DeliveryApi; @@ -474,7 +473,7 @@ public class RichTextParserTests : PropertyValueConverterTests Mock.Of>()); } - private void SetupTestContent(out IApiContentRouteBuilder routeBuilder, out IPublishedSnapshotAccessor snapshotAccessor, out IPublishedUrlProvider urlProvider) + private void SetupTestContent(out IApiContentRouteBuilder routeBuilder, out IPublishedSnapshotAccessor snapshotAccessor, out IApiMediaUrlProvider apiMediaUrlProvider) { var contentMock = new Mock(); contentMock.SetupGet(m => m.Key).Returns(_contentKey); @@ -502,14 +501,14 @@ public class RichTextParserTests : PropertyValueConverterTests .Setup(m => m.Build(contentMock.Object, null)) .Returns(new ApiContentRoute("/some-content-path", new ApiContentStartItem(_contentRootKey, "the-root-path"))); - var urlProviderMock = new Mock(); - urlProviderMock - .Setup(m => m.GetMediaUrl(mediaMock.Object, It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + var apiMediaUrlProviderMock = new Mock(); + apiMediaUrlProviderMock + .Setup(m => m.GetUrl(mediaMock.Object)) .Returns("/some-media-url"); routeBuilder = routeBuilderMock.Object; snapshotAccessor = snapshotAccessorMock.Object; - urlProvider = urlProviderMock.Object; + apiMediaUrlProvider = apiMediaUrlProviderMock.Object; } private IPublishedElement CreateElement(Guid id, int propertyValue) From ae7db56f77bc1b3f831cd009f9d66624df14f00b Mon Sep 17 00:00:00 2001 From: Andreas Zerbst <73799582+andr317c@users.noreply.github.com> Date: Tue, 4 Jun 2024 14:25:57 +0200 Subject: [PATCH 034/113] Decreased retry count (#16554) --- tests/Umbraco.Tests.AcceptanceTest/playwright.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Umbraco.Tests.AcceptanceTest/playwright.config.ts b/tests/Umbraco.Tests.AcceptanceTest/playwright.config.ts index f3d94a05f7..e72026e0e8 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/playwright.config.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/playwright.config.ts @@ -21,7 +21,7 @@ const config: PlaywrightTestConfig = { /* Fail the build on CI if you accidentally left test.only in the source code. */ forbidOnly: !!process.env.CI, /* Retry on CI only */ - retries: process.env.CI ? 5 : 2, + retries: process.env.CI ? 2 : 1, // 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 */ From 78bf04ef67882cc8ee095ea7685da929d2b30d8e Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 4 Jun 2024 14:56:22 +0200 Subject: [PATCH 035/113] bump version.json --- version.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.json b/version.json index 529d4b8c65..b9fa941e03 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.4.0-rc", + "version": "13.4.0", "assemblyVersion": { "precision": "build" }, From 5f8eac0464779c7601a22b9737c2217a66f737f7 Mon Sep 17 00:00:00 2001 From: Zeegaan Date: Thu, 6 Jun 2024 10:22:18 +0200 Subject: [PATCH 036/113] bump version.json --- version.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.json b/version.json index b9fa941e03..288af47e08 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.4.0", + "version": "13.5.0-rc", "assemblyVersion": { "precision": "build" }, From 5ae5fe338a96098c8bb3f97cdffb423b5c352e56 Mon Sep 17 00:00:00 2001 From: Mole Date: Fri, 7 Jun 2024 12:49:13 +0200 Subject: [PATCH 037/113] V13: Set request culture for VirtualPageController (#16572) * Rename FindDomain to FindAndSetDomain * Ensure VariationContext and PublishedRequest is updated for virtual page controller --- src/Umbraco.Core/Routing/IPublishedRouter.cs | 33 ++++++++++++++ src/Umbraco.Core/Routing/PublishedRouter.cs | 43 ++++++++++++++----- .../UmbracoVirtualPageFilterAttribute.cs | 8 ++++ .../Routing/UmbracoVirtualPageRoute.cs | 1 + .../ContentFinderByAliasWithDomainsTests.cs | 2 +- .../ContentFinderByUrlWithDomainsTests.cs | 4 +- .../Routing/DomainsAndCulturesTests.cs | 6 +-- .../Routing/UrlsWithNestedDomains.cs | 2 +- 8 files changed, 82 insertions(+), 17 deletions(-) diff --git a/src/Umbraco.Core/Routing/IPublishedRouter.cs b/src/Umbraco.Core/Routing/IPublishedRouter.cs index 53a07ff325..a01e17c94d 100644 --- a/src/Umbraco.Core/Routing/IPublishedRouter.cs +++ b/src/Umbraco.Core/Routing/IPublishedRouter.cs @@ -47,4 +47,37 @@ public interface IPublishedRouter /// /// Task UpdateRequestAsync(IPublishedRequest request, IPublishedContent? publishedContent); + + /// + /// Finds the site root (if any) matching the http request, and updates the PublishedRequest and VariationContext accordingly. + /// + /// + /// This method is used for VirtualPage routing. + /// + /// + /// In this case we do not want to run the entire routing pipeline since ContentFinders are not needed here. + /// However, we do want to set the culture on VariationContext and PublishedRequest to the values specified by the domains. + /// + /// + /// + /// The request to update the culture on domain on + /// True if a domain was found otherwise false. + bool RouteDomain(IPublishedRequestBuilder request) => false; + + /// + /// Finds the site root (if any) matching the http request, and updates the VariationContext accordingly. + /// + /// + /// + /// This is used for VirtualPage routing. + /// + /// + /// This is required to set the culture on VariationContext to the values specified by the domains, before the FindContent method is called. + /// In order to allow the FindContent implementer to correctly find content based off the culture. Before the PublishedRequest is built. + /// + /// + /// The URI to resolve the domain from. + /// True if a domain was found, otherwise false. + bool UpdateVariationContext(Uri uri) => false; + } diff --git a/src/Umbraco.Core/Routing/PublishedRouter.cs b/src/Umbraco.Core/Routing/PublishedRouter.cs index f04fd04ca2..df1d459327 100644 --- a/src/Umbraco.Core/Routing/PublishedRouter.cs +++ b/src/Umbraco.Core/Routing/PublishedRouter.cs @@ -108,7 +108,7 @@ public class PublishedRouter : IPublishedRouter // find domain if (builder.Domain == null) { - FindDomain(builder); + FindAndSetDomain(builder); } await RouteRequestInternalAsync(builder); @@ -185,7 +185,7 @@ public class PublishedRouter : IPublishedRouter private async Task TryRouteRequest(IPublishedRequestBuilder request) { - FindDomain(request); + FindAndSetDomain(request); if (request.IsRedirect()) { @@ -270,18 +270,31 @@ public class PublishedRouter : IPublishedRouter // to find out the appropriate template } - /// - /// Finds the site root (if any) matching the http request, and updates the PublishedRequest accordingly. - /// - /// A value indicating whether a domain was found. - internal bool FindDomain(IPublishedRequestBuilder request) + /// + public bool RouteDomain(IPublishedRequestBuilder request) + { + var found = FindAndSetDomain(request); + HandleWildcardDomains(request); + SetVariationContext(request.Culture); + return found; + } + + /// + public bool UpdateVariationContext(Uri uri) + { + DomainAndUri? domain = FindDomain(uri, out _); + SetVariationContext(domain?.Culture); + return domain?.Culture is not null; + } + + private DomainAndUri? FindDomain(Uri uri, out string? defaultCulture) { const string tracePrefix = "FindDomain: "; // note - we are not handling schemes nor ports here. if (_logger.IsEnabled(LogLevel.Debug)) { - _logger.LogDebug("{TracePrefix}Uri={RequestUri}", tracePrefix, request.Uri); + _logger.LogDebug("{TracePrefix}Uri={RequestUri}", tracePrefix, uri); } IUmbracoContext umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext(); @@ -315,10 +328,20 @@ public class PublishedRouter : IPublishedRouter domains = domains?.Where(IsPublishedContentDomain).ToList(); - var defaultCulture = domainsCache?.DefaultCulture; + defaultCulture = domainsCache?.DefaultCulture; + return DomainUtilities.SelectDomain(domains, uri, defaultCulture: defaultCulture); + } + + /// + /// Finds the site root (if any) matching the http request, and updates the PublishedRequest accordingly. + /// + /// A value indicating whether a domain was found. + internal bool FindAndSetDomain(IPublishedRequestBuilder request) + { + const string tracePrefix = "FindDomain: "; // try to find a domain matching the current request - DomainAndUri? domainAndUri = DomainUtilities.SelectDomain(domains, request.Uri, defaultCulture: defaultCulture); + DomainAndUri? domainAndUri = FindDomain(request.Uri, out var defaultCulture); // handle domain - always has a contentId and a culture if (domainAndUri != null) diff --git a/src/Umbraco.Web.Common/Filters/UmbracoVirtualPageFilterAttribute.cs b/src/Umbraco.Web.Common/Filters/UmbracoVirtualPageFilterAttribute.cs index 707dfe0b8d..fadd0d19e2 100644 --- a/src/Umbraco.Web.Common/Filters/UmbracoVirtualPageFilterAttribute.cs +++ b/src/Umbraco.Web.Common/Filters/UmbracoVirtualPageFilterAttribute.cs @@ -1,9 +1,11 @@ using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Extensions; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.Routing; using Umbraco.Cms.Web.Common.Controllers; using Umbraco.Cms.Web.Common.Routing; @@ -40,6 +42,12 @@ public class UmbracoVirtualPageFilterAttribute : Attribute, IAsyncActionFilter if (endpoint != null) { IUmbracoVirtualPageRoute umbracoVirtualPageRoute = context.HttpContext.RequestServices.GetRequiredService(); + IPublishedRouter publishedRouter = context.HttpContext.RequestServices.GetRequiredService(); + UriUtility uriUtility = context.HttpContext.RequestServices.GetRequiredService(); + + var originalRequestUrl = new Uri(context.HttpContext.Request.GetEncodedUrl()); + Uri cleanedUri = uriUtility.UriToUmbraco(originalRequestUrl); + publishedRouter.UpdateVariationContext(cleanedUri); IPublishedContent? publishedContent = umbracoVirtualPageRoute.FindContent(endpoint, context); diff --git a/src/Umbraco.Web.Common/Routing/UmbracoVirtualPageRoute.cs b/src/Umbraco.Web.Common/Routing/UmbracoVirtualPageRoute.cs index cff5a589f6..9813cf3212 100644 --- a/src/Umbraco.Web.Common/Routing/UmbracoVirtualPageRoute.cs +++ b/src/Umbraco.Web.Common/Routing/UmbracoVirtualPageRoute.cs @@ -155,6 +155,7 @@ public class UmbracoVirtualPageRoute : IUmbracoVirtualPageRoute IPublishedRequestBuilder requestBuilder = await _publishedRouter.CreateRequestAsync(cleanedUrl); requestBuilder.SetPublishedContent(publishedContent); + _publishedRouter.RouteDomain(requestBuilder); return requestBuilder.Build(); } diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/ContentFinderByAliasWithDomainsTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/ContentFinderByAliasWithDomainsTests.cs index 3f639965cd..30bb4ae70c 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/ContentFinderByAliasWithDomainsTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/ContentFinderByAliasWithDomainsTests.cs @@ -31,7 +31,7 @@ public class ContentFinderByAliasWithDomainsTests : UrlRoutingTestBase var request = await publishedRouter.CreateRequestAsync(umbracoContext.CleanedUmbracoUrl); // must lookup domain - publishedRouter.FindDomain(request); + publishedRouter.FindAndSetDomain(request); if (expectedNode > 0) { diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/ContentFinderByUrlWithDomainsTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/ContentFinderByUrlWithDomainsTests.cs index c9069046ac..4606641265 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/ContentFinderByUrlWithDomainsTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/ContentFinderByUrlWithDomainsTests.cs @@ -207,7 +207,7 @@ public class ContentFinderByUrlWithDomainsTests : UrlRoutingTestBase var frequest = await publishedRouter.CreateRequestAsync(umbracoContext.CleanedUmbracoUrl); // must lookup domain else lookup by URL fails - publishedRouter.FindDomain(frequest); + publishedRouter.FindAndSetDomain(frequest); var lookup = new ContentFinderByUrl(Mock.Of>(), umbracoContextAccessor); var result = await lookup.TryFindContent(frequest); @@ -245,7 +245,7 @@ public class ContentFinderByUrlWithDomainsTests : UrlRoutingTestBase var frequest = await publishedRouter.CreateRequestAsync(umbracoContext.CleanedUmbracoUrl); // must lookup domain else lookup by URL fails - publishedRouter.FindDomain(frequest); + publishedRouter.FindAndSetDomain(frequest); Assert.AreEqual(expectedCulture, frequest.Culture); var lookup = new ContentFinderByUrl(Mock.Of>(), umbracoContextAccessor); diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/DomainsAndCulturesTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/DomainsAndCulturesTests.cs index c54e540864..3945e2346d 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/DomainsAndCulturesTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/DomainsAndCulturesTests.cs @@ -261,7 +261,7 @@ public class DomainsAndCulturesTests : UrlRoutingTestBase var frequest = await publishedRouter.CreateRequestAsync(umbracoContext.CleanedUmbracoUrl); // lookup domain - publishedRouter.FindDomain(frequest); + publishedRouter.FindAndSetDomain(frequest); Assert.AreEqual(expectedCulture, frequest.Culture); @@ -310,7 +310,7 @@ public class DomainsAndCulturesTests : UrlRoutingTestBase var frequest = await publishedRouter.CreateRequestAsync(umbracoContext.CleanedUmbracoUrl); // lookup domain - publishedRouter.FindDomain(frequest); + publishedRouter.FindAndSetDomain(frequest); // find document var finder = new ContentFinderByUrl(Mock.Of>(), umbracoContextAccessor); @@ -345,7 +345,7 @@ public class DomainsAndCulturesTests : UrlRoutingTestBase var frequest = await publishedRouter.CreateRequestAsync(umbracoContext.CleanedUmbracoUrl); // lookup domain - publishedRouter.FindDomain(frequest); + publishedRouter.FindAndSetDomain(frequest); Assert.IsNotNull(frequest.Domain); Assert.AreEqual(expectedCulture, frequest.Culture); diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/UrlsWithNestedDomains.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/UrlsWithNestedDomains.cs index 9edce34707..d0536640e2 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/UrlsWithNestedDomains.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/UrlsWithNestedDomains.cs @@ -62,7 +62,7 @@ public class UrlsWithNestedDomains : UrlRoutingTestBase var publishedRouter = CreatePublishedRouter(umbracoContextAccessor); var frequest = await publishedRouter.CreateRequestAsync(umbracoContext.CleanedUmbracoUrl); - publishedRouter.FindDomain(frequest); + publishedRouter.FindAndSetDomain(frequest); Assert.IsTrue(frequest.HasDomain()); // check that it's been routed From 24abc117fe5db9fda5dc2badea49ffcd9235971f Mon Sep 17 00:00:00 2001 From: Zeegaan Date: Fri, 7 Jun 2024 12:50:46 +0200 Subject: [PATCH 038/113] update version.json --- version.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.json b/version.json index 288af47e08..b9fa941e03 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.5.0-rc", + "version": "13.4.0", "assemblyVersion": { "precision": "build" }, From 5e31fde4169d58a352ecc1b6cc7d2726d1dd52f8 Mon Sep 17 00:00:00 2001 From: Andreas Zerbst <73799582+andr317c@users.noreply.github.com> Date: Mon, 17 Jun 2024 10:49:16 +0200 Subject: [PATCH 039/113] V13 QA Updated depedencies (#16606) * Bumped version of helper and builder * Removed faker --- .../package-lock.json | 28 +++++++------------ .../Umbraco.Tests.AcceptanceTest/package.json | 5 ++-- 2 files changed, 12 insertions(+), 21 deletions(-) diff --git a/tests/Umbraco.Tests.AcceptanceTest/package-lock.json b/tests/Umbraco.Tests.AcceptanceTest/package-lock.json index 82892582ce..e3bb3947ff 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/package-lock.json +++ b/tests/Umbraco.Tests.AcceptanceTest/package-lock.json @@ -7,11 +7,10 @@ "name": "acceptancetest", "hasInstallScript": true, "dependencies": { - "@umbraco/json-models-builders": "^1.0.6", - "@umbraco/playwright-testhelpers": "^1.0.25", + "@umbraco/json-models-builders": "^1.0.8", + "@umbraco/playwright-testhelpers": "^1.0.28", "camelize": "^1.0.0", "dotenv": "^16.0.2", - "faker": "^4.1.0", "form-data": "^4.0.0", "node-fetch": "^2.6.7", "xhr2": "^0.2.1" @@ -122,22 +121,20 @@ "dev": true }, "node_modules/@umbraco/json-models-builders": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@umbraco/json-models-builders/-/json-models-builders-1.0.6.tgz", - "integrity": "sha512-bXwfXcpuqG1Ye714L9KJEGXuSzJfckysE/6CuPjdG8FqHWTE1brv28teR2oMw+ih8ca2u2zUboRgdzLEU/1D3Q==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@umbraco/json-models-builders/-/json-models-builders-1.0.8.tgz", + "integrity": "sha512-qBiOwaFO0V/hRpByBnwWe65nlNvaKNQPQj17MCiUTkCf1LxkB1T0ZoQv50vvnLFx6xUBZKfLhEXqYmoJqsQsPg==", "dependencies": { - "camelize": "^1.0.0", - "faker": "^4.1.0" + "camelize": "^1.0.0" } }, "node_modules/@umbraco/playwright-testhelpers": { - "version": "1.0.25", - "resolved": "https://registry.npmjs.org/@umbraco/playwright-testhelpers/-/playwright-testhelpers-1.0.25.tgz", - "integrity": "sha512-6H452J6LhP0EHjF4jR7V7i0U8WPTiAbSyhN1J459BbbYEJ4QX1A2ZlCdA6VSBAsK1xYdMXD+yxsVJq7AAwiy9A==", + "version": "1.0.28", + "resolved": "https://registry.npmjs.org/@umbraco/playwright-testhelpers/-/playwright-testhelpers-1.0.28.tgz", + "integrity": "sha512-AOk0eKkGV1Tyhb+iac9hyKzm2wCqwf+ELRQsiCWNVW8DxrPZiXIqQyjeI22YFuzkVJ3MJBDcoSwp7d31b1gm/w==", "dependencies": { - "@umbraco/json-models-builders": "^1.0.6", + "@umbraco/json-models-builders": "^1.0.8", "camelize": "^1.0.0", - "faker": "^4.1.0", "form-data": "^4.0.0", "node-fetch": "^2.6.7", "xhr2": "^0.2.1" @@ -323,11 +320,6 @@ "node": "> 0.1.90" } }, - "node_modules/faker": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/faker/-/faker-4.1.0.tgz", - "integrity": "sha1-HkW7vsxndLPBlfrSg1EJxtdIzD8=" - }, "node_modules/fast-glob": { "version": "3.2.12", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", diff --git a/tests/Umbraco.Tests.AcceptanceTest/package.json b/tests/Umbraco.Tests.AcceptanceTest/package.json index adcdf64f1d..6dc84f51dc 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/package.json +++ b/tests/Umbraco.Tests.AcceptanceTest/package.json @@ -19,10 +19,9 @@ "wait-on": "^7.2.0" }, "dependencies": { - "@umbraco/json-models-builders": "^1.0.6", - "@umbraco/playwright-testhelpers": "^1.0.25", + "@umbraco/json-models-builders": "^1.0.8", + "@umbraco/playwright-testhelpers": "^1.0.28", "camelize": "^1.0.0", - "faker": "^4.1.0", "form-data": "^4.0.0", "node-fetch": "^2.6.7", "xhr2": "^0.2.1", From 8e98c1e023d51a69b68cbb7db5ce5f03b53dd2b1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 14 Jun 2024 06:27:39 +0000 Subject: [PATCH 040/113] Bump braces from 3.0.2 to 3.0.3 in /tests/Umbraco.Tests.AcceptanceTest Bumps [braces](https://github.com/micromatch/braces) from 3.0.2 to 3.0.3. - [Changelog](https://github.com/micromatch/braces/blob/master/CHANGELOG.md) - [Commits](https://github.com/micromatch/braces/compare/3.0.2...3.0.3) --- updated-dependencies: - dependency-name: braces dependency-type: indirect ... Signed-off-by: dependabot[bot] --- .../Umbraco.Tests.AcceptanceTest/package-lock.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/Umbraco.Tests.AcceptanceTest/package-lock.json b/tests/Umbraco.Tests.AcceptanceTest/package-lock.json index 940e5ca79c..37b554808c 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/package-lock.json +++ b/tests/Umbraco.Tests.AcceptanceTest/package-lock.json @@ -219,12 +219,12 @@ } }, "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" @@ -375,9 +375,9 @@ } }, "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "dependencies": { "to-regex-range": "^5.0.1" From 82b4f506c28909f6e66de050cf46589189ac89e0 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 18 Jun 2024 09:08:40 +0200 Subject: [PATCH 041/113] bump version to rc2 --- version.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.json b/version.json index b9fa941e03..800c209374 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.4.0", + "version": "13.4.0-rc2", "assemblyVersion": { "precision": "build" }, From 344245b1ea2af41ae43b04c1c54cbb9c899b844a Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 18 Jun 2024 10:05:36 +0200 Subject: [PATCH 042/113] bump to final version --- version.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.json b/version.json index 800c209374..b9fa941e03 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.4.0-rc2", + "version": "13.4.0", "assemblyVersion": { "precision": "build" }, From a686ba2a0ed97cb0f818e532bc84dbd179c4c027 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Wed, 19 Jun 2024 07:50:52 +0200 Subject: [PATCH 043/113] V13: Update nuget packages (#16616) * Update nuget packages * Fix Imagesharp 2 --------- Co-authored-by: Bjarke Berg --- Directory.Packages.props | 48 +++++++++---------- .../Umbraco.Cms.Imaging.ImageSharp.csproj | 2 +- .../Umbraco.Cms.Imaging.ImageSharp2.csproj | 4 +- .../Umbraco.PublishedCache.NuCache.csproj | 4 +- .../Umbraco.Web.Common.csproj | 2 +- 5 files changed, 29 insertions(+), 31 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 02dbaba948..ccd1e91a2e 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -12,25 +12,25 @@ - - + + - - - - + + + + - + - - + + - + @@ -45,13 +45,13 @@ - - - - - + + + + + - + @@ -73,21 +73,21 @@ - - - - - + + + + + - + - + - + \ No newline at end of file diff --git a/src/Umbraco.Cms.Imaging.ImageSharp/Umbraco.Cms.Imaging.ImageSharp.csproj b/src/Umbraco.Cms.Imaging.ImageSharp/Umbraco.Cms.Imaging.ImageSharp.csproj index 13126a24b5..549ea5cb40 100644 --- a/src/Umbraco.Cms.Imaging.ImageSharp/Umbraco.Cms.Imaging.ImageSharp.csproj +++ b/src/Umbraco.Cms.Imaging.ImageSharp/Umbraco.Cms.Imaging.ImageSharp.csproj @@ -6,7 +6,7 @@ - + diff --git a/src/Umbraco.Cms.Imaging.ImageSharp2/Umbraco.Cms.Imaging.ImageSharp2.csproj b/src/Umbraco.Cms.Imaging.ImageSharp2/Umbraco.Cms.Imaging.ImageSharp2.csproj index 43bf47cb75..724c5b5e34 100644 --- a/src/Umbraco.Cms.Imaging.ImageSharp2/Umbraco.Cms.Imaging.ImageSharp2.csproj +++ b/src/Umbraco.Cms.Imaging.ImageSharp2/Umbraco.Cms.Imaging.ImageSharp2.csproj @@ -5,10 +5,8 @@ - + - - diff --git a/src/Umbraco.PublishedCache.NuCache/Umbraco.PublishedCache.NuCache.csproj b/src/Umbraco.PublishedCache.NuCache/Umbraco.PublishedCache.NuCache.csproj index 75dfa13ec6..92f222525c 100644 --- a/src/Umbraco.PublishedCache.NuCache/Umbraco.PublishedCache.NuCache.csproj +++ b/src/Umbraco.PublishedCache.NuCache/Umbraco.PublishedCache.NuCache.csproj @@ -7,10 +7,10 @@ - + - + diff --git a/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj b/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj index 02a039a6db..b0b5f2af6a 100644 --- a/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj +++ b/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj @@ -12,7 +12,7 @@ - + From 65c76fcfab17d7a66966c3f5a0a5576ef91fbfaa Mon Sep 17 00:00:00 2001 From: Andreas Zerbst <73799582+andr317c@users.noreply.github.com> Date: Wed, 19 Jun 2024 09:49:04 +0200 Subject: [PATCH 044/113] Decreased to 1 retry for this file only (#16623) --- .../tests/DefaultConfig/Tour/tours.spec.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Tour/tours.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Tour/tours.spec.ts index f5974db43d..03bbe86c78 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Tour/tours.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Tour/tours.spec.ts @@ -3,6 +3,7 @@ import {test} from '@umbraco/playwright-testhelpers'; test.describe('Tours', () => { const timeout = 60000; + test.describe.configure({ retries: 1 }); test.beforeEach(async ({ page, umbracoApi }, testInfo) => { await umbracoApi.report.report(testInfo); await umbracoApi.login(); @@ -100,4 +101,4 @@ test.describe('Tours', () => { await expect(await umbracoUi.getGlobalHelp()).toBeVisible(); await getPercentage(17, timeout, page); }); -}); \ No newline at end of file +}); From 1f52d01493d502b139f135a640bd0b80c23f5eaf Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Wed, 19 Jun 2024 15:19:31 +0200 Subject: [PATCH 045/113] Do not rely on claims to figure out user access + prepare for claims removal (#16552) * Do not rely on claims to figure out user access + prepare for claims removal * Fix typos :) * Ensure we fire all relevant notifications when creating and updating user groups * Leave it to the cache refreshers to flush user cache data (start nodes) --- .../BackOfficeAuthBuilderExtensions.cs | 3 - .../BackOfficeAuthPolicyBuilderExtensions.cs | 63 ++++--- ...AuthenticationTokensNotificationHandler.cs | 160 ------------------ .../User/AllowedApplicationHandler.cs | 24 +++ .../User/AllowedApplicationRequirement.cs | 14 ++ src/Umbraco.Core/Constants-Security.cs | 3 + .../Extensions/ClaimsIdentityExtensions.cs | 8 +- src/Umbraco.Core/Models/UserExtensions.cs | 24 +-- src/Umbraco.Core/Services/UserGroupService.cs | 22 ++- 9 files changed, 112 insertions(+), 209 deletions(-) create mode 100644 src/Umbraco.Cms.Api.Management/Security/Authorization/User/AllowedApplicationHandler.cs create mode 100644 src/Umbraco.Cms.Api.Management/Security/Authorization/User/AllowedApplicationRequirement.cs diff --git a/src/Umbraco.Cms.Api.Management/DependencyInjection/BackOfficeAuthBuilderExtensions.cs b/src/Umbraco.Cms.Api.Management/DependencyInjection/BackOfficeAuthBuilderExtensions.cs index e36f103e92..5ddb98b570 100644 --- a/src/Umbraco.Cms.Api.Management/DependencyInjection/BackOfficeAuthBuilderExtensions.cs +++ b/src/Umbraco.Cms.Api.Management/DependencyInjection/BackOfficeAuthBuilderExtensions.cs @@ -27,11 +27,8 @@ public static class BackOfficeAuthBuilderExtensions public static IUmbracoBuilder AddTokenRevocation(this IUmbracoBuilder builder) { - builder.AddNotificationAsyncHandler(); builder.AddNotificationAsyncHandler(); builder.AddNotificationAsyncHandler(); - builder.AddNotificationAsyncHandler(); - builder.AddNotificationAsyncHandler(); builder.AddNotificationAsyncHandler(); return builder; diff --git a/src/Umbraco.Cms.Api.Management/DependencyInjection/BackOfficeAuthPolicyBuilderExtensions.cs b/src/Umbraco.Cms.Api.Management/DependencyInjection/BackOfficeAuthPolicyBuilderExtensions.cs index 4714c54c74..45eccad5ec 100644 --- a/src/Umbraco.Cms.Api.Management/DependencyInjection/BackOfficeAuthPolicyBuilderExtensions.cs +++ b/src/Umbraco.Cms.Api.Management/DependencyInjection/BackOfficeAuthPolicyBuilderExtensions.cs @@ -28,6 +28,7 @@ internal static class BackOfficeAuthPolicyBuilderExtensions builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); + builder.Services.AddSingleton(); builder.Services.AddAuthorization(CreatePolicies); return builder; @@ -35,14 +36,12 @@ internal static class BackOfficeAuthPolicyBuilderExtensions private static void CreatePolicies(AuthorizationOptions options) { - void AddPolicy(string policyName, string claimType, params string[] allowedClaimValues) - { - options.AddPolicy(policyName, policy => + void AddAllowedApplicationsPolicy(string policyName, params string[] allowedClaimValues) + => options.AddPolicy(policyName, policy => { policy.AuthenticationSchemes.Add(OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme); - policy.RequireClaim(claimType, allowedClaimValues); + policy.Requirements.Add(new AllowedApplicationRequirement(allowedClaimValues)); }); - } options.AddPolicy(AuthorizationPolicies.BackOfficeAccess, policy => { @@ -56,39 +55,39 @@ internal static class BackOfficeAuthPolicyBuilderExtensions policy.RequireRole(Constants.Security.AdminGroupAlias); }); - AddPolicy(AuthorizationPolicies.SectionAccessContent, Constants.Security.AllowedApplicationsClaimType, Constants.Applications.Content); - AddPolicy(AuthorizationPolicies.SectionAccessContentOrMedia, Constants.Security.AllowedApplicationsClaimType, Constants.Applications.Content, Constants.Applications.Media); - AddPolicy(AuthorizationPolicies.SectionAccessForContentTree, Constants.Security.AllowedApplicationsClaimType, + AddAllowedApplicationsPolicy(AuthorizationPolicies.SectionAccessContent, Constants.Applications.Content); + AddAllowedApplicationsPolicy(AuthorizationPolicies.SectionAccessContentOrMedia, Constants.Applications.Content, Constants.Applications.Media); + AddAllowedApplicationsPolicy(AuthorizationPolicies.SectionAccessForContentTree, Constants.Applications.Content, Constants.Applications.Media, Constants.Applications.Users, Constants.Applications.Settings, Constants.Applications.Packages, Constants.Applications.Members); - AddPolicy(AuthorizationPolicies.SectionAccessForMediaTree, Constants.Security.AllowedApplicationsClaimType, + AddAllowedApplicationsPolicy(AuthorizationPolicies.SectionAccessForMediaTree, Constants.Applications.Content, Constants.Applications.Media, Constants.Applications.Users, Constants.Applications.Settings, Constants.Applications.Packages, Constants.Applications.Members); - AddPolicy(AuthorizationPolicies.SectionAccessForMemberTree, Constants.Security.AllowedApplicationsClaimType, + AddAllowedApplicationsPolicy(AuthorizationPolicies.SectionAccessForMemberTree, Constants.Applications.Content, Constants.Applications.Media, Constants.Applications.Members); - AddPolicy(AuthorizationPolicies.SectionAccessMedia, Constants.Security.AllowedApplicationsClaimType, Constants.Applications.Media); - AddPolicy(AuthorizationPolicies.SectionAccessMembers, Constants.Security.AllowedApplicationsClaimType, Constants.Applications.Members); - AddPolicy(AuthorizationPolicies.SectionAccessPackages, Constants.Security.AllowedApplicationsClaimType, Constants.Applications.Packages); - AddPolicy(AuthorizationPolicies.SectionAccessSettings, Constants.Security.AllowedApplicationsClaimType, Constants.Applications.Settings); - AddPolicy(AuthorizationPolicies.SectionAccessUsers, Constants.Security.AllowedApplicationsClaimType, Constants.Applications.Users); + AddAllowedApplicationsPolicy(AuthorizationPolicies.SectionAccessMedia, Constants.Applications.Media); + AddAllowedApplicationsPolicy(AuthorizationPolicies.SectionAccessMembers, Constants.Applications.Members); + AddAllowedApplicationsPolicy(AuthorizationPolicies.SectionAccessPackages, Constants.Applications.Packages); + AddAllowedApplicationsPolicy(AuthorizationPolicies.SectionAccessSettings, Constants.Applications.Settings); + AddAllowedApplicationsPolicy(AuthorizationPolicies.SectionAccessUsers, Constants.Applications.Users); - AddPolicy(AuthorizationPolicies.TreeAccessDataTypes, Constants.Security.AllowedApplicationsClaimType, Constants.Applications.Settings); - AddPolicy(AuthorizationPolicies.TreeAccessDictionary, Constants.Security.AllowedApplicationsClaimType, Constants.Applications.Translation); - AddPolicy(AuthorizationPolicies.TreeAccessDictionaryOrTemplates, Constants.Security.AllowedApplicationsClaimType, Constants.Applications.Translation, Constants.Applications.Settings); - AddPolicy(AuthorizationPolicies.TreeAccessDocuments, Constants.Security.AllowedApplicationsClaimType, Constants.Applications.Content); - AddPolicy(AuthorizationPolicies.TreeAccessDocumentsOrDocumentTypes, Constants.Security.AllowedApplicationsClaimType, Constants.Applications.Content, Constants.Applications.Settings); - AddPolicy(AuthorizationPolicies.TreeAccessDocumentTypes, Constants.Security.AllowedApplicationsClaimType, Constants.Applications.Settings); - AddPolicy(AuthorizationPolicies.TreeAccessLanguages, Constants.Security.AllowedApplicationsClaimType, Constants.Applications.Settings); - AddPolicy(AuthorizationPolicies.TreeAccessMediaTypes, Constants.Security.AllowedApplicationsClaimType, Constants.Applications.Settings); - AddPolicy(AuthorizationPolicies.TreeAccessMediaOrMediaTypes, Constants.Security.AllowedApplicationsClaimType, Constants.Applications.Media, Constants.Applications.Settings); - AddPolicy(AuthorizationPolicies.TreeAccessMemberGroups, Constants.Security.AllowedApplicationsClaimType, Constants.Applications.Members); - AddPolicy(AuthorizationPolicies.TreeAccessMemberTypes, Constants.Security.AllowedApplicationsClaimType, Constants.Applications.Settings); - AddPolicy(AuthorizationPolicies.TreeAccessPartialViews, Constants.Security.AllowedApplicationsClaimType, Constants.Applications.Settings); - AddPolicy(AuthorizationPolicies.TreeAccessRelationTypes, Constants.Security.AllowedApplicationsClaimType, Constants.Applications.Settings); - AddPolicy(AuthorizationPolicies.TreeAccessScripts, Constants.Security.AllowedApplicationsClaimType, Constants.Applications.Settings); - AddPolicy(AuthorizationPolicies.TreeAccessStylesheets, Constants.Security.AllowedApplicationsClaimType, Constants.Applications.Settings); - AddPolicy(AuthorizationPolicies.TreeAccessTemplates, Constants.Security.AllowedApplicationsClaimType, Constants.Applications.Settings); - AddPolicy(AuthorizationPolicies.TreeAccessWebhooks, Constants.Security.AllowedApplicationsClaimType, Constants.Applications.Settings); + AddAllowedApplicationsPolicy(AuthorizationPolicies.TreeAccessDataTypes, Constants.Applications.Settings); + AddAllowedApplicationsPolicy(AuthorizationPolicies.TreeAccessDictionary, Constants.Applications.Translation); + AddAllowedApplicationsPolicy(AuthorizationPolicies.TreeAccessDictionaryOrTemplates, Constants.Applications.Translation, Constants.Applications.Settings); + AddAllowedApplicationsPolicy(AuthorizationPolicies.TreeAccessDocuments, Constants.Applications.Content); + AddAllowedApplicationsPolicy(AuthorizationPolicies.TreeAccessDocumentsOrDocumentTypes, Constants.Applications.Content, Constants.Applications.Settings); + AddAllowedApplicationsPolicy(AuthorizationPolicies.TreeAccessDocumentTypes, Constants.Applications.Settings); + AddAllowedApplicationsPolicy(AuthorizationPolicies.TreeAccessLanguages, Constants.Applications.Settings); + AddAllowedApplicationsPolicy(AuthorizationPolicies.TreeAccessMediaTypes, Constants.Applications.Settings); + AddAllowedApplicationsPolicy(AuthorizationPolicies.TreeAccessMediaOrMediaTypes, Constants.Applications.Media, Constants.Applications.Settings); + AddAllowedApplicationsPolicy(AuthorizationPolicies.TreeAccessMemberGroups, Constants.Applications.Members); + AddAllowedApplicationsPolicy(AuthorizationPolicies.TreeAccessMemberTypes, Constants.Applications.Settings); + AddAllowedApplicationsPolicy(AuthorizationPolicies.TreeAccessPartialViews, Constants.Applications.Settings); + AddAllowedApplicationsPolicy(AuthorizationPolicies.TreeAccessRelationTypes, Constants.Applications.Settings); + AddAllowedApplicationsPolicy(AuthorizationPolicies.TreeAccessScripts, Constants.Applications.Settings); + AddAllowedApplicationsPolicy(AuthorizationPolicies.TreeAccessStylesheets, Constants.Applications.Settings); + AddAllowedApplicationsPolicy(AuthorizationPolicies.TreeAccessTemplates, Constants.Applications.Settings); + AddAllowedApplicationsPolicy(AuthorizationPolicies.TreeAccessWebhooks, Constants.Applications.Settings); // Contextual permissions options.AddPolicy(AuthorizationPolicies.ContentPermissionByResource, policy => diff --git a/src/Umbraco.Cms.Api.Management/Handlers/RevokeUserAuthenticationTokensNotificationHandler.cs b/src/Umbraco.Cms.Api.Management/Handlers/RevokeUserAuthenticationTokensNotificationHandler.cs index acf6cb508a..e56e776f85 100644 --- a/src/Umbraco.Cms.Api.Management/Handlers/RevokeUserAuthenticationTokensNotificationHandler.cs +++ b/src/Umbraco.Cms.Api.Management/Handlers/RevokeUserAuthenticationTokensNotificationHandler.cs @@ -13,77 +13,31 @@ using Umbraco.Extensions; namespace Umbraco.Cms.Api.Management.Handlers; internal sealed class RevokeUserAuthenticationTokensNotificationHandler : - INotificationAsyncHandler, INotificationAsyncHandler, INotificationAsyncHandler, - INotificationAsyncHandler, - INotificationAsyncHandler, INotificationAsyncHandler { - private const string NotificationStateKey = "Umbraco.Cms.Api.Management.Handlers.RevokeUserAuthenticationTokensNotificationHandler"; - private readonly IUserService _userService; - private readonly IUserGroupService _userGroupService; private readonly IOpenIddictTokenManager _tokenManager; private readonly ILogger _logger; private readonly SecuritySettings _securitySettings; public RevokeUserAuthenticationTokensNotificationHandler( IUserService userService, - IUserGroupService userGroupService, IOpenIddictTokenManager tokenManager, ILogger logger, IOptions securitySettingsOptions) { _userService = userService; - _userGroupService = userGroupService; _tokenManager = tokenManager; _logger = logger; _securitySettings = securitySettingsOptions.Value; } - // We need to know the pre-saving state of the saved users in order to compare if their access has changed - public async Task HandleAsync(UserSavingNotification notification, CancellationToken cancellationToken) - { - try - { - var usersAccess = new Dictionary(); - foreach (IUser user in notification.SavedEntities) - { - UserStartNodesAndGroupAccess? priorUserAccess = await GetRelevantUserAccessDataByUserKeyAsync(user.Key); - if (priorUserAccess == null) - { - continue; - } - - usersAccess.Add(user.Key, priorUserAccess); - } - - notification.State[NotificationStateKey] = usersAccess; - } - catch (DbException e) - { - _logger.LogWarning(e, "This is expected when we upgrade from < Umbraco 14. Otherwise it should not happen"); - } - } - public async Task HandleAsync(UserSavedNotification notification, CancellationToken cancellationToken) { try { - Dictionary? preSavingUsersState = null; - - if (notification.State.TryGetValue(NotificationStateKey, out var value)) - { - preSavingUsersState = value as Dictionary; - } - - // If we have a new user, there is no token - if (preSavingUsersState is null || preSavingUsersState.Count == 0) - { - return; - } - foreach (IUser user in notification.SavedEntities) { if (user.IsSuper()) @@ -95,23 +49,6 @@ internal sealed class RevokeUserAuthenticationTokensNotificationHandler : if (user.IsLockedOut || user.IsApproved is false) { await RevokeTokensAsync(user); - continue; - } - - // Don't revoke admin tokens to prevent log out when accidental changes - if (user.IsAdmin()) - { - continue; - } - - // Check if the user access has changed - we also need to revoke all tokens in this case - if (preSavingUsersState.TryGetValue(user.Key, out UserStartNodesAndGroupAccess? preSavingState)) - { - UserStartNodesAndGroupAccess postSavingState = MapToUserStartNodesAndGroupAccess(user); - if (preSavingState.CompareAccess(postSavingState) == false) - { - await RevokeTokensAsync(user); - } } } } @@ -131,49 +68,6 @@ internal sealed class RevokeUserAuthenticationTokensNotificationHandler : } } - // We need to know the pre-deleting state of the users part of the deleted group to revoke their tokens - public async Task HandleAsync(UserGroupDeletingNotification notification, CancellationToken cancellationToken) - { - var usersInGroups = new Dictionary>(); - foreach (IUserGroup userGroup in notification.DeletedEntities) - { - var users = await GetUsersByGroupKeyAsync(userGroup.Key); - if (users == null) - { - continue; - } - - usersInGroups.Add(userGroup.Key, users); - } - - notification.State[NotificationStateKey] = usersInGroups; - } - - public async Task HandleAsync(UserGroupDeletedNotification notification, CancellationToken cancellationToken) - { - Dictionary>? preDeletingUsersInGroups = null; - - if (notification.State.TryGetValue(NotificationStateKey, out var value)) - { - preDeletingUsersInGroups = value as Dictionary>; - } - - if (preDeletingUsersInGroups is null) - { - return; - } - - // since the user group was deleted, we can only use the information we collected before the deletion - // this means that we will not be able to detect users in any groups that were eventually deleted (due to implementor/3th party supplier interference) - // that were not in the initial to be deleted list - foreach (IUser user in preDeletingUsersInGroups - .Where(group => notification.DeletedEntities.Any(entity => group.Key == entity.Key)) - .SelectMany(group => group.Value)) - { - await RevokeTokensAsync(user); - } - } - public async Task HandleAsync(UserLoginSuccessNotification notification, CancellationToken cancellationToken) { if (_securitySettings.AllowConcurrentLogins is false) @@ -190,29 +84,6 @@ internal sealed class RevokeUserAuthenticationTokensNotificationHandler : } } - // Get data about the user before saving - private async Task GetRelevantUserAccessDataByUserKeyAsync(Guid userKey) - { - IUser? user = await _userService.GetAsync(userKey); - - return user is null - ? null - : MapToUserStartNodesAndGroupAccess(user); - } - - private UserStartNodesAndGroupAccess MapToUserStartNodesAndGroupAccess(IUser user) - => new(user.Groups.Select(g => g.Key), user.StartContentIds, user.StartMediaIds); - - // Get data about the users part of a group before deleting it - private async Task?> GetUsersByGroupKeyAsync(Guid userGroupKey) - { - IUserGroup? userGroup = await _userGroupService.GetAsync(userGroupKey); - - return userGroup is null - ? null - : _userService.GetAllInGroup(userGroup.Id); - } - private async Task RevokeTokensAsync(IUser user) { _logger.LogInformation("Revoking active tokens for user with ID {id}", user.Id); @@ -236,35 +107,4 @@ internal sealed class RevokeUserAuthenticationTokensNotificationHandler : return null; } - - private class UserStartNodesAndGroupAccess - { - public IEnumerable GroupKeys { get; } - - public int[]? StartContentIds { get; } - - public int[]? StartMediaIds { get; } - - public UserStartNodesAndGroupAccess(IEnumerable groupKeys, int[]? startContentIds, int[]? startMediaIds) - { - GroupKeys = groupKeys; - StartContentIds = startContentIds; - StartMediaIds = startMediaIds; - } - - public bool CompareAccess(UserStartNodesAndGroupAccess other) - { - var areContentStartNodesEqual = (StartContentIds == null && other.StartContentIds == null) || - (StartContentIds != null && other.StartContentIds != null && - StartContentIds.SequenceEqual(other.StartContentIds)); - - var areMediaStartNodesEqual = (StartMediaIds == null && other.StartMediaIds == null) || - (StartMediaIds != null && other.StartMediaIds != null && - StartMediaIds.SequenceEqual(other.StartMediaIds)); - - return areContentStartNodesEqual && - areMediaStartNodesEqual && - GroupKeys.SequenceEqual(other.GroupKeys); - } - } } diff --git a/src/Umbraco.Cms.Api.Management/Security/Authorization/User/AllowedApplicationHandler.cs b/src/Umbraco.Cms.Api.Management/Security/Authorization/User/AllowedApplicationHandler.cs new file mode 100644 index 0000000000..a36a592827 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Security/Authorization/User/AllowedApplicationHandler.cs @@ -0,0 +1,24 @@ +using Microsoft.AspNetCore.Authorization; +using Umbraco.Cms.Core.Models.Membership; +using Umbraco.Cms.Core.Security.Authorization; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Api.Management.Security.Authorization.User; + +/// +/// Authorizes that the current user has the correct permission access to the applications listed in the requirement. +/// +internal sealed class AllowedApplicationHandler : MustSatisfyRequirementAuthorizationHandler +{ + private readonly IAuthorizationHelper _authorizationHelper; + + public AllowedApplicationHandler(IAuthorizationHelper authorizationHelper) + => _authorizationHelper = authorizationHelper; + + protected override Task IsAuthorized(AuthorizationHandlerContext context, AllowedApplicationRequirement requirement) + { + IUser user = _authorizationHelper.GetUmbracoUser(context.User); + var allowed = user.AllowedSections.ContainsAny(requirement.Applications); + return Task.FromResult(allowed); + } +} diff --git a/src/Umbraco.Cms.Api.Management/Security/Authorization/User/AllowedApplicationRequirement.cs b/src/Umbraco.Cms.Api.Management/Security/Authorization/User/AllowedApplicationRequirement.cs new file mode 100644 index 0000000000..dce6d8773e --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Security/Authorization/User/AllowedApplicationRequirement.cs @@ -0,0 +1,14 @@ +using Microsoft.AspNetCore.Authorization; + +namespace Umbraco.Cms.Api.Management.Security.Authorization.User; + +/// +/// Authorization requirement for the . +/// +internal sealed class AllowedApplicationRequirement : IAuthorizationRequirement +{ + public string[] Applications { get; } + + public AllowedApplicationRequirement(params string[] applications) + => Applications = applications; +} diff --git a/src/Umbraco.Core/Constants-Security.cs b/src/Umbraco.Core/Constants-Security.cs index 842add06fa..2f24fa182d 100644 --- a/src/Umbraco.Core/Constants-Security.cs +++ b/src/Umbraco.Core/Constants-Security.cs @@ -93,12 +93,15 @@ public static partial class Constants public const string MemberExternalAuthenticationTypePrefix = "UmbracoMembers."; + [Obsolete("Please use the UserExtensions class to access user start node info. Will be removed in V15.")] public const string StartContentNodeIdClaimType = "http://umbraco.org/2015/02/identity/claims/backoffice/startcontentnode"; + [Obsolete("Please use the UserExtensions class to access user start node info. Will be removed in V15.")] public const string StartMediaNodeIdClaimType = "http://umbraco.org/2015/02/identity/claims/backoffice/startmedianode"; + [Obsolete("Please use IUser.AllowedSections instead. Will be removed in V15.")] public const string AllowedApplicationsClaimType = "http://umbraco.org/2015/02/identity/claims/backoffice/allowedapp"; diff --git a/src/Umbraco.Core/Extensions/ClaimsIdentityExtensions.cs b/src/Umbraco.Core/Extensions/ClaimsIdentityExtensions.cs index 38f9bf15ff..73157767b2 100644 --- a/src/Umbraco.Core/Extensions/ClaimsIdentityExtensions.cs +++ b/src/Umbraco.Core/Extensions/ClaimsIdentityExtensions.cs @@ -34,8 +34,6 @@ public static class ClaimsIdentityExtensions ClaimTypes.Name, // username ClaimTypes.GivenName, - // Constants.Security.StartContentNodeIdClaimType, These seem to be able to be null... - // Constants.Security.StartMediaNodeIdClaimType, ClaimTypes.Locality, Constants.Security.SecurityStampClaimType, }; @@ -250,6 +248,7 @@ public static class ClaimsIdentityExtensions identity)); } + // NOTE: this can be removed when the obsolete claim type has been deleted if (identity.HasClaim(x => x.Type == Constants.Security.StartContentNodeIdClaimType) == false && startContentNodes != null) { @@ -265,6 +264,7 @@ public static class ClaimsIdentityExtensions } } + // NOTE: this can be removed when the obsolete claim type has been deleted if (identity.HasClaim(x => x.Type == Constants.Security.StartMediaNodeIdClaimType) == false && startMediaNodes != null) { @@ -304,6 +304,7 @@ public static class ClaimsIdentityExtensions } // Add each app as a separate claim + // NOTE: this can be removed when the obsolete claim type has been deleted if (identity.HasClaim(x => x.Type == Constants.Security.AllowedApplicationsClaimType) == false && allowedApps != null) { foreach (var application in allowedApps) @@ -343,6 +344,7 @@ public static class ClaimsIdentityExtensions /// /// /// Array of start content nodes + [Obsolete("Please use the UserExtensions class to access user start node info. Will be removed in V15.")] public static int[] GetStartContentNodes(this ClaimsIdentity identity) => identity.FindAll(x => x.Type == Constants.Security.StartContentNodeIdClaimType) .Select(node => int.TryParse(node.Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var i) @@ -355,6 +357,7 @@ public static class ClaimsIdentityExtensions /// /// /// Array of start media nodes + [Obsolete("Please use the UserExtensions class to access user start node info. Will be removed in V15.")] public static int[] GetStartMediaNodes(this ClaimsIdentity identity) => identity.FindAll(x => x.Type == Constants.Security.StartMediaNodeIdClaimType) .Select(node => int.TryParse(node.Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var i) @@ -367,6 +370,7 @@ public static class ClaimsIdentityExtensions /// /// /// + [Obsolete("Please use IUser.AllowedSections instead. Will be removed in V15.")] public static string[] GetAllowedApplications(this ClaimsIdentity identity) => identity .FindAll(x => x.Type == Constants.Security.AllowedApplicationsClaimType).Select(app => app.Value).ToArray(); diff --git a/src/Umbraco.Core/Models/UserExtensions.cs b/src/Umbraco.Core/Models/UserExtensions.cs index 6682e6e055..b3c115c41b 100644 --- a/src/Umbraco.Core/Models/UserExtensions.cs +++ b/src/Umbraco.Core/Models/UserExtensions.cs @@ -1,5 +1,3 @@ -using System.Net; -using System.Security.Cryptography; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Media; @@ -155,8 +153,8 @@ public static class UserExtensions public static int[]? CalculateContentStartNodeIds(this IUser user, IEntityService entityService, AppCaches appCaches) { - var cacheKey = CacheKeys.UserAllContentStartNodesPrefix + user.Key; - IAppPolicyCache runtimeCache = appCaches.IsolatedCaches.GetOrCreate(); + var cacheKey = user.UserCacheKey(CacheKeys.UserAllContentStartNodesPrefix); + IAppPolicyCache runtimeCache = GetUserCache(appCaches); var result = runtimeCache.GetCacheItem( cacheKey, () => @@ -189,8 +187,8 @@ public static class UserExtensions /// public static int[]? CalculateMediaStartNodeIds(this IUser user, IEntityService entityService, AppCaches appCaches) { - var cacheKey = CacheKeys.UserAllMediaStartNodesPrefix + user.Id; - IAppPolicyCache runtimeCache = appCaches.IsolatedCaches.GetOrCreate(); + var cacheKey = user.UserCacheKey(CacheKeys.UserAllMediaStartNodesPrefix); + IAppPolicyCache runtimeCache = GetUserCache(appCaches); var result = runtimeCache.GetCacheItem( cacheKey, () => @@ -214,8 +212,8 @@ public static class UserExtensions public static string[]? GetMediaStartNodePaths(this IUser user, IEntityService entityService, AppCaches appCaches) { - var cacheKey = CacheKeys.UserMediaStartNodePathsPrefix + user.Id; - IAppPolicyCache runtimeCache = appCaches.IsolatedCaches.GetOrCreate(); + var cacheKey = user.UserCacheKey(CacheKeys.UserMediaStartNodePathsPrefix); + IAppPolicyCache runtimeCache = GetUserCache(appCaches); var result = runtimeCache.GetCacheItem( cacheKey, () => @@ -232,8 +230,8 @@ public static class UserExtensions public static string[]? GetContentStartNodePaths(this IUser user, IEntityService entityService, AppCaches appCaches) { - var cacheKey = CacheKeys.UserContentStartNodePathsPrefix + user.Id; - IAppPolicyCache runtimeCache = appCaches.IsolatedCaches.GetOrCreate(); + var cacheKey = user.UserCacheKey(CacheKeys.UserContentStartNodePathsPrefix); + IAppPolicyCache runtimeCache = GetUserCache(appCaches); var result = runtimeCache.GetCacheItem( cacheKey, () => @@ -317,6 +315,12 @@ public static class UserExtensions return lsn.ToArray(); } + private static IAppPolicyCache GetUserCache(AppCaches appCaches) + => appCaches.IsolatedCaches.GetOrCreate(); + + private static string UserCacheKey(this IUser user, string cacheKey) + => $"{cacheKey}{user.Key}"; + private static bool StartsWithPath(string test, string path) => test.StartsWith(path) && test.Length > path.Length && test[path.Length] == ','; diff --git a/src/Umbraco.Core/Services/UserGroupService.cs b/src/Umbraco.Core/Services/UserGroupService.cs index 56c2222b3c..fbe6a52386 100644 --- a/src/Umbraco.Core/Services/UserGroupService.cs +++ b/src/Umbraco.Core/Services/UserGroupService.cs @@ -314,8 +314,7 @@ internal sealed class UserGroupService : RepositoryService, IUserGroupService // Since this is a brand new creation we don't have to be worried about what users were added and removed // simply put all members that are requested to be in the group will be "added" var userGroupWithUsers = new UserGroupWithUsers(userGroup, usersToAdd, Array.Empty()); - var savingUserGroupWithUsersNotification = - new UserGroupWithUsersSavingNotification(userGroupWithUsers, eventMessages); + var savingUserGroupWithUsersNotification = new UserGroupWithUsersSavingNotification(userGroupWithUsers, eventMessages); if (await scope.Notifications.PublishCancelableAsync(savingUserGroupWithUsersNotification)) { scope.Complete(); @@ -324,6 +323,11 @@ internal sealed class UserGroupService : RepositoryService, IUserGroupService _userGroupRepository.AddOrUpdateGroupWithUsers(userGroup, usersToAdd.Select(x => x.Id).ToArray()); + scope.Notifications.Publish( + new UserGroupSavedNotification(userGroup, eventMessages).WithStateFrom(savingNotification)); + scope.Notifications.Publish( + new UserGroupWithUsersSavedNotification(userGroupWithUsers, eventMessages).WithStateFrom(savingUserGroupWithUsersNotification)); + scope.Complete(); return Attempt.SucceedWithStatus(UserGroupOperationStatus.Success, userGroup); } @@ -385,9 +389,23 @@ internal sealed class UserGroupService : RepositoryService, IUserGroupService return Attempt.FailWithStatus(UserGroupOperationStatus.CancelledByNotification, userGroup); } + // We need to fire this notification - both for backwards compat, and to ensure caches across all servers. + // Since we are not adding or removing any users, we'll just fire the notification with empty collections + // for "added" and "removed" users. + var userGroupWithUsers = new UserGroupWithUsers(userGroup, [], []); + var savingUserGroupWithUsersNotification = new UserGroupWithUsersSavingNotification(userGroupWithUsers, eventMessages); + if (await scope.Notifications.PublishCancelableAsync(savingUserGroupWithUsersNotification)) + { + scope.Complete(); + return Attempt.FailWithStatus(UserGroupOperationStatus.CancelledByNotification, userGroup); + } + _userGroupRepository.Save(userGroup); + scope.Notifications.Publish( new UserGroupSavedNotification(userGroup, eventMessages).WithStateFrom(savingNotification)); + scope.Notifications.Publish( + new UserGroupWithUsersSavedNotification(userGroupWithUsers, eventMessages).WithStateFrom(savingUserGroupWithUsersNotification)); scope.Complete(); return Attempt.SucceedWithStatus(UserGroupOperationStatus.Success, userGroup); From 75c42f4ea4e365f7d19f3244e851f94845cc01d6 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Wed, 19 Jun 2024 15:21:57 +0200 Subject: [PATCH 046/113] Added post configuration of OpenIddictServerOptions that removes the ValidateTransportSecurityRequirement iff globalsettings.usehttps is false. (#16614) --- .../Configuration/PostConfigureOpenIddict.cs | 44 +++++++++++++++++++ .../UmbracoBuilderAuthExtensions.cs | 2 + 2 files changed, 46 insertions(+) create mode 100644 src/Umbraco.Cms.Api.Common/Configuration/PostConfigureOpenIddict.cs diff --git a/src/Umbraco.Cms.Api.Common/Configuration/PostConfigureOpenIddict.cs b/src/Umbraco.Cms.Api.Common/Configuration/PostConfigureOpenIddict.cs new file mode 100644 index 0000000000..f01b71fbb1 --- /dev/null +++ b/src/Umbraco.Cms.Api.Common/Configuration/PostConfigureOpenIddict.cs @@ -0,0 +1,44 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using OpenIddict.Server; +using OpenIddict.Server.AspNetCore; +using Umbraco.Cms.Core.Configuration.Models; + +namespace Umbraco.Cms.Api.Common.Configuration; + +internal class PostConfigureOpenIddict : IPostConfigureOptions +{ + private readonly IOptions _globalSettings; + + public PostConfigureOpenIddict(IOptions globalSettings) + { + _globalSettings = globalSettings; + } + + public void PostConfigure(string? name, OpenIddictServerOptions options) + { + EnsureHttpsIsNotRequiredWhenConfigAllowHttp(options); + } + + /// + /// Ensures OpenIddict is configured to allow Http requrest, if and only if, the global settings are configured to allow Http. + /// + /// + /// The logic actually allowing http by removing the ValidateTransportSecurityRequirement Descriptor is borrowed from + /// + private void EnsureHttpsIsNotRequiredWhenConfigAllowHttp(OpenIddictServerOptions options) + { + if (_globalSettings.Value.UseHttps is false) + { + OpenIddictServerHandlerDescriptor descriptor = OpenIddictServerAspNetCoreHandlers.ValidateTransportSecurityRequirement.Descriptor; + + for (var index = options.Handlers.Count - 1; index >= 0; index--) + { + if (options.Handlers[index].ServiceDescriptor.ServiceType == descriptor.ServiceDescriptor.ServiceType) + { + options.Handlers.RemoveAt(index); + } + } + } + } +} diff --git a/src/Umbraco.Cms.Api.Common/DependencyInjection/UmbracoBuilderAuthExtensions.cs b/src/Umbraco.Cms.Api.Common/DependencyInjection/UmbracoBuilderAuthExtensions.cs index 7e730695f3..c215eeecf8 100644 --- a/src/Umbraco.Cms.Api.Common/DependencyInjection/UmbracoBuilderAuthExtensions.cs +++ b/src/Umbraco.Cms.Api.Common/DependencyInjection/UmbracoBuilderAuthExtensions.cs @@ -4,6 +4,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.IdentityModel.Tokens; using OpenIddict.Server; using OpenIddict.Validation; +using Umbraco.Cms.Api.Common.Configuration; using Umbraco.Cms.Api.Common.Security; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Configuration.Models; @@ -132,5 +133,6 @@ public static class UmbracoBuilderAuthExtensions }); builder.Services.AddRecurringBackgroundJob(); + builder.Services.ConfigureOptions(); } } From f717a5d0b7646dd72397101c2ddf01e6b7db5784 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Thu, 20 Jun 2024 10:39:24 +0200 Subject: [PATCH 047/113] Simplified how we disable the TransportSecurityRequirement in OpenIddict (#16629) --- .../Configuration/ConfigureOpenIddict.cs | 15 +++++++ .../Configuration/PostConfigureOpenIddict.cs | 44 ------------------- .../UmbracoBuilderAuthExtensions.cs | 2 +- 3 files changed, 16 insertions(+), 45 deletions(-) create mode 100644 src/Umbraco.Cms.Api.Common/Configuration/ConfigureOpenIddict.cs delete mode 100644 src/Umbraco.Cms.Api.Common/Configuration/PostConfigureOpenIddict.cs diff --git a/src/Umbraco.Cms.Api.Common/Configuration/ConfigureOpenIddict.cs b/src/Umbraco.Cms.Api.Common/Configuration/ConfigureOpenIddict.cs new file mode 100644 index 0000000000..f428957bd9 --- /dev/null +++ b/src/Umbraco.Cms.Api.Common/Configuration/ConfigureOpenIddict.cs @@ -0,0 +1,15 @@ +using Microsoft.Extensions.Options; +using OpenIddict.Server.AspNetCore; +using Umbraco.Cms.Core.Configuration.Models; + +namespace Umbraco.Cms.Api.Common.Configuration; + +internal class ConfigureOpenIddict : IConfigureOptions +{ + private readonly IOptions _globalSettings; + + public ConfigureOpenIddict(IOptions globalSettings) => _globalSettings = globalSettings; + + public void Configure(OpenIddictServerAspNetCoreOptions options) + => options.DisableTransportSecurityRequirement = _globalSettings.Value.UseHttps is false; +} diff --git a/src/Umbraco.Cms.Api.Common/Configuration/PostConfigureOpenIddict.cs b/src/Umbraco.Cms.Api.Common/Configuration/PostConfigureOpenIddict.cs deleted file mode 100644 index f01b71fbb1..0000000000 --- a/src/Umbraco.Cms.Api.Common/Configuration/PostConfigureOpenIddict.cs +++ /dev/null @@ -1,44 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Options; -using OpenIddict.Server; -using OpenIddict.Server.AspNetCore; -using Umbraco.Cms.Core.Configuration.Models; - -namespace Umbraco.Cms.Api.Common.Configuration; - -internal class PostConfigureOpenIddict : IPostConfigureOptions -{ - private readonly IOptions _globalSettings; - - public PostConfigureOpenIddict(IOptions globalSettings) - { - _globalSettings = globalSettings; - } - - public void PostConfigure(string? name, OpenIddictServerOptions options) - { - EnsureHttpsIsNotRequiredWhenConfigAllowHttp(options); - } - - /// - /// Ensures OpenIddict is configured to allow Http requrest, if and only if, the global settings are configured to allow Http. - /// - /// - /// The logic actually allowing http by removing the ValidateTransportSecurityRequirement Descriptor is borrowed from - /// - private void EnsureHttpsIsNotRequiredWhenConfigAllowHttp(OpenIddictServerOptions options) - { - if (_globalSettings.Value.UseHttps is false) - { - OpenIddictServerHandlerDescriptor descriptor = OpenIddictServerAspNetCoreHandlers.ValidateTransportSecurityRequirement.Descriptor; - - for (var index = options.Handlers.Count - 1; index >= 0; index--) - { - if (options.Handlers[index].ServiceDescriptor.ServiceType == descriptor.ServiceDescriptor.ServiceType) - { - options.Handlers.RemoveAt(index); - } - } - } - } -} diff --git a/src/Umbraco.Cms.Api.Common/DependencyInjection/UmbracoBuilderAuthExtensions.cs b/src/Umbraco.Cms.Api.Common/DependencyInjection/UmbracoBuilderAuthExtensions.cs index c215eeecf8..3619da3071 100644 --- a/src/Umbraco.Cms.Api.Common/DependencyInjection/UmbracoBuilderAuthExtensions.cs +++ b/src/Umbraco.Cms.Api.Common/DependencyInjection/UmbracoBuilderAuthExtensions.cs @@ -133,6 +133,6 @@ public static class UmbracoBuilderAuthExtensions }); builder.Services.AddRecurringBackgroundJob(); - builder.Services.ConfigureOptions(); + builder.Services.ConfigureOptions(); } } From 00ca9e0d2dbfb9c8a75fd8cfc00ca272ae18efbe Mon Sep 17 00:00:00 2001 From: Mole Date: Thu, 20 Jun 2024 11:25:02 +0200 Subject: [PATCH 048/113] V13: Eaglery route domains for virtual page controllers (#16635) * Do domain routing eagerly * Cleanup * Fix comment --- .../Routing/EagerMatcherPolicy.cs | 39 ++++++++++++++++++- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.Website/Routing/EagerMatcherPolicy.cs b/src/Umbraco.Web.Website/Routing/EagerMatcherPolicy.cs index bb5cb52a4d..b6cd4a3615 100644 --- a/src/Umbraco.Web.Website/Routing/EagerMatcherPolicy.cs +++ b/src/Umbraco.Web.Website/Routing/EagerMatcherPolicy.cs @@ -8,7 +8,9 @@ using Umbraco.Cms.Core; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Routing; using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Web; using Umbraco.Cms.Web.Common.Controllers; +using Umbraco.Cms.Web.Common.Routing; using Umbraco.Cms.Web.Website.Controllers; using Umbraco.Extensions; @@ -37,6 +39,8 @@ internal class EagerMatcherPolicy : MatcherPolicy, IEndpointSelectorPolicy private readonly IRuntimeState _runtimeState; private readonly EndpointDataSource _endpointDataSource; private readonly UmbracoRequestPaths _umbracoRequestPaths; + private readonly IUmbracoContextAccessor _umbracoContextAccessor; + private readonly IPublishedRouter _publishedRouter; private GlobalSettings _globalSettings; private readonly Lazy _installEndpoint; private readonly Lazy _renderEndpoint; @@ -45,11 +49,15 @@ internal class EagerMatcherPolicy : MatcherPolicy, IEndpointSelectorPolicy IRuntimeState runtimeState, EndpointDataSource endpointDataSource, UmbracoRequestPaths umbracoRequestPaths, - IOptionsMonitor globalSettings) + IOptionsMonitor globalSettings, + IUmbracoContextAccessor umbracoContextAccessor, + IPublishedRouter publishedRouter) { _runtimeState = runtimeState; _endpointDataSource = endpointDataSource; _umbracoRequestPaths = umbracoRequestPaths; + _umbracoContextAccessor = umbracoContextAccessor; + _publishedRouter = publishedRouter; _globalSettings = globalSettings.CurrentValue; globalSettings.OnChange(settings => _globalSettings = settings); _installEndpoint = new Lazy(GetInstallEndpoint); @@ -112,11 +120,22 @@ internal class EagerMatcherPolicy : MatcherPolicy, IEndpointSelectorPolicy ControllerActionDescriptor? controllerDescriptor = routeEndpoint.Metadata.GetMetadata(); TypeInfo? controllerTypeInfo = controllerDescriptor?.ControllerTypeInfo; if (controllerTypeInfo is not null && - (controllerTypeInfo.IsType() || controllerTypeInfo.IsType())) + (controllerTypeInfo.IsType() + || controllerTypeInfo.IsType())) { return; } + // 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. + // 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. + if (controllerTypeInfo is not null && controllerTypeInfo.IsType()) + { + await RouteVirtualRequestAsync(httpContext); + } + if (routeEndpoint.Order < lowestOrder) { // We have to ensure that the route is valid for the current request method. @@ -153,6 +172,22 @@ internal class EagerMatcherPolicy : MatcherPolicy, IEndpointSelectorPolicy } } + private async Task RouteVirtualRequestAsync(HttpContext context) + { + if (_umbracoContextAccessor.TryGetUmbracoContext(out IUmbracoContext? umbracoContext) is false) + { + return; + } + + IPublishedRequestBuilder requestBuilder = + await _publishedRouter.CreateRequestAsync(umbracoContext.CleanedUmbracoUrl); + _publishedRouter.RouteDomain(requestBuilder); + // This is just a temporary RouteValues object just for culture which will be overwritten later + // so we can just use a dummy action descriptor. + var umbracoRouteValues = new UmbracoRouteValues(requestBuilder.Build(), new ControllerActionDescriptor()); + context.Features.Set(umbracoRouteValues); + } + /// /// Replaces the first endpoint candidate with the specified endpoint, invalidating all other candidates, /// guaranteeing that the specified endpoint will be hit. From 56710d5b5be2ecc2d09bce04168b2fdd149d7341 Mon Sep 17 00:00:00 2001 From: Mole Date: Thu, 20 Jun 2024 11:25:02 +0200 Subject: [PATCH 049/113] V13: Eaglery route domains for virtual page controllers (#16635) * Do domain routing eagerly * Cleanup * Fix comment --- .../Routing/EagerMatcherPolicy.cs | 39 ++++++++++++++++++- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.Website/Routing/EagerMatcherPolicy.cs b/src/Umbraco.Web.Website/Routing/EagerMatcherPolicy.cs index bb5cb52a4d..b6cd4a3615 100644 --- a/src/Umbraco.Web.Website/Routing/EagerMatcherPolicy.cs +++ b/src/Umbraco.Web.Website/Routing/EagerMatcherPolicy.cs @@ -8,7 +8,9 @@ using Umbraco.Cms.Core; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Routing; using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Web; using Umbraco.Cms.Web.Common.Controllers; +using Umbraco.Cms.Web.Common.Routing; using Umbraco.Cms.Web.Website.Controllers; using Umbraco.Extensions; @@ -37,6 +39,8 @@ internal class EagerMatcherPolicy : MatcherPolicy, IEndpointSelectorPolicy private readonly IRuntimeState _runtimeState; private readonly EndpointDataSource _endpointDataSource; private readonly UmbracoRequestPaths _umbracoRequestPaths; + private readonly IUmbracoContextAccessor _umbracoContextAccessor; + private readonly IPublishedRouter _publishedRouter; private GlobalSettings _globalSettings; private readonly Lazy _installEndpoint; private readonly Lazy _renderEndpoint; @@ -45,11 +49,15 @@ internal class EagerMatcherPolicy : MatcherPolicy, IEndpointSelectorPolicy IRuntimeState runtimeState, EndpointDataSource endpointDataSource, UmbracoRequestPaths umbracoRequestPaths, - IOptionsMonitor globalSettings) + IOptionsMonitor globalSettings, + IUmbracoContextAccessor umbracoContextAccessor, + IPublishedRouter publishedRouter) { _runtimeState = runtimeState; _endpointDataSource = endpointDataSource; _umbracoRequestPaths = umbracoRequestPaths; + _umbracoContextAccessor = umbracoContextAccessor; + _publishedRouter = publishedRouter; _globalSettings = globalSettings.CurrentValue; globalSettings.OnChange(settings => _globalSettings = settings); _installEndpoint = new Lazy(GetInstallEndpoint); @@ -112,11 +120,22 @@ internal class EagerMatcherPolicy : MatcherPolicy, IEndpointSelectorPolicy ControllerActionDescriptor? controllerDescriptor = routeEndpoint.Metadata.GetMetadata(); TypeInfo? controllerTypeInfo = controllerDescriptor?.ControllerTypeInfo; if (controllerTypeInfo is not null && - (controllerTypeInfo.IsType() || controllerTypeInfo.IsType())) + (controllerTypeInfo.IsType() + || controllerTypeInfo.IsType())) { return; } + // 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. + // 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. + if (controllerTypeInfo is not null && controllerTypeInfo.IsType()) + { + await RouteVirtualRequestAsync(httpContext); + } + if (routeEndpoint.Order < lowestOrder) { // We have to ensure that the route is valid for the current request method. @@ -153,6 +172,22 @@ internal class EagerMatcherPolicy : MatcherPolicy, IEndpointSelectorPolicy } } + private async Task RouteVirtualRequestAsync(HttpContext context) + { + if (_umbracoContextAccessor.TryGetUmbracoContext(out IUmbracoContext? umbracoContext) is false) + { + return; + } + + IPublishedRequestBuilder requestBuilder = + await _publishedRouter.CreateRequestAsync(umbracoContext.CleanedUmbracoUrl); + _publishedRouter.RouteDomain(requestBuilder); + // This is just a temporary RouteValues object just for culture which will be overwritten later + // so we can just use a dummy action descriptor. + var umbracoRouteValues = new UmbracoRouteValues(requestBuilder.Build(), new ControllerActionDescriptor()); + context.Features.Set(umbracoRouteValues); + } + /// /// Replaces the first endpoint candidate with the specified endpoint, invalidating all other candidates, /// guaranteeing that the specified endpoint will be hit. From b2b112eb8b7964968650412313fe94e096dd3773 Mon Sep 17 00:00:00 2001 From: Andreas Zerbst <73799582+andr317c@users.noreply.github.com> Date: Mon, 17 Jun 2024 10:49:16 +0200 Subject: [PATCH 050/113] V13 QA Updated depedencies (#16606) * Bumped version of helper and builder * Removed faker --- .../package-lock.json | 28 +++++++------------ .../Umbraco.Tests.AcceptanceTest/package.json | 5 ++-- 2 files changed, 12 insertions(+), 21 deletions(-) diff --git a/tests/Umbraco.Tests.AcceptanceTest/package-lock.json b/tests/Umbraco.Tests.AcceptanceTest/package-lock.json index 82892582ce..e3bb3947ff 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/package-lock.json +++ b/tests/Umbraco.Tests.AcceptanceTest/package-lock.json @@ -7,11 +7,10 @@ "name": "acceptancetest", "hasInstallScript": true, "dependencies": { - "@umbraco/json-models-builders": "^1.0.6", - "@umbraco/playwright-testhelpers": "^1.0.25", + "@umbraco/json-models-builders": "^1.0.8", + "@umbraco/playwright-testhelpers": "^1.0.28", "camelize": "^1.0.0", "dotenv": "^16.0.2", - "faker": "^4.1.0", "form-data": "^4.0.0", "node-fetch": "^2.6.7", "xhr2": "^0.2.1" @@ -122,22 +121,20 @@ "dev": true }, "node_modules/@umbraco/json-models-builders": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@umbraco/json-models-builders/-/json-models-builders-1.0.6.tgz", - "integrity": "sha512-bXwfXcpuqG1Ye714L9KJEGXuSzJfckysE/6CuPjdG8FqHWTE1brv28teR2oMw+ih8ca2u2zUboRgdzLEU/1D3Q==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@umbraco/json-models-builders/-/json-models-builders-1.0.8.tgz", + "integrity": "sha512-qBiOwaFO0V/hRpByBnwWe65nlNvaKNQPQj17MCiUTkCf1LxkB1T0ZoQv50vvnLFx6xUBZKfLhEXqYmoJqsQsPg==", "dependencies": { - "camelize": "^1.0.0", - "faker": "^4.1.0" + "camelize": "^1.0.0" } }, "node_modules/@umbraco/playwright-testhelpers": { - "version": "1.0.25", - "resolved": "https://registry.npmjs.org/@umbraco/playwright-testhelpers/-/playwright-testhelpers-1.0.25.tgz", - "integrity": "sha512-6H452J6LhP0EHjF4jR7V7i0U8WPTiAbSyhN1J459BbbYEJ4QX1A2ZlCdA6VSBAsK1xYdMXD+yxsVJq7AAwiy9A==", + "version": "1.0.28", + "resolved": "https://registry.npmjs.org/@umbraco/playwright-testhelpers/-/playwright-testhelpers-1.0.28.tgz", + "integrity": "sha512-AOk0eKkGV1Tyhb+iac9hyKzm2wCqwf+ELRQsiCWNVW8DxrPZiXIqQyjeI22YFuzkVJ3MJBDcoSwp7d31b1gm/w==", "dependencies": { - "@umbraco/json-models-builders": "^1.0.6", + "@umbraco/json-models-builders": "^1.0.8", "camelize": "^1.0.0", - "faker": "^4.1.0", "form-data": "^4.0.0", "node-fetch": "^2.6.7", "xhr2": "^0.2.1" @@ -323,11 +320,6 @@ "node": "> 0.1.90" } }, - "node_modules/faker": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/faker/-/faker-4.1.0.tgz", - "integrity": "sha1-HkW7vsxndLPBlfrSg1EJxtdIzD8=" - }, "node_modules/fast-glob": { "version": "3.2.12", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", diff --git a/tests/Umbraco.Tests.AcceptanceTest/package.json b/tests/Umbraco.Tests.AcceptanceTest/package.json index adcdf64f1d..6dc84f51dc 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/package.json +++ b/tests/Umbraco.Tests.AcceptanceTest/package.json @@ -19,10 +19,9 @@ "wait-on": "^7.2.0" }, "dependencies": { - "@umbraco/json-models-builders": "^1.0.6", - "@umbraco/playwright-testhelpers": "^1.0.25", + "@umbraco/json-models-builders": "^1.0.8", + "@umbraco/playwright-testhelpers": "^1.0.28", "camelize": "^1.0.0", - "faker": "^4.1.0", "form-data": "^4.0.0", "node-fetch": "^2.6.7", "xhr2": "^0.2.1", From 6e3a6917c8264b51caf7d3bf56907caeec0f223e Mon Sep 17 00:00:00 2001 From: Andreas Zerbst <73799582+andr317c@users.noreply.github.com> Date: Wed, 19 Jun 2024 09:49:04 +0200 Subject: [PATCH 051/113] Decreased to 1 retry for this file only (#16623) --- .../tests/DefaultConfig/Tour/tours.spec.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Tour/tours.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Tour/tours.spec.ts index f5974db43d..03bbe86c78 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Tour/tours.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Tour/tours.spec.ts @@ -3,6 +3,7 @@ import {test} from '@umbraco/playwright-testhelpers'; test.describe('Tours', () => { const timeout = 60000; + test.describe.configure({ retries: 1 }); test.beforeEach(async ({ page, umbracoApi }, testInfo) => { await umbracoApi.report.report(testInfo); await umbracoApi.login(); @@ -100,4 +101,4 @@ test.describe('Tours', () => { await expect(await umbracoUi.getGlobalHelp()).toBeVisible(); await getPercentage(17, timeout, page); }); -}); \ No newline at end of file +}); From e3d65967aafddf201b3cc7cf5adb195ed2be895e Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Thu, 20 Jun 2024 14:45:13 +0200 Subject: [PATCH 052/113] Bump version.json --- version.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.json b/version.json index b9fa941e03..288af47e08 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.4.0", + "version": "13.5.0-rc", "assemblyVersion": { "precision": "build" }, From 462903397fb40e6693a5e352a2e60678ae0345a9 Mon Sep 17 00:00:00 2001 From: georgebid <91198628+georgebid@users.noreply.github.com> Date: Fri, 21 Jun 2024 16:22:40 +0100 Subject: [PATCH 053/113] test --- src/Umbraco.Web.UI.Client | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client b/src/Umbraco.Web.UI.Client index 6366776159..b04739e570 160000 --- a/src/Umbraco.Web.UI.Client +++ b/src/Umbraco.Web.UI.Client @@ -1 +1 @@ -Subproject commit 63667761595d0eba06137d358db214d7d72f7d14 +Subproject commit b04739e5702fb3cfa8bccf1673c29ce24f0f7390 From 53139a10468addfeebb66a8990387822091dc33d Mon Sep 17 00:00:00 2001 From: georgebid <91198628+georgebid@users.noreply.github.com> Date: Fri, 21 Jun 2024 16:23:35 +0100 Subject: [PATCH 054/113] Revert "test" This reverts commit 462903397fb40e6693a5e352a2e60678ae0345a9. --- src/Umbraco.Web.UI.Client | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client b/src/Umbraco.Web.UI.Client index b04739e570..6366776159 160000 --- a/src/Umbraco.Web.UI.Client +++ b/src/Umbraco.Web.UI.Client @@ -1 +1 @@ -Subproject commit b04739e5702fb3cfa8bccf1673c29ce24f0f7390 +Subproject commit 63667761595d0eba06137d358db214d7d72f7d14 From de74ae4c043ec9a1eca26cb931c685f99b101c2b Mon Sep 17 00:00:00 2001 From: Lars-Erik Aabech Date: Fri, 21 Jun 2024 17:43:30 +0200 Subject: [PATCH 055/113] Added custom mvc setup method to testserver base class (#16545) --- .../TestServerTest/UmbracoTestServerTestBase.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs b/tests/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs index dbb5e436e9..cf321d9498 100644 --- a/tests/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs +++ b/tests/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs @@ -278,6 +278,8 @@ namespace Umbraco.Cms.Tests.Integration.TestServerTest // Adds Umbraco.Tests.Integration mvcBuilder.AddApplicationPart(typeof(UmbracoTestServerTestBase).Assembly); + + CustomMvcSetup(mvcBuilder); }) .AddWebServer() .AddWebsite() @@ -294,6 +296,11 @@ namespace Umbraco.Cms.Tests.Integration.TestServerTest builder.Build(); } + protected virtual void CustomMvcSetup(IMvcBuilder mvcBuilder) + { + + } + /// /// Hook for registering test doubles. /// From cb090353f4f037431c3f712f2175a366f20999c6 Mon Sep 17 00:00:00 2001 From: Josh Brown Date: Sat, 22 Jun 2024 10:20:12 +0100 Subject: [PATCH 056/113] Fix unguarded calls to ServiceDescriptor.ImplementationType for keyed services (#16604) * Update integration test base class to verify that calls to ServiceDescriptor.ImplementationType are guarded for keyed services * Fix unguarded calls to ServiceDescriptor.ImplementationType for keyed services --- .../DependencyInjection/UmbracoBuilderApiExtensions.cs | 2 +- .../DependencyInjection/UmbracoBuilderAuthExtensions.cs | 2 +- .../DependencyInjection/UmbracoBuilderExtensions.cs | 2 +- .../Testing/UmbracoIntegrationTest.cs | 6 ++++++ .../Umbraco.Infrastructure/Persistence/LocksTests.cs | 2 +- .../Umbraco.Persistence.EFCore/Scoping/EFCoreLockTests.cs | 2 +- 6 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Cms.Api.Common/DependencyInjection/UmbracoBuilderApiExtensions.cs b/src/Umbraco.Cms.Api.Common/DependencyInjection/UmbracoBuilderApiExtensions.cs index b74727ff52..49fe1f233e 100644 --- a/src/Umbraco.Cms.Api.Common/DependencyInjection/UmbracoBuilderApiExtensions.cs +++ b/src/Umbraco.Cms.Api.Common/DependencyInjection/UmbracoBuilderApiExtensions.cs @@ -11,7 +11,7 @@ public static class UmbracoBuilderApiExtensions { public static IUmbracoBuilder AddUmbracoApiOpenApiUI(this IUmbracoBuilder builder) { - if (builder.Services.Any(x => x.ImplementationType == typeof(OperationIdSelector))) + if (builder.Services.Any(x => !x.IsKeyedService && x.ImplementationType == typeof(OperationIdSelector))) { return builder; } diff --git a/src/Umbraco.Cms.Api.Common/DependencyInjection/UmbracoBuilderAuthExtensions.cs b/src/Umbraco.Cms.Api.Common/DependencyInjection/UmbracoBuilderAuthExtensions.cs index 7e730695f3..d5556f63c2 100644 --- a/src/Umbraco.Cms.Api.Common/DependencyInjection/UmbracoBuilderAuthExtensions.cs +++ b/src/Umbraco.Cms.Api.Common/DependencyInjection/UmbracoBuilderAuthExtensions.cs @@ -17,7 +17,7 @@ public static class UmbracoBuilderAuthExtensions { public static IUmbracoBuilder AddUmbracoOpenIddict(this IUmbracoBuilder builder) { - if (builder.Services.Any(x=>x.ImplementationType == typeof(OpenIddictCleanupJob)) is false) + if (builder.Services.Any(x => !x.IsKeyedService && x.ImplementationType == typeof(OpenIddictCleanupJob)) is false) { ConfigureOpenIddict(builder); } diff --git a/src/Umbraco.Cms.Api.Management/DependencyInjection/UmbracoBuilderExtensions.cs b/src/Umbraco.Cms.Api.Management/DependencyInjection/UmbracoBuilderExtensions.cs index 9acd86381c..90993aa382 100644 --- a/src/Umbraco.Cms.Api.Management/DependencyInjection/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.Cms.Api.Management/DependencyInjection/UmbracoBuilderExtensions.cs @@ -24,7 +24,7 @@ public static partial class UmbracoBuilderExtensions builder.Services.AddUnique(); builder.AddUmbracoApiOpenApiUI(); - if (!services.Any(x => x.ImplementationType == typeof(JsonPatchService))) + if (!services.Any(x => !x.IsKeyedService && x.ImplementationType == typeof(JsonPatchService))) { ModelsBuilderBuilderExtensions.AddModelsBuilder(builder) .AddJson() diff --git a/tests/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs b/tests/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs index 664fdb5493..43ade570e9 100644 --- a/tests/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs +++ b/tests/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs @@ -136,6 +136,12 @@ public abstract class UmbracoIntegrationTest : UmbracoIntegrationTestBase services.AddLogger(webHostEnvironment, Configuration); + // Register a keyed service to verify that all calls to ServiceDescriptor.ImplementationType + // are guarded by checking IsKeyedService first. + // Failure to check this when accessing a keyed service descriptor's ImplementationType property + // throws a InvalidOperationException. + services.AddKeyedSingleton("key"); + // Add it! var hostingEnvironment = TestHelper.GetHostingEnvironment(); var typeLoader = services.AddTypeLoader( diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/LocksTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/LocksTests.cs index 8a79cc78b8..93e4782195 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/LocksTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/LocksTests.cs @@ -36,7 +36,7 @@ public class LocksTests : UmbracoIntegrationTest protected override void ConfigureTestServices(IServiceCollection services) => // SQLite + retry policy makes tests fail, we retry before throwing distributed locking timeout. - services.RemoveAll(x => x.ImplementationType == typeof(SqliteAddRetryPolicyInterceptor)); + services.RemoveAll(x => !x.IsKeyedService && x.ImplementationType == typeof(SqliteAddRetryPolicyInterceptor)); [Test] public void SingleReadLockTest() diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Persistence.EFCore/Scoping/EFCoreLockTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Persistence.EFCore/Scoping/EFCoreLockTests.cs index 5103c2e2fa..200da2557b 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Persistence.EFCore/Scoping/EFCoreLockTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Persistence.EFCore/Scoping/EFCoreLockTests.cs @@ -23,7 +23,7 @@ public class EFCoreLockTests : UmbracoIntegrationTest protected override void ConfigureTestServices(IServiceCollection services) { // SQLite + retry policy makes tests fail, we retry before throwing distributed locking timeout. - services.RemoveAll(x => x.ImplementationType == typeof(SqliteAddRetryPolicyInterceptor)); + services.RemoveAll(x => !x.IsKeyedService && x.ImplementationType == typeof(SqliteAddRetryPolicyInterceptor)); // Remove all locking implementations to ensure we only use EFCoreDistributedLockingMechanisms services.RemoveAll(x => x.ServiceType == typeof(IDistributedLockingMechanism)); From 2a57af8240f2b3f7bf458567e44892f5c05d98d2 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Mon, 24 Jun 2024 14:52:49 +0200 Subject: [PATCH 057/113] update backoffice submodule --- src/Umbraco.Web.UI.Client | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client b/src/Umbraco.Web.UI.Client index 6366776159..81d3527fe2 160000 --- a/src/Umbraco.Web.UI.Client +++ b/src/Umbraco.Web.UI.Client @@ -1 +1 @@ -Subproject commit 63667761595d0eba06137d358db214d7d72f7d14 +Subproject commit 81d3527fe2f0e1b5b889d6cfcbe7a152e7ff81dc From 9ad67e27c5f2b9ebf181f02dc90df1b18e85541a Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Tue, 25 Jun 2024 10:03:19 +0200 Subject: [PATCH 058/113] Fixed test by moving when the publishing notification is fired. --- src/Umbraco.Core/Services/ContentService.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs index fb98a8ff88..dd61585203 100644 --- a/src/Umbraco.Core/Services/ContentService.cs +++ b/src/Umbraco.Core/Services/ContentService.cs @@ -1188,14 +1188,6 @@ public class ContentService : RepositoryService, IContentService var allLangs = _languageRepository.GetMany().ToList(); - // Change state to publishing - content.PublishedState = PublishedState.Publishing; - var publishingNotification = new ContentPublishingNotification(content, evtMsgs); - if (scope.Notifications.PublishCancelable(publishingNotification)) - { - return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, content); - } - // this will create the correct culture impact even if culture is * or null IEnumerable impacts = cultures.Select(culture => _cultureImpactFactory.Create(culture, IsDefaultCulture(allLangs, culture), content)); @@ -1207,6 +1199,14 @@ public class ContentService : RepositoryService, IContentService content.PublishCulture(impact); } + // Change state to publishing + content.PublishedState = PublishedState.Publishing; + var publishingNotification = new ContentPublishingNotification(content, evtMsgs); + if (scope.Notifications.PublishCancelable(publishingNotification)) + { + return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, content); + } + PublishResult result = CommitDocumentChangesInternal(scope, content, evtMsgs, allLangs, publishingNotification.State, userId); scope.Complete(); return result; From 76bb2b084740697ccc3d49e9f16fdceb6e8070bb Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Tue, 25 Jun 2024 10:34:16 +0200 Subject: [PATCH 059/113] Add endpoint for calculating effective user start nodes (#16609) * Add endpoint for calculating effective user start nodes * Fix OpenAPI --- .../User/CalculateStartNodesUserController.cs | 59 ++++++++++ .../Factories/IUserPresentationFactory.cs | 2 + .../Factories/UserPresentationFactory.cs | 17 +++ src/Umbraco.Cms.Api.Management/OpenApi.json | 105 ++++++++++++++++++ .../CalculatedUserStartNodesResponseModel.cs | 14 +++ 5 files changed, 197 insertions(+) create mode 100644 src/Umbraco.Cms.Api.Management/Controllers/User/CalculateStartNodesUserController.cs create mode 100644 src/Umbraco.Cms.Api.Management/ViewModels/User/CalculatedUserStartNodesResponseModel.cs diff --git a/src/Umbraco.Cms.Api.Management/Controllers/User/CalculateStartNodesUserController.cs b/src/Umbraco.Cms.Api.Management/Controllers/User/CalculateStartNodesUserController.cs new file mode 100644 index 0000000000..98f1526ce9 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Controllers/User/CalculateStartNodesUserController.cs @@ -0,0 +1,59 @@ +using Asp.Versioning; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Api.Management.Factories; +using Umbraco.Cms.Api.Management.ViewModels.User; +using Umbraco.Cms.Core.Models.Membership; +using Umbraco.Cms.Core.Security.Authorization; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Services.OperationStatus; +using Umbraco.Cms.Web.Common.Authorization; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Api.Management.Controllers.User; + +[ApiVersion("1.0")] +public class CalculatedStartNodesUserController : UserControllerBase +{ + private readonly IAuthorizationService _authorizationService; + private readonly IUserService _userService; + private readonly IUserPresentationFactory _userPresentationFactory; + + public CalculatedStartNodesUserController( + IAuthorizationService authorizationService, + IUserService userService, + IUserPresentationFactory userPresentationFactory) + { + _authorizationService = authorizationService; + _userService = userService; + _userPresentationFactory = userPresentationFactory; + } + + [HttpGet("{id:guid}/calculate-start-nodes")] + [MapToApiVersion("1.0")] + [ProducesResponseType(typeof(CalculatedUserStartNodesResponseModel), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)] + public async Task CalculatedStartNodes(CancellationToken cancellationToken, Guid id) + { + AuthorizationResult authorizationResult = await _authorizationService.AuthorizeResourceAsync( + User, + UserPermissionResource.WithKeys(id), + AuthorizationPolicies.UserPermissionByResource); + + if (!authorizationResult.Succeeded) + { + return Forbidden(); + } + + IUser? user = await _userService.GetAsync(id); + + if (user is null) + { + return UserOperationStatusResult(UserOperationStatus.UserNotFound); + } + + CalculatedUserStartNodesResponseModel responseModel = await _userPresentationFactory.CreateCalculatedUserStartNodesResponseModelAsync(user); + return Ok(responseModel); + } +} diff --git a/src/Umbraco.Cms.Api.Management/Factories/IUserPresentationFactory.cs b/src/Umbraco.Cms.Api.Management/Factories/IUserPresentationFactory.cs index 9af4ace9e1..a1e8a79edf 100644 --- a/src/Umbraco.Cms.Api.Management/Factories/IUserPresentationFactory.cs +++ b/src/Umbraco.Cms.Api.Management/Factories/IUserPresentationFactory.cs @@ -25,4 +25,6 @@ public interface IUserPresentationFactory Task CreateCurrentUserConfigurationModelAsync(); UserItemResponseModel CreateItemResponseModel(IUser user); + + Task CreateCalculatedUserStartNodesResponseModelAsync(IUser user); } diff --git a/src/Umbraco.Cms.Api.Management/Factories/UserPresentationFactory.cs b/src/Umbraco.Cms.Api.Management/Factories/UserPresentationFactory.cs index 641f5883ed..9164be6772 100644 --- a/src/Umbraco.Cms.Api.Management/Factories/UserPresentationFactory.cs +++ b/src/Umbraco.Cms.Api.Management/Factories/UserPresentationFactory.cs @@ -212,6 +212,23 @@ public class UserPresentationFactory : IUserPresentationFactory }); } + public async Task CreateCalculatedUserStartNodesResponseModelAsync(IUser user) + { + var mediaStartNodeIds = user.CalculateMediaStartNodeIds(_entityService, _appCaches); + ISet mediaStartNodeKeys = GetKeysFromIds(mediaStartNodeIds, UmbracoObjectTypes.Media); + var contentStartNodeIds = user.CalculateContentStartNodeIds(_entityService, _appCaches); + ISet documentStartNodeKeys = GetKeysFromIds(contentStartNodeIds, UmbracoObjectTypes.Document); + + return await Task.FromResult(new CalculatedUserStartNodesResponseModel() + { + Id = user.Key, + MediaStartNodeIds = mediaStartNodeKeys, + HasMediaRootAccess = HasRootAccess(mediaStartNodeIds), + DocumentStartNodeIds = documentStartNodeKeys, + HasDocumentRootAccess = HasRootAccess(contentStartNodeIds), + }); + } + private ISet GetKeysFromIds(IEnumerable? ids, UmbracoObjectTypes type) { IEnumerable? models = ids? diff --git a/src/Umbraco.Cms.Api.Management/OpenApi.json b/src/Umbraco.Cms.Api.Management/OpenApi.json index 0adc3c36b7..11dee452ca 100644 --- a/src/Umbraco.Cms.Api.Management/OpenApi.json +++ b/src/Umbraco.Cms.Api.Management/OpenApi.json @@ -30298,6 +30298,66 @@ ] } }, + "/umbraco/management/api/v1/user/{id}/calculate-start-nodes": { + "get": { + "tags": [ + "User" + ], + "operationId": "GetUserByIdCalculateStartNodes", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/CalculatedUserStartNodesResponseModel" + } + ] + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/ProblemDetails" + } + ] + } + } + } + }, + "401": { + "description": "The resource is protected and requires an authentication token" + }, + "403": { + "description": "The authenticated user do not have access to this resource" + } + }, + "security": [ + { + "Backoffice User": [ ] + } + ] + } + }, "/umbraco/management/api/v1/user/{id}/change-password": { "post": { "tags": [ @@ -33447,6 +33507,51 @@ }, "additionalProperties": false }, + "CalculatedUserStartNodesResponseModel": { + "required": [ + "documentStartNodeIds", + "hasDocumentRootAccess", + "hasMediaRootAccess", + "id", + "mediaStartNodeIds" + ], + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "documentStartNodeIds": { + "uniqueItems": true, + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#/components/schemas/ReferenceByIdModel" + } + ] + } + }, + "hasDocumentRootAccess": { + "type": "boolean" + }, + "mediaStartNodeIds": { + "uniqueItems": true, + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#/components/schemas/ReferenceByIdModel" + } + ] + } + }, + "hasMediaRootAccess": { + "type": "boolean" + } + }, + "additionalProperties": false + }, "ChangePasswordCurrentUserRequestModel": { "required": [ "newPassword" diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/User/CalculatedUserStartNodesResponseModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/User/CalculatedUserStartNodesResponseModel.cs new file mode 100644 index 0000000000..8cc71e8482 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/ViewModels/User/CalculatedUserStartNodesResponseModel.cs @@ -0,0 +1,14 @@ +namespace Umbraco.Cms.Api.Management.ViewModels.User; + +public class CalculatedUserStartNodesResponseModel +{ + public required Guid Id { get; init; } + + public ISet DocumentStartNodeIds { get; set; } = new HashSet(); + + public bool HasDocumentRootAccess { get; set; } + + public ISet MediaStartNodeIds { get; set; } = new HashSet(); + + public bool HasMediaRootAccess { get; set; } +} From 3c34eaf739e169ff076ffea06c55b377ebd892f2 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Tue, 25 Jun 2024 11:20:52 +0200 Subject: [PATCH 060/113] Updated nuget packages --- Directory.Packages.props | 16 ++++++++-------- .../Umbraco.PublishedCache.NuCache.csproj | 1 - tests/Directory.Packages.props | 2 +- .../Umbraco.JsonSchema/Umbraco.JsonSchema.csproj | 2 +- 4 files changed, 10 insertions(+), 11 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index c48c260baa..78d394ab7c 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -5,16 +5,16 @@ - + - + - + @@ -58,9 +58,9 @@ - - - + + + @@ -69,7 +69,7 @@ - + @@ -88,4 +88,4 @@ - + \ No newline at end of file diff --git a/src/Umbraco.PublishedCache.NuCache/Umbraco.PublishedCache.NuCache.csproj b/src/Umbraco.PublishedCache.NuCache/Umbraco.PublishedCache.NuCache.csproj index 92f222525c..4894759e5f 100644 --- a/src/Umbraco.PublishedCache.NuCache/Umbraco.PublishedCache.NuCache.csproj +++ b/src/Umbraco.PublishedCache.NuCache/Umbraco.PublishedCache.NuCache.csproj @@ -10,7 +10,6 @@ - diff --git a/tests/Directory.Packages.props b/tests/Directory.Packages.props index 4fce9e86f3..ba3af0bd42 100644 --- a/tests/Directory.Packages.props +++ b/tests/Directory.Packages.props @@ -5,7 +5,7 @@ - + diff --git a/tools/Umbraco.JsonSchema/Umbraco.JsonSchema.csproj b/tools/Umbraco.JsonSchema/Umbraco.JsonSchema.csproj index 7771d9c195..431674852e 100644 --- a/tools/Umbraco.JsonSchema/Umbraco.JsonSchema.csproj +++ b/tools/Umbraco.JsonSchema/Umbraco.JsonSchema.csproj @@ -7,7 +7,7 @@ - + From 0afb4f7283620a2afc5512d4aef9557e101c908b Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Tue, 25 Jun 2024 13:47:25 +0200 Subject: [PATCH 061/113] Make GetHeaderValue support HttpContext unavailable (#16654) * Make GetHeaderValue tolerant for when the http context is not available. Now it just returns null. * Add unit tests --- .../Services/RequestHeaderHandler.cs | 8 +--- .../Services/RequestHeaderHandlerTests.cs | 48 +++++++++++++++++++ 2 files changed, 49 insertions(+), 7 deletions(-) create mode 100644 tests/Umbraco.Tests.UnitTests/Umbraco.Cms.Api.Delivery/Services/RequestHeaderHandlerTests.cs diff --git a/src/Umbraco.Cms.Api.Delivery/Services/RequestHeaderHandler.cs b/src/Umbraco.Cms.Api.Delivery/Services/RequestHeaderHandler.cs index 08d7a916e6..37651a4158 100644 --- a/src/Umbraco.Cms.Api.Delivery/Services/RequestHeaderHandler.cs +++ b/src/Umbraco.Cms.Api.Delivery/Services/RequestHeaderHandler.cs @@ -8,11 +8,5 @@ internal abstract class RequestHeaderHandler protected RequestHeaderHandler(IHttpContextAccessor httpContextAccessor) => _httpContextAccessor = httpContextAccessor; - protected string? GetHeaderValue(string headerName) - { - HttpContext httpContext = _httpContextAccessor.HttpContext ?? - throw new InvalidOperationException("Could not obtain an HTTP context"); - - return httpContext.Request.Headers[headerName]; - } + protected string? GetHeaderValue(string headerName) => _httpContextAccessor.HttpContext?.Request.Headers[headerName]; } diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Cms.Api.Delivery/Services/RequestHeaderHandlerTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Cms.Api.Delivery/Services/RequestHeaderHandlerTests.cs new file mode 100644 index 0000000000..9b4da511eb --- /dev/null +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Cms.Api.Delivery/Services/RequestHeaderHandlerTests.cs @@ -0,0 +1,48 @@ +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 RequestHeaderHandlerTests +{ + private const string HeaderName = "TestHeader"; + [Test] + public void GetHeaderValue_return_null_when_http_context_is_unavailable() + { + IHttpContextAccessor httpContextAccessor = Mock.Of(); + + var sut = new TestRequestHeaderHandler(httpContextAccessor); + + Assert.IsNull(sut.TestGetHeaderValue(HeaderName)); + } + + [Test] + public void GetHeaderValue_return_header_value_when_http_context_is_available() + { + + const string headerValue = "TestValue"; + + HttpContext httpContext = new DefaultHttpContext(); + httpContext.Request.Headers[HeaderName] = headerValue; + + IHttpContextAccessor httpContextAccessor = Mock.Of(); + Mock.Get(httpContextAccessor).Setup(x => x.HttpContext).Returns(httpContext); + + var sut = new TestRequestHeaderHandler(httpContextAccessor); + + Assert.AreEqual(headerValue, sut.TestGetHeaderValue(HeaderName)); + } +} + + +internal class TestRequestHeaderHandler : RequestHeaderHandler +{ + public TestRequestHeaderHandler(IHttpContextAccessor httpContextAccessor) : base(httpContextAccessor) + { + } + + public string? TestGetHeaderValue(string headerName) => base.GetHeaderValue(headerName); +} From 0b868638e00f3cb28e2cbab3a49b7e1cf73cf287 Mon Sep 17 00:00:00 2001 From: Andreas Zerbst <73799582+andr317c@users.noreply.github.com> Date: Tue, 25 Jun 2024 17:24:47 +0200 Subject: [PATCH 062/113] V14 QA updated dictionary naming in E2E test (#16657) * Updated naming * Bumped version of helper --- .../package-lock.json | 8 +- .../Umbraco.Tests.AcceptanceTest/package.json | 2 +- .../Dictionary.spec.ts} | 110 +++++++++--------- 3 files changed, 60 insertions(+), 60 deletions(-) rename tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/{Translation/Translation.spec.ts => Dictionary/Dictionary.spec.ts} (55%) diff --git a/tests/Umbraco.Tests.AcceptanceTest/package-lock.json b/tests/Umbraco.Tests.AcceptanceTest/package-lock.json index 8383cb5028..25ef832219 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/package-lock.json +++ b/tests/Umbraco.Tests.AcceptanceTest/package-lock.json @@ -8,7 +8,7 @@ "hasInstallScript": true, "dependencies": { "@umbraco/json-models-builders": "^2.0.7", - "@umbraco/playwright-testhelpers": "^2.0.0-beta.61", + "@umbraco/playwright-testhelpers": "^2.0.0-beta.63", "camelize": "^1.0.0", "dotenv": "^16.3.1", "faker": "^4.1.0", @@ -146,9 +146,9 @@ "integrity": "sha512-9tCqYEDHI5RYFQigXFwF1hnCwcWCOJl/hmll0lr5D2Ljjb0o4wphb69wikeJDz5qCEzXCoPvG6ss5SDP6IfOdg==" }, "node_modules/@umbraco/playwright-testhelpers": { - "version": "2.0.0-beta.61", - "resolved": "https://registry.npmjs.org/@umbraco/playwright-testhelpers/-/playwright-testhelpers-2.0.0-beta.61.tgz", - "integrity": "sha512-Y2RqGrjfLDCZGDPyix4r8LoSl/YaluzY8RHLlkdcbL5GojDprzfB0jN9P3ZbrCDvnu9hydA8qE6ElPq/Zw5qXw==", + "version": "2.0.0-beta.63", + "resolved": "https://registry.npmjs.org/@umbraco/playwright-testhelpers/-/playwright-testhelpers-2.0.0-beta.63.tgz", + "integrity": "sha512-fLXUcWNJupfGKkD6zOGg6WcU5cmqQ6gQkyIyG+UsKSrkgCxK23+N5LrOz2OVp2NZ8GQ8kB5pJ4izvCp+yMMOnA==", "dependencies": { "@umbraco/json-models-builders": "2.0.7", "camelize": "^1.0.0", diff --git a/tests/Umbraco.Tests.AcceptanceTest/package.json b/tests/Umbraco.Tests.AcceptanceTest/package.json index 11606d1a8e..8126ebf602 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/package.json +++ b/tests/Umbraco.Tests.AcceptanceTest/package.json @@ -22,7 +22,7 @@ }, "dependencies": { "@umbraco/json-models-builders": "^2.0.7", - "@umbraco/playwright-testhelpers": "^2.0.0-beta.61", + "@umbraco/playwright-testhelpers": "^2.0.0-beta.63", "camelize": "^1.0.0", "dotenv": "^16.3.1", "faker": "^4.1.0", diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Translation/Translation.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Dictionary/Dictionary.spec.ts similarity index 55% rename from tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Translation/Translation.spec.ts rename to tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Dictionary/Dictionary.spec.ts index 355442a365..68ad9ee39b 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Translation/Translation.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Dictionary/Dictionary.spec.ts @@ -15,64 +15,64 @@ test.afterEach(async ({umbracoApi}) => { test('can create a dictionary item', async ({umbracoApi, umbracoUi}) => { // Arrange await umbracoApi.dictionary.ensureNameNotExists(dictionaryName); - await umbracoUi.translation.goToSection(ConstantHelper.sections.translation); + await umbracoUi.dictionary.goToSection(ConstantHelper.sections.dictionary); // Act - await umbracoUi.translation.clickCreateLink(); - await umbracoUi.translation.enterDictionaryName(dictionaryName); - await umbracoUi.translation.clickSaveButton(); + await umbracoUi.dictionary.clickCreateLink(); + await umbracoUi.dictionary.enterDictionaryName(dictionaryName); + await umbracoUi.dictionary.clickSaveButton(); // Assert expect(await umbracoApi.dictionary.doesNameExist(dictionaryName)).toBeTruthy(); - await umbracoUi.translation.isSuccessNotificationVisible(); - await umbracoUi.translation.clickLeftArrowButton(); + await umbracoUi.dictionary.isSuccessNotificationVisible(); + await umbracoUi.dictionary.clickLeftArrowButton(); // Verify the dictionary item displays in the tree and in the list - await umbracoUi.translation.isDictionaryTreeItemVisible(dictionaryName); - expect(await umbracoUi.translation.doesDictionaryListHaveText(dictionaryName)).toBeTruthy(); + await umbracoUi.dictionary.isDictionaryTreeItemVisible(dictionaryName); + expect(await umbracoUi.dictionary.doesDictionaryListHaveText(dictionaryName)).toBeTruthy(); }); test('can delete a dictionary item', async ({umbracoApi, umbracoUi}) => { // Arrange await umbracoApi.dictionary.ensureNameNotExists(dictionaryName); await umbracoApi.dictionary.create(dictionaryName); - await umbracoUi.translation.goToSection(ConstantHelper.sections.translation); + await umbracoUi.dictionary.goToSection(ConstantHelper.sections.dictionary); // Act - await umbracoUi.translation.clickActionsMenuForDictionary(dictionaryName); - await umbracoUi.translation.deleteDictionary(); + await umbracoUi.dictionary.clickActionsMenuForDictionary(dictionaryName); + await umbracoUi.dictionary.deleteDictionary(); // Assert - await umbracoUi.translation.isSuccessNotificationVisible(); + await umbracoUi.dictionary.isSuccessNotificationVisible(); expect(await umbracoApi.dictionary.doesNameExist(dictionaryName)).toBeFalsy(); // Verify the dictionary item does not display in the tree - await umbracoUi.translation.isDictionaryTreeItemVisible(dictionaryName, false); + await umbracoUi.dictionary.isDictionaryTreeItemVisible(dictionaryName, false); // TODO: Uncomment this when the front-end is ready. Currently the dictionary list is not updated immediately. // Verify the dictionary item does not display in the list - //expect(await umbracoUi.translation.doesDictionaryListHaveText(dictionaryName)).toBeFalsy(); + //expect(await umbracoUi.dictionary.doesDictionaryListHaveText(dictionaryName)).toBeFalsy(); }); test('can create a dictionary item in a dictionary', {tag: '@smoke'}, async ({umbracoApi, umbracoUi}) => { // Arrange await umbracoApi.dictionary.ensureNameNotExists(parentDictionaryName); let parentDictionaryId = await umbracoApi.dictionary.create(parentDictionaryName); - await umbracoUi.translation.goToSection(ConstantHelper.sections.translation); + await umbracoUi.dictionary.goToSection(ConstantHelper.sections.dictionary); // Act - await umbracoUi.translation.clickActionsMenuForDictionary(parentDictionaryName); - await umbracoUi.translation.clickCreateDictionaryItemButton(); - await umbracoUi.translation.enterDictionaryName(dictionaryName); - await umbracoUi.translation.clickSaveButton(); + await umbracoUi.dictionary.clickActionsMenuForDictionary(parentDictionaryName); + await umbracoUi.dictionary.clickCreateDictionaryItemButton(); + await umbracoUi.dictionary.enterDictionaryName(dictionaryName); + await umbracoUi.dictionary.clickSaveButton(); // Assert - await umbracoUi.translation.isSuccessNotificationVisible(); + await umbracoUi.dictionary.isSuccessNotificationVisible(); const dictionaryChildren = await umbracoApi.dictionary.getChildren(parentDictionaryId); expect(dictionaryChildren[0].name).toEqual(dictionaryName); - await umbracoUi.translation.clickLeftArrowButton(); + await umbracoUi.dictionary.clickLeftArrowButton(); // Verify the new dictionary item displays in the list - expect(await umbracoUi.translation.doesDictionaryListHaveText(dictionaryName)).toBeTruthy(); + expect(await umbracoUi.dictionary.doesDictionaryListHaveText(dictionaryName)).toBeTruthy(); // Verify the new dictionary item displays in the tree - await umbracoUi.translation.reloadTree(parentDictionaryName); - await umbracoUi.translation.isDictionaryTreeItemVisible(dictionaryName); + await umbracoUi.dictionary.reloadTree(parentDictionaryName); + await umbracoUi.dictionary.isDictionaryTreeItemVisible(dictionaryName); // Clean await umbracoApi.dictionary.ensureNameNotExists(parentDictionaryName); @@ -82,12 +82,12 @@ test('can export a dictionary item', async ({umbracoApi, umbracoUi}) => { // Arrange await umbracoApi.dictionary.ensureNameNotExists(dictionaryName); const dictionaryId = await umbracoApi.dictionary.create(dictionaryName); - await umbracoUi.translation.goToSection(ConstantHelper.sections.translation); + await umbracoUi.dictionary.goToSection(ConstantHelper.sections.dictionary); // Act - await umbracoUi.translation.clickActionsMenuForDictionary(dictionaryName); - await umbracoUi.translation.clickExportMenu(); - const exportData = await umbracoUi.translation.exportDictionary(false); + await umbracoUi.dictionary.clickActionsMenuForDictionary(dictionaryName); + await umbracoUi.dictionary.clickExportMenu(); + const exportData = await umbracoUi.dictionary.exportDictionary(false); // Assert expect(exportData).toEqual(dictionaryId + '.udt'); @@ -98,12 +98,12 @@ test('can export a dictionary item with descendants', {tag: '@smoke'}, async ({u await umbracoApi.dictionary.ensureNameNotExists(parentDictionaryName); let parentDictionaryId = await umbracoApi.dictionary.create(parentDictionaryName); await umbracoApi.dictionary.create(dictionaryName, [], parentDictionaryId); - await umbracoUi.translation.goToSection(ConstantHelper.sections.translation); + await umbracoUi.dictionary.goToSection(ConstantHelper.sections.dictionary); // Act - await umbracoUi.translation.clickActionsMenuForDictionary(parentDictionaryName); - await umbracoUi.translation.clickExportMenu(); - const exportData = await umbracoUi.translation.exportDictionary(true); + await umbracoUi.dictionary.clickActionsMenuForDictionary(parentDictionaryName); + await umbracoUi.dictionary.clickExportMenu(); + const exportData = await umbracoUi.dictionary.exportDictionary(true); // Assert expect(exportData).toEqual(parentDictionaryId + '.udt'); @@ -119,20 +119,20 @@ test('can import a dictionary item', async ({umbracoApi, umbracoUi}) => { const importDictionaryName = 'TestImportDictionary'; await umbracoApi.dictionary.ensureNameNotExists(dictionaryName); await umbracoApi.dictionary.create(dictionaryName); - await umbracoUi.translation.goToSection(ConstantHelper.sections.translation); + await umbracoUi.dictionary.goToSection(ConstantHelper.sections.dictionary); // Act - await umbracoUi.translation.clickActionsMenuForDictionary(dictionaryName); - await umbracoUi.translation.clickImportMenu(); - await umbracoUi.translation.importDictionary(udtFilePath); + await umbracoUi.dictionary.clickActionsMenuForDictionary(dictionaryName); + await umbracoUi.dictionary.clickImportMenu(); + await umbracoUi.dictionary.importDictionary(udtFilePath); // Assert // Verify the imported dictionary item displays in the tree - await umbracoUi.translation.reloadTree(dictionaryName); - await umbracoUi.translation.isDictionaryTreeItemVisible(importDictionaryName); + await umbracoUi.dictionary.reloadTree(dictionaryName); + await umbracoUi.dictionary.isDictionaryTreeItemVisible(importDictionaryName); // TODO: Uncomment this when the front-end is ready. Currently the dictionary list is not updated immediately. // Verify the imported dictionary item displays in the list - //expect(await umbracoUi.translation.doesDictionaryListHaveText(importDictionaryName)).toBeTruthy(); + //expect(await umbracoUi.dictionary.doesDictionaryListHaveText(importDictionaryName)).toBeTruthy(); }); test('can import a dictionary item with descendants', {tag: '@smoke'}, async ({umbracoApi, umbracoUi}) => { @@ -143,23 +143,23 @@ test('can import a dictionary item with descendants', {tag: '@smoke'}, async ({u const importChildDictionaryName = 'TestImportChild'; await umbracoApi.dictionary.ensureNameNotExists(dictionaryName); await umbracoApi.dictionary.create(dictionaryName); - await umbracoUi.translation.goToSection(ConstantHelper.sections.translation); + await umbracoUi.dictionary.goToSection(ConstantHelper.sections.dictionary); // Act - await umbracoUi.translation.clickActionsMenuForDictionary(dictionaryName); - await umbracoUi.translation.clickImportMenu(); - await umbracoUi.translation.importDictionary(udtFilePath); + await umbracoUi.dictionary.clickActionsMenuForDictionary(dictionaryName); + await umbracoUi.dictionary.clickImportMenu(); + await umbracoUi.dictionary.importDictionary(udtFilePath); // Assert // Verify the imported dictionary items display in the tree - await umbracoUi.translation.reloadTree(dictionaryName); - await umbracoUi.translation.isDictionaryTreeItemVisible(importParentDictionaryName); - await umbracoUi.translation.reloadTree(importParentDictionaryName); - await umbracoUi.translation.isDictionaryTreeItemVisible(importChildDictionaryName); + await umbracoUi.dictionary.reloadTree(dictionaryName); + await umbracoUi.dictionary.isDictionaryTreeItemVisible(importParentDictionaryName); + await umbracoUi.dictionary.reloadTree(importParentDictionaryName); + await umbracoUi.dictionary.isDictionaryTreeItemVisible(importChildDictionaryName); // TODO: Uncomment this when the front-end is ready. Currently the dictionary list is not updated immediately. // Verify the imported dictionary items display in the list - //expect(await umbracoUi.translation.doesDictionaryListHaveText(importParentDictionaryName)).toBeTruthy(); - //expect(await umbracoUi.translation.doesDictionaryListHaveText(importChildDictionaryName)).toBeTruthy(); + //expect(await umbracoUi.dictionary.doesDictionaryListHaveText(importParentDictionaryName)).toBeTruthy(); + //expect(await umbracoUi.dictionary.doesDictionaryListHaveText(importChildDictionaryName)).toBeTruthy(); }); // Skip this test as the search function is removed @@ -167,13 +167,13 @@ test.skip('can search a dictionary item in list when have results', async ({umbr // Arrange await umbracoApi.dictionary.ensureNameNotExists(dictionaryName); await umbracoApi.dictionary.create(dictionaryName); - await umbracoUi.translation.goToSection(ConstantHelper.sections.translation); + await umbracoUi.dictionary.goToSection(ConstantHelper.sections.dictionary); // Act - await umbracoUi.translation.enterSearchKeywordAndPressEnter(dictionaryName); + await umbracoUi.dictionary.enterSearchKeywordAndPressEnter(dictionaryName); // Assert - expect(await umbracoUi.translation.doesDictionaryListHaveText(dictionaryName)).toBeTruthy(); + expect(await umbracoUi.dictionary.doesDictionaryListHaveText(dictionaryName)).toBeTruthy(); }); // Skip this test as the search function is removed @@ -182,11 +182,11 @@ test.skip('can search a dictionary item in list when have no results', async ({u const emptySearchResultMessage = 'No Dictionary items to choose from'; await umbracoApi.dictionary.ensureNameNotExists(dictionaryName); await umbracoApi.dictionary.create(dictionaryName); - await umbracoUi.translation.goToSection(ConstantHelper.sections.translation); + await umbracoUi.dictionary.goToSection(ConstantHelper.sections.dictionary); // Act - await umbracoUi.translation.enterSearchKeywordAndPressEnter('xyz'); + await umbracoUi.dictionary.enterSearchKeywordAndPressEnter('xyz'); // Assert - await umbracoUi.translation.isSearchResultMessageDisplayEmpty(emptySearchResultMessage); + await umbracoUi.dictionary.isSearchResultMessageDisplayEmpty(emptySearchResultMessage); }); From 8ca637d0cb47b0d648f427931a6b834927a7c9da Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Wed, 26 Jun 2024 10:18:05 +0200 Subject: [PATCH 063/113] V14: Fix source code editor not showing on fresh install / upgrade (#16655) * Create richtext with sourcecode by default * Add rich text migration * Create migration for migration "ace" value to "sourcecode" --- .../Migrations/Install/DatabaseDataCreator.cs | 2 +- .../Migrations/Upgrade/UmbracoPlan.cs | 1 + .../V_14_0_0/MigrateRichTextConfiguration.cs | 32 +++++++++++++++++++ 3 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 src/Umbraco.Infrastructure/Migrations/Upgrade/V_14_0_0/MigrateRichTextConfiguration.cs diff --git a/src/Umbraco.Infrastructure/Migrations/Install/DatabaseDataCreator.cs b/src/Umbraco.Infrastructure/Migrations/Install/DatabaseDataCreator.cs index 009811bc7d..53f7495822 100644 --- a/src/Umbraco.Infrastructure/Migrations/Install/DatabaseDataCreator.cs +++ b/src/Umbraco.Infrastructure/Migrations/Install/DatabaseDataCreator.cs @@ -1934,7 +1934,7 @@ internal class DatabaseDataCreator EditorUiAlias = "Umb.PropertyEditorUi.TinyMCE", DbType = "Ntext", Configuration = - "{\"toolbar\":[\"ace\",\"styles\",\"bold\",\"italic\",\"alignleft\",\"aligncenter\",\"alignright\",\"bullist\",\"numlist\",\"outdent\",\"indent\",\"link\",\"umbmediapicker\",\"umbembeddialog\"],\"stylesheets\":[],\"maxImageSize\":500,\"mode\":\"classic\"}", + "{\"toolbar\":[\"sourcecode\",\"styles\",\"bold\",\"italic\",\"alignleft\",\"aligncenter\",\"alignright\",\"bullist\",\"numlist\",\"outdent\",\"indent\",\"link\",\"umbmediapicker\",\"umbembeddialog\"],\"stylesheets\":[],\"maxImageSize\":500,\"mode\":\"classic\"}", }); } diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs index a53833d99e..91ca3b4d58 100644 --- a/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs +++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs @@ -84,5 +84,6 @@ public class UmbracoPlan : MigrationPlan // we need to re-run this migration, as it was flawed for V14 RC3 (the migration can run twice without any issues) To("{6FB5CA9E-C823-473B-A14C-FE760D75943C}"); To("{827360CA-0855-42A5-8F86-A51F168CB559}"); + To("{FEF2DAF4-5408-4636-BB0E-B8798DF8F095}"); } } diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_14_0_0/MigrateRichTextConfiguration.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_14_0_0/MigrateRichTextConfiguration.cs new file mode 100644 index 0000000000..950259f575 --- /dev/null +++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_14_0_0/MigrateRichTextConfiguration.cs @@ -0,0 +1,32 @@ +using NPoco; +using Umbraco.Cms.Core; +using Umbraco.Cms.Infrastructure.Persistence; +using Umbraco.Cms.Infrastructure.Persistence.Dtos; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_14_0_0; + +public class MigrateRichTextConfiguration : MigrationBase +{ + + public MigrateRichTextConfiguration(IMigrationContext context) : base(context) + { + } + + protected override void Migrate() + { + Sql sql = Sql() + .Select() + .From() + .Where(x => x.EditorAlias.Equals(Constants.PropertyEditors.Aliases.RichText)); + + List dataTypeDtos = Database.Fetch(sql); + + foreach (DataTypeDto dataTypeDto in dataTypeDtos) + { + // Update the configuration + dataTypeDto.Configuration = dataTypeDto.Configuration?.Replace("\"ace", "\"sourcecode"); + Database.Update(dataTypeDto); + } + } +} From 7e8dd02e793aa61fc31fafe41b1a908bcd55f099 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Wed, 26 Jun 2024 10:32:45 +0200 Subject: [PATCH 064/113] update backoffice submodule --- src/Umbraco.Web.UI.Client | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client b/src/Umbraco.Web.UI.Client index c0a342b5e8..9077e80b32 160000 --- a/src/Umbraco.Web.UI.Client +++ b/src/Umbraco.Web.UI.Client @@ -1 +1 @@ -Subproject commit c0a342b5e88535c8f9c8bf36dd15fa493f3822a6 +Subproject commit 9077e80b3298c3ef9ca491fca7a33cc662ea6f5b From bb058c4f893be153332e48b95d7eedf88d67a969 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Wed, 26 Jun 2024 11:22:45 +0200 Subject: [PATCH 065/113] update lockfile --- src/Umbraco.Web.UI.Login/package-lock.json | 46 ++++++++++++++-------- 1 file changed, 30 insertions(+), 16 deletions(-) diff --git a/src/Umbraco.Web.UI.Login/package-lock.json b/src/Umbraco.Web.UI.Login/package-lock.json index 33ed12c308..73617f8f64 100644 --- a/src/Umbraco.Web.UI.Login/package-lock.json +++ b/src/Umbraco.Web.UI.Login/package-lock.json @@ -20,25 +20,39 @@ }, "../Umbraco.Web.UI.Client": { "name": "@umbraco-cms/backoffice", - "version": "14.0.0-rc2", + "version": "14.1.0", "dev": true, "license": "MIT", + "workspaces": [ + "./src/packages/block", + "./src/packages/core", + "./src/packages/data-type", + "./src/packages/dictionary", + "./src/packages/documents", + "./src/packages/health-check", + "./src/packages/language", + "./src/packages/tags", + "./src/packages/umbraco-news", + "./src/packages/webhook", + "./src/packages/health-check", + "./src/packages/tags" + ], "dependencies": { - "@types/diff": "^5.0.9", + "@types/diff": "^5.2.1", "@types/dompurify": "^3.0.5", "@types/uuid": "^9.0.8", - "@umbraco-ui/uui": "1.8.0-rc.3", - "@umbraco-ui/uui-css": "1.8.0-rc.0", + "@umbraco-ui/uui": "1.8.2", + "@umbraco-ui/uui-css": "1.8.0", "base64-js": "^1.5.1", "diff": "^5.2.0", "dompurify": "^3.1.4", - "element-internals-polyfill": "^1.3.10", - "lit": "^3.1.2", - "marked": "^12.0.0", - "monaco-editor": "^0.46.0", + "element-internals-polyfill": "^1.3.11", + "lit": "^3.1.3", + "marked": "^12.0.2", + "monaco-editor": "^0.48.0", "rxjs": "^7.8.1", "tinymce": "^6.8.3", - "tinymce-i18n": "^24.1.29", + "tinymce-i18n": "^24.5.8", "uuid": "^9.0.1" }, "devDependencies": { @@ -64,7 +78,7 @@ "@typescript-eslint/parser": "^7.1.0", "@web/dev-server-esbuild": "^1.0.2", "@web/dev-server-import-maps": "^0.2.0", - "@web/dev-server-rollup": "^0.6.1", + "@web/dev-server-rollup": "^0.6.3", "@web/test-runner": "^0.18.1", "@web/test-runner-playwright": "^0.11.0", "babel-loader": "^9.1.3", @@ -72,13 +86,13 @@ "eslint-config-prettier": "^9.1.0", "eslint-import-resolver-typescript": "^3.6.1", "eslint-plugin-import": "^2.29.1", - "eslint-plugin-lit": "^1.11.0", + "eslint-plugin-lit": "^1.14.0", "eslint-plugin-lit-a11y": "^4.1.1", "eslint-plugin-local-rules": "^2.0.1", "eslint-plugin-storybook": "^0.6.15", "eslint-plugin-wc": "^2.0.4", "glob": "^10.3.10", - "lucide-static": "^0.367.0", + "lucide-static": "^0.379.0", "msw": "^1.3.2", "playwright-msw": "^3.0.1", "prettier": "3.2.5", @@ -89,15 +103,15 @@ "rollup-plugin-esbuild": "^6.1.1", "rollup-plugin-import-css": "^3.5.0", "rollup-plugin-web-worker-loader": "^1.6.1", - "simple-icons": "^11.11.0", + "simple-icons": "^12.0.0", "storybook": "^7.6.17", "tiny-glob": "^0.2.9", "tsc-alias": "^1.8.8", - "typedoc": "^0.25.10", - "typescript": "^5.4.5", + "typedoc": "^0.25.13", + "typescript": "^5.5.2", "typescript-json-schema": "^0.63.0", "vite": "^5.2.9", - "vite-plugin-static-copy": "^1.0.2", + "vite-plugin-static-copy": "^1.0.5", "vite-tsconfig-paths": "^4.3.2", "web-component-analyzer": "^2.0.0" }, From f1b54fe4268388850bfce4a10daf83017efd4c37 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Wed, 26 Jun 2024 11:34:52 +0200 Subject: [PATCH 066/113] fix login build errors --- src/Umbraco.Web.UI.Login/package-lock.json | 1787 +++++++++++++---- src/Umbraco.Web.UI.Login/package.json | 4 +- .../public/mockServiceWorker.js | 2 +- 3 files changed, 1420 insertions(+), 373 deletions(-) diff --git a/src/Umbraco.Web.UI.Login/package-lock.json b/src/Umbraco.Web.UI.Login/package-lock.json index 73617f8f64..43eaac8a2b 100644 --- a/src/Umbraco.Web.UI.Login/package-lock.json +++ b/src/Umbraco.Web.UI.Login/package-lock.json @@ -6,8 +6,8 @@ "": { "name": "login", "devDependencies": { - "@umbraco-cms/backoffice": "file:../Umbraco.Web.UI.Client", - "@umbraco-ui/uui-css": "^1.8.0-rc.0", + "@umbraco-cms/backoffice": "^14.0.0", + "@umbraco-ui/uui-css": "^1.8.0", "msw": "^2.3.0", "typescript": "^5.4.5", "vite": "^5.2.11", @@ -18,108 +18,6 @@ "npm": ">=10.1" } }, - "../Umbraco.Web.UI.Client": { - "name": "@umbraco-cms/backoffice", - "version": "14.1.0", - "dev": true, - "license": "MIT", - "workspaces": [ - "./src/packages/block", - "./src/packages/core", - "./src/packages/data-type", - "./src/packages/dictionary", - "./src/packages/documents", - "./src/packages/health-check", - "./src/packages/language", - "./src/packages/tags", - "./src/packages/umbraco-news", - "./src/packages/webhook", - "./src/packages/health-check", - "./src/packages/tags" - ], - "dependencies": { - "@types/diff": "^5.2.1", - "@types/dompurify": "^3.0.5", - "@types/uuid": "^9.0.8", - "@umbraco-ui/uui": "1.8.2", - "@umbraco-ui/uui-css": "1.8.0", - "base64-js": "^1.5.1", - "diff": "^5.2.0", - "dompurify": "^3.1.4", - "element-internals-polyfill": "^1.3.11", - "lit": "^3.1.3", - "marked": "^12.0.2", - "monaco-editor": "^0.48.0", - "rxjs": "^7.8.1", - "tinymce": "^6.8.3", - "tinymce-i18n": "^24.5.8", - "uuid": "^9.0.1" - }, - "devDependencies": { - "@babel/core": "^7.24.3", - "@hey-api/openapi-ts": "^0.37.3", - "@mdx-js/react": "^3.0.0", - "@open-wc/testing": "^4.0.0", - "@playwright/test": "^1.41.1", - "@rollup/plugin-commonjs": "^25.0.7", - "@rollup/plugin-json": "^6.1.0", - "@rollup/plugin-node-resolve": "^15.2.3", - "@rollup/plugin-replace": "^5.0.5", - "@storybook/addon-a11y": "^7.6.17", - "@storybook/addon-actions": "^7.6.17", - "@storybook/addon-essentials": "^7.6.17", - "@storybook/addon-links": "^7.6.17", - "@storybook/mdx2-csf": "^1.1.0", - "@storybook/web-components": "^7.6.17", - "@storybook/web-components-vite": "^7.6.17", - "@types/chai": "^4.3.5", - "@types/mocha": "^10.0.1", - "@typescript-eslint/eslint-plugin": "^7.1.0", - "@typescript-eslint/parser": "^7.1.0", - "@web/dev-server-esbuild": "^1.0.2", - "@web/dev-server-import-maps": "^0.2.0", - "@web/dev-server-rollup": "^0.6.3", - "@web/test-runner": "^0.18.1", - "@web/test-runner-playwright": "^0.11.0", - "babel-loader": "^9.1.3", - "eslint": "^8.56.0", - "eslint-config-prettier": "^9.1.0", - "eslint-import-resolver-typescript": "^3.6.1", - "eslint-plugin-import": "^2.29.1", - "eslint-plugin-lit": "^1.14.0", - "eslint-plugin-lit-a11y": "^4.1.1", - "eslint-plugin-local-rules": "^2.0.1", - "eslint-plugin-storybook": "^0.6.15", - "eslint-plugin-wc": "^2.0.4", - "glob": "^10.3.10", - "lucide-static": "^0.379.0", - "msw": "^1.3.2", - "playwright-msw": "^3.0.1", - "prettier": "3.2.5", - "react": "^18.2.0", - "react-dom": "^18.2.0", - "remark-gfm": "^3.0.1", - "rollup": "^4.14.1", - "rollup-plugin-esbuild": "^6.1.1", - "rollup-plugin-import-css": "^3.5.0", - "rollup-plugin-web-worker-loader": "^1.6.1", - "simple-icons": "^12.0.0", - "storybook": "^7.6.17", - "tiny-glob": "^0.2.9", - "tsc-alias": "^1.8.8", - "typedoc": "^0.25.13", - "typescript": "^5.5.2", - "typescript-json-schema": "^0.63.0", - "vite": "^5.2.9", - "vite-plugin-static-copy": "^1.0.5", - "vite-tsconfig-paths": "^4.3.2", - "web-component-analyzer": "^2.0.0" - }, - "engines": { - "node": ">=20.9 <21", - "npm": ">=10.1 < 11" - } - }, "node_modules/@bundled-es-modules/cookie": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@bundled-es-modules/cookie/-/cookie-2.0.0.tgz", @@ -139,9 +37,9 @@ } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz", - "integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", "cpu": [ "ppc64" ], @@ -155,9 +53,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz", - "integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", "cpu": [ "arm" ], @@ -171,9 +69,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz", - "integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", "cpu": [ "arm64" ], @@ -187,9 +85,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz", - "integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", "cpu": [ "x64" ], @@ -203,9 +101,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz", - "integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", "cpu": [ "arm64" ], @@ -219,9 +117,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz", - "integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", "cpu": [ "x64" ], @@ -235,9 +133,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz", - "integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", "cpu": [ "arm64" ], @@ -251,9 +149,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz", - "integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", "cpu": [ "x64" ], @@ -267,9 +165,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz", - "integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", "cpu": [ "arm" ], @@ -283,9 +181,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz", - "integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", "cpu": [ "arm64" ], @@ -299,9 +197,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz", - "integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", "cpu": [ "ia32" ], @@ -315,9 +213,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz", - "integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", "cpu": [ "loong64" ], @@ -331,9 +229,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz", - "integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", "cpu": [ "mips64el" ], @@ -347,9 +245,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz", - "integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", "cpu": [ "ppc64" ], @@ -363,9 +261,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz", - "integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", "cpu": [ "riscv64" ], @@ -379,9 +277,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz", - "integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", "cpu": [ "s390x" ], @@ -395,9 +293,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz", - "integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", "cpu": [ "x64" ], @@ -411,9 +309,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz", - "integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", "cpu": [ "x64" ], @@ -427,9 +325,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz", - "integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", "cpu": [ "x64" ], @@ -443,9 +341,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz", - "integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", "cpu": [ "x64" ], @@ -459,9 +357,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz", - "integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", "cpu": [ "arm64" ], @@ -475,9 +373,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz", - "integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", "cpu": [ "ia32" ], @@ -491,9 +389,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz", - "integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", "cpu": [ "x64" ], @@ -507,35 +405,34 @@ } }, "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==", + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-3.1.10.tgz", + "integrity": "sha512-/aAHu83Njy6yf44T+ZrRPUkMcUqprrOiIKsyMvf9jOV+vF5BNb2ja1aLP33MK36W8eaf91MTL/mU/e6METuENg==", "dev": true, "dependencies": { - "@inquirer/core": "^7.0.0", - "@inquirer/type": "^1.2.0" + "@inquirer/core": "^8.2.3", + "@inquirer/type": "^1.3.3" }, "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==", + "version": "8.2.3", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-8.2.3.tgz", + "integrity": "sha512-WrpDVPAaxJQjHid3Ra4FhUO70YBzkHSYVyW5X48L5zHYdudoPISJqTRRWSeamHfaXda7PNNaC5Py5MEo7QwBNA==", "dev": true, "dependencies": { - "@inquirer/type": "^1.2.0", + "@inquirer/figures": "^1.0.3", + "@inquirer/type": "^1.3.3", "@types/mute-stream": "^0.0.4", - "@types/node": "^20.11.16", + "@types/node": "^20.14.6", "@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" @@ -544,24 +441,19 @@ "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==", + "node_modules/@inquirer/figures": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.3.tgz", + "integrity": "sha512-ErXXzENMH5pJt5/ssXV0DfWUZqly8nGzf0UcBV9xTnP+KyffE2mqyxIMBrZ8ijQck2nU0TQm40EQB53YreyWHw==", "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, "engines": { - "node": ">=8" + "node": ">=18" } }, "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==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-1.3.3.tgz", + "integrity": "sha512-xTUt0NulylX27/zMx04ZYar/kr1raaiFTVvQ5feljQsiAgdm0WPj4S73/ye0fbslh+15QrIuDvfCXTek7pMY5A==", "dev": true, "engines": { "node": ">=18" @@ -585,9 +477,9 @@ } }, "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==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@mswjs/cookies/-/cookies-1.1.1.tgz", + "integrity": "sha512-W68qOHEjx1iD+4VjQudlx26CPIoxmIAtK4ZCexU0/UJBG6jYhcuyzKJx+Iw8uhBIGd9eba64XgWVgo20it1qwA==", "dev": true, "engines": { "node": ">=18" @@ -633,9 +525,9 @@ "dev": true }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.13.0.tgz", - "integrity": "sha512-5ZYPOuaAqEH/W3gYsRkxQATBW3Ii1MfaT4EQstTnLKViLi2gLSQmlmtTpGucNP3sXEpOiI5tdGhjdE111ekyEg==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.18.0.tgz", + "integrity": "sha512-Tya6xypR10giZV1XzxmH5wr25VcZSncG0pZIjfePT0OVBvqNEurzValetGNarVrGiq66EBVAFn15iYX4w6FKgQ==", "cpu": [ "arm" ], @@ -646,9 +538,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.13.0.tgz", - "integrity": "sha512-BSbaCmn8ZadK3UAQdlauSvtaJjhlDEjS5hEVVIN3A4bbl3X+otyf/kOJV08bYiRxfejP3DXFzO2jz3G20107+Q==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.18.0.tgz", + "integrity": "sha512-avCea0RAP03lTsDhEyfy+hpfr85KfyTctMADqHVhLAF3MlIkq83CP8UfAHUssgXTYd+6er6PaAhx/QGv4L1EiA==", "cpu": [ "arm64" ], @@ -659,9 +551,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.13.0.tgz", - "integrity": "sha512-Ovf2evVaP6sW5Ut0GHyUSOqA6tVKfrTHddtmxGQc1CTQa1Cw3/KMCDEEICZBbyppcwnhMwcDce9ZRxdWRpVd6g==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.18.0.tgz", + "integrity": "sha512-IWfdwU7KDSm07Ty0PuA/W2JYoZ4iTj3TUQjkVsO/6U+4I1jN5lcR71ZEvRh52sDOERdnNhhHU57UITXz5jC1/w==", "cpu": [ "arm64" ], @@ -672,9 +564,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.13.0.tgz", - "integrity": "sha512-U+Jcxm89UTK592vZ2J9st9ajRv/hrwHdnvyuJpa5A2ngGSVHypigidkQJP+YiGL6JODiUeMzkqQzbCG3At81Gg==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.18.0.tgz", + "integrity": "sha512-n2LMsUz7Ynu7DoQrSQkBf8iNrjOGyPLrdSg802vk6XT3FtsgX6JbE8IHRvposskFm9SNxzkLYGSq9QdpLYpRNA==", "cpu": [ "x64" ], @@ -685,9 +577,22 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.13.0.tgz", - "integrity": "sha512-8wZidaUJUTIR5T4vRS22VkSMOVooG0F4N+JSwQXWSRiC6yfEsFMLTYRFHvby5mFFuExHa/yAp9juSphQQJAijQ==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.18.0.tgz", + "integrity": "sha512-C/zbRYRXFjWvz9Z4haRxcTdnkPt1BtCkz+7RtBSuNmKzMzp3ZxdM28Mpccn6pt28/UWUCTXa+b0Mx1k3g6NOMA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.18.0.tgz", + "integrity": "sha512-l3m9ewPgjQSXrUMHg93vt0hYCGnrMOcUpTz6FLtbwljo2HluS4zTXFy2571YQbisTnfTKPZ01u/ukJdQTLGh9A==", "cpu": [ "arm" ], @@ -698,9 +603,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.13.0.tgz", - "integrity": "sha512-Iu0Kno1vrD7zHQDxOmvweqLkAzjxEVqNhUIXBsZ8hu8Oak7/5VTPrxOEZXYC1nmrBVJp0ZcL2E7lSuuOVaE3+w==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.18.0.tgz", + "integrity": "sha512-rJ5D47d8WD7J+7STKdCUAgmQk49xuFrRi9pZkWoRD1UeSMakbcepWXPF8ycChBoAqs1pb2wzvbY6Q33WmN2ftw==", "cpu": [ "arm64" ], @@ -711,9 +616,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.13.0.tgz", - "integrity": "sha512-C31QrW47llgVyrRjIwiOwsHFcaIwmkKi3PCroQY5aVq4H0A5v/vVVAtFsI1nfBngtoRpeREvZOkIhmRwUKkAdw==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.18.0.tgz", + "integrity": "sha512-be6Yx37b24ZwxQ+wOQXXLZqpq4jTckJhtGlWGZs68TgdKXJgw54lUUoFYrg6Zs/kjzAQwEwYbp8JxZVzZLRepQ==", "cpu": [ "arm64" ], @@ -723,10 +628,23 @@ "linux" ] }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.18.0.tgz", + "integrity": "sha512-hNVMQK+qrA9Todu9+wqrXOHxFiD5YmdEi3paj6vP02Kx1hjd2LLYR2eaN7DsEshg09+9uzWi2W18MJDlG0cxJA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.13.0.tgz", - "integrity": "sha512-Oq90dtMHvthFOPMl7pt7KmxzX7E71AfyIhh+cPhLY9oko97Zf2C9tt/XJD4RgxhaGeAraAXDtqxvKE1y/j35lA==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.18.0.tgz", + "integrity": "sha512-ROCM7i+m1NfdrsmvwSzoxp9HFtmKGHEqu5NNDiZWQtXLA8S5HBCkVvKAxJ8U+CVctHwV2Gb5VUaK7UAkzhDjlg==", "cpu": [ "riscv64" ], @@ -736,10 +654,23 @@ "linux" ] }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.18.0.tgz", + "integrity": "sha512-0UyyRHyDN42QL+NbqevXIIUnKA47A+45WyasO+y2bGJ1mhQrfrtXUpTxCOrfxCR4esV3/RLYyucGVPiUsO8xjg==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.13.0.tgz", - "integrity": "sha512-yUD/8wMffnTKuiIsl6xU+4IA8UNhQ/f1sAnQebmE/lyQ8abjsVyDkyRkWop0kdMhKMprpNIhPmYlCxgHrPoXoA==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.18.0.tgz", + "integrity": "sha512-xuglR2rBVHA5UsI8h8UbX4VJ470PtGCf5Vpswh7p2ukaqBGFTnsfzxUBetoWBWymHMxbIG0Cmx7Y9qDZzr648w==", "cpu": [ "x64" ], @@ -750,9 +681,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.13.0.tgz", - "integrity": "sha512-9RyNqoFNdF0vu/qqX63fKotBh43fJQeYC98hCaf89DYQpv+xu0D8QFSOS0biA7cGuqJFOc1bJ+m2rhhsKcw1hw==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.18.0.tgz", + "integrity": "sha512-LKaqQL9osY/ir2geuLVvRRs+utWUNilzdE90TpyoX0eNqPzWjRm14oMEE+YLve4k/NAqCdPkGYDaDF5Sw+xBfg==", "cpu": [ "x64" ], @@ -763,9 +694,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.13.0.tgz", - "integrity": "sha512-46ue8ymtm/5PUU6pCvjlic0z82qWkxv54GTJZgHrQUuZnVH+tvvSP0LsozIDsCBFO4VjJ13N68wqrKSeScUKdA==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.18.0.tgz", + "integrity": "sha512-7J6TkZQFGo9qBKH0pk2cEVSRhJbL6MtfWxth7Y5YmZs57Pi+4x6c2dStAUvaQkHQLnEQv1jzBUW43GvZW8OFqA==", "cpu": [ "arm64" ], @@ -776,9 +707,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.13.0.tgz", - "integrity": "sha512-P5/MqLdLSlqxbeuJ3YDeX37srC8mCflSyTrUsgbU1c/U9j6l2g2GiIdYaGD9QjdMQPMSgYm7hgg0551wHyIluw==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.18.0.tgz", + "integrity": "sha512-Txjh+IxBPbkUB9+SXZMpv+b/vnTEtFyfWZgJ6iyCmt2tdx0OF5WhFowLmnh8ENGNpfUlUZkdI//4IEmhwPieNg==", "cpu": [ "ia32" ], @@ -789,9 +720,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.13.0.tgz", - "integrity": "sha512-UKXUQNbO3DOhzLRwHSpa0HnhhCgNODvfoPWv2FCXme8N/ANFfhIPMGuOT+QuKd16+B5yxZ0HdpNlqPvTMS1qfw==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.18.0.tgz", + "integrity": "sha512-UOo5FdvOL0+eIVTgS4tIdbW+TtnBLWg1YBCcU2KWM7nuNwRz9bksDX1bekJJCpu25N1DVWaCwnT39dVQxzqS8g==", "cpu": [ "x64" ], @@ -807,6 +738,23 @@ "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==", "dev": true }, + "node_modules/@types/diff": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/@types/diff/-/diff-5.2.1.tgz", + "integrity": "sha512-uxpcuwWJGhe2AR1g8hD9F5OYGCqjqWnBUQFD8gMZsDbv8oPHzxJF6iMO6n8Tk0AdzlxoaaoQhOYlIg/PukVU8g==", + "dev": true, + "peer": true + }, + "node_modules/@types/dompurify": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/dompurify/-/dompurify-3.0.5.tgz", + "integrity": "sha512-1Wg0g3BtQF7sSb27fJQAKck1HECM6zV1EB66j8JH9i3LCjYabJa0FSdiSgsD5K/RbrsR0SiraKacLB+T8ZVYAg==", + "dev": true, + "peer": true, + "dependencies": { + "@types/trusted-types": "*" + } + }, "node_modules/@types/estree": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", @@ -823,18 +771,18 @@ } }, "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==", + "version": "20.14.9", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.9.tgz", + "integrity": "sha512-06OCtnTXtWOZBJlRApleWndH4JsRVs1pDCc8dLSQp+7PpUpX3ePdHyeNSFTeSe7FtKyQkrlPvHwJOW3SLd8Oyg==", "dev": true, "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==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/statuses/-/statuses-2.0.5.tgz", + "integrity": "sha512-jmIUGWrAiwu3dZpxntxieC+1n/5c3mjrImkmOSQ2NC5uP6cYO4aAZDdSmRcI5C1oiTmqlZGHC+/NmJrKogbP5A==", "dev": true }, "node_modules/@types/trusted-types": { @@ -844,6 +792,13 @@ "dev": true, "peer": true }, + "node_modules/@types/uuid": { + "version": "9.0.8", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.8.tgz", + "integrity": "sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==", + "dev": true, + "peer": true + }, "node_modules/@types/wrap-ansi": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@types/wrap-ansi/-/wrap-ansi-3.0.0.tgz", @@ -851,18 +806,1009 @@ "dev": true }, "node_modules/@umbraco-cms/backoffice": { - "resolved": "../Umbraco.Web.UI.Client", - "link": true + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/@umbraco-cms/backoffice/-/backoffice-14.0.0.tgz", + "integrity": "sha512-PpJXHBeqDEEKTf4L/K7d8Ua9E2G9VuXHL76lBmBrf+1Sa7iWVuqf5BCvSa0wy1hKLrU1ytLsmByvVHEcix4XOw==", + "dev": true, + "engines": { + "node": ">=20.9 <21", + "npm": ">=10.1 < 11" + }, + "peerDependencies": { + "@types/diff": "^5.2.1", + "@types/dompurify": "^3.0.5", + "@types/uuid": "^9.0.8", + "@umbraco-ui/uui": "1.8.1", + "@umbraco-ui/uui-css": "1.8.0", + "base64-js": "^1.5.1", + "diff": "^5.2.0", + "dompurify": "^3.1.4", + "element-internals-polyfill": "^1.3.11", + "lit": "^3.1.3", + "marked": "^12.0.2", + "monaco-editor": "^0.48.0", + "rxjs": "^7.8.1", + "tinymce": "^6.8.3", + "tinymce-i18n": "^24.5.8", + "uuid": "^9.0.1" + } + }, + "node_modules/@umbraco-ui/uui": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui/-/uui-1.8.1.tgz", + "integrity": "sha512-KWKtuSQdxeCbGH2gezumuFysf+33q2TZy4h85zCABFvHgOeeR1EB7/S2jUNGoe2IOqYLktYl0GdQszfmyFN1dg==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-action-bar": "1.8.0", + "@umbraco-ui/uui-avatar": "1.8.0", + "@umbraco-ui/uui-avatar-group": "1.8.0", + "@umbraco-ui/uui-badge": "1.8.0", + "@umbraco-ui/uui-base": "1.8.0", + "@umbraco-ui/uui-boolean-input": "1.8.0", + "@umbraco-ui/uui-box": "1.8.0", + "@umbraco-ui/uui-breadcrumbs": "1.8.0", + "@umbraco-ui/uui-button": "1.8.0", + "@umbraco-ui/uui-button-group": "1.8.0", + "@umbraco-ui/uui-button-inline-create": "1.8.0", + "@umbraco-ui/uui-card": "1.8.0", + "@umbraco-ui/uui-card-block-type": "1.8.0", + "@umbraco-ui/uui-card-content-node": "1.8.0", + "@umbraco-ui/uui-card-media": "1.8.0", + "@umbraco-ui/uui-card-user": "1.8.0", + "@umbraco-ui/uui-caret": "1.8.0", + "@umbraco-ui/uui-checkbox": "1.8.0", + "@umbraco-ui/uui-color-area": "1.8.0", + "@umbraco-ui/uui-color-picker": "1.8.0", + "@umbraco-ui/uui-color-slider": "1.8.0", + "@umbraco-ui/uui-color-swatch": "1.8.0", + "@umbraco-ui/uui-color-swatches": "1.8.0", + "@umbraco-ui/uui-combobox": "1.8.0", + "@umbraco-ui/uui-combobox-list": "1.8.0", + "@umbraco-ui/uui-css": "1.8.0", + "@umbraco-ui/uui-dialog": "1.8.0", + "@umbraco-ui/uui-dialog-layout": "1.8.0", + "@umbraco-ui/uui-file-dropzone": "1.8.0", + "@umbraco-ui/uui-file-preview": "1.8.0", + "@umbraco-ui/uui-form": "1.8.0", + "@umbraco-ui/uui-form-layout-item": "1.8.1", + "@umbraco-ui/uui-form-validation-message": "1.8.1", + "@umbraco-ui/uui-icon": "1.8.0", + "@umbraco-ui/uui-icon-registry": "1.8.0", + "@umbraco-ui/uui-icon-registry-essential": "1.8.0", + "@umbraco-ui/uui-input": "1.8.0", + "@umbraco-ui/uui-input-file": "1.8.0", + "@umbraco-ui/uui-input-lock": "1.8.0", + "@umbraco-ui/uui-input-password": "1.8.0", + "@umbraco-ui/uui-keyboard-shortcut": "1.8.0", + "@umbraco-ui/uui-label": "1.8.0", + "@umbraco-ui/uui-loader": "1.8.0", + "@umbraco-ui/uui-loader-bar": "1.8.0", + "@umbraco-ui/uui-loader-circle": "1.8.0", + "@umbraco-ui/uui-menu-item": "1.8.0", + "@umbraco-ui/uui-modal": "1.8.0", + "@umbraco-ui/uui-pagination": "1.8.0", + "@umbraco-ui/uui-popover": "1.8.0", + "@umbraco-ui/uui-popover-container": "1.8.0", + "@umbraco-ui/uui-progress-bar": "1.8.0", + "@umbraco-ui/uui-radio": "1.8.0", + "@umbraco-ui/uui-range-slider": "1.8.0", + "@umbraco-ui/uui-ref": "1.8.0", + "@umbraco-ui/uui-ref-list": "1.8.0", + "@umbraco-ui/uui-ref-node": "1.8.0", + "@umbraco-ui/uui-ref-node-data-type": "1.8.0", + "@umbraco-ui/uui-ref-node-document-type": "1.8.0", + "@umbraco-ui/uui-ref-node-form": "1.8.0", + "@umbraco-ui/uui-ref-node-member": "1.8.0", + "@umbraco-ui/uui-ref-node-package": "1.8.0", + "@umbraco-ui/uui-ref-node-user": "1.8.0", + "@umbraco-ui/uui-scroll-container": "1.8.0", + "@umbraco-ui/uui-select": "1.8.0", + "@umbraco-ui/uui-slider": "1.8.0", + "@umbraco-ui/uui-symbol-expand": "1.8.0", + "@umbraco-ui/uui-symbol-file": "1.8.0", + "@umbraco-ui/uui-symbol-file-dropzone": "1.8.0", + "@umbraco-ui/uui-symbol-file-thumbnail": "1.8.0", + "@umbraco-ui/uui-symbol-folder": "1.8.0", + "@umbraco-ui/uui-symbol-lock": "1.8.0", + "@umbraco-ui/uui-symbol-more": "1.8.0", + "@umbraco-ui/uui-symbol-sort": "1.8.0", + "@umbraco-ui/uui-table": "1.8.0", + "@umbraco-ui/uui-tabs": "1.8.0", + "@umbraco-ui/uui-tag": "1.8.0", + "@umbraco-ui/uui-textarea": "1.8.0", + "@umbraco-ui/uui-toast-notification": "1.8.0", + "@umbraco-ui/uui-toast-notification-container": "1.8.0", + "@umbraco-ui/uui-toast-notification-layout": "1.8.0", + "@umbraco-ui/uui-toggle": "1.8.0", + "@umbraco-ui/uui-visually-hidden": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-action-bar": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-action-bar/-/uui-action-bar-1.8.0.tgz", + "integrity": "sha512-IRs42chstgXFo5b3i0j80Emt+uZSt/WmDDv7gTtB768FL1C+k0BR5sYVleEmUdkfCOv+WIVo1FAqd+9CPFkDDw==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0", + "@umbraco-ui/uui-button-group": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-avatar": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-avatar/-/uui-avatar-1.8.0.tgz", + "integrity": "sha512-ek6SFYEvEbu1Jf1FVrqBDHuWqCnekkU1hm4XDHEpEyhPE5OOC70SyYLB6brT0kvgBE0QKB2txYu7u8ZbWzy+OQ==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-avatar-group": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-avatar-group/-/uui-avatar-group-1.8.0.tgz", + "integrity": "sha512-AS6+GzeoAOS6vuZ6okP30iik8cvYPjBvoWtSYcnV0gScw52FIg9ak+j5L+rQHzE8LCqT8c6RE63HsAsJe7f6oA==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-avatar": "1.8.0", + "@umbraco-ui/uui-base": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-badge": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-badge/-/uui-badge-1.8.0.tgz", + "integrity": "sha512-mKHkkXIwN7oUybeQo5J5TOgqghinJH5gE9lJwOemNCy/oiV/TeYHOr7MqHxIJ+13Nwl9O6JbSRWbPbOD9TSkVw==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-base": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-base/-/uui-base-1.8.0.tgz", + "integrity": "sha512-LIPS3sfgOr/cgpDueTqpX+t6Bw0BpNISQSrAeyC+c6X0WiahKLuwob6UXSnefh9j5xIYa5+GY1gEUDgI4wlRhg==", + "dev": true, + "peer": true, + "peerDependencies": { + "lit": ">=2.8.0" + } + }, + "node_modules/@umbraco-ui/uui-boolean-input": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-boolean-input/-/uui-boolean-input-1.8.0.tgz", + "integrity": "sha512-6GqzuALrzrJIWIAdsYAau9t3sxYL0P+OKSKpcvj+4/DkbhbWjk54CtVFyWBAzYa9LhZHauGl2VYzxSvmGWARSA==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-box": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-box/-/uui-box-1.8.0.tgz", + "integrity": "sha512-/j/69od/uWd1Utb2IzU5pq5cvKpsq0cV4F+pS9e6HejzpcVUCz1AtdKNQvgpyOzd/oS9r8Z6pYL/V/gEydyqwg==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0", + "@umbraco-ui/uui-css": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-breadcrumbs": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-breadcrumbs/-/uui-breadcrumbs-1.8.0.tgz", + "integrity": "sha512-Hv5NAfRzfaXcDYcuribjpaooZk1LVcHNTaLwoxVAUN64sufYS8kfw0sN8+jsacumUP3rpy0XgR9Ic37JUoIkBw==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-button": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-button/-/uui-button-1.8.0.tgz", + "integrity": "sha512-M0pLzpGt2CuzQAHqiHQwVdzFOyb9EznCT7b+YMQOlJCMfeVmN2KBF2xUlfb/3M2LVDukTHyGHzqaWj8Lj6YUbA==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0", + "@umbraco-ui/uui-icon-registry-essential": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-button-group": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-button-group/-/uui-button-group-1.8.0.tgz", + "integrity": "sha512-5K/cvrOWvRmoXByuI1illF2e9sCzzegmlEpS4XbVk1XW/6quzRJpXSCrY+awj01kFrxB+UC8mB1DIECHKNyeVg==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-button-inline-create": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-button-inline-create/-/uui-button-inline-create-1.8.0.tgz", + "integrity": "sha512-smSZKMG0cAp+BTZe0wRaYotcQElja8gqznGaIyuGuWvvpvWEiuWMC9xjMytFNvaawCN1+uLc5fdcCArPmFjH+w==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-card": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-card/-/uui-card-1.8.0.tgz", + "integrity": "sha512-wwHaDbwDmragvIhhAtv/D2Ys47kXFPBKvE+/ahofXUygjTGbmjbKJ57Qfo6x8sD1BM3bSTDU6gUWaf4s0/D3WQ==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-card-block-type": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-card-block-type/-/uui-card-block-type-1.8.0.tgz", + "integrity": "sha512-8Ydat2K4LipsQaCEhDTN4DeVHiqOCdEOY4Z43XCf6bTU91lNPGdagtC0ZE9b4M28E03ou4E19Ms7o2m59g0OWw==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0", + "@umbraco-ui/uui-card": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-card-content-node": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-card-content-node/-/uui-card-content-node-1.8.0.tgz", + "integrity": "sha512-R0SYtKk5Z+on0xQ0cD1z2z43mSTgYi7sKtQDrhwoP/sWbp9xIS2wPOO+PoMiUonwXPo4JuSZ9ghgT4HzsQce1A==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0", + "@umbraco-ui/uui-card": "1.8.0", + "@umbraco-ui/uui-icon": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-card-media": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-card-media/-/uui-card-media-1.8.0.tgz", + "integrity": "sha512-C1xsdO9mWJ/gzE8nLfF2k5NfpFzJ2yMQYzJVtov3s9C33iy6NVq7OM67o+QugCqDuwwYSkonjgNJLHTav78KVg==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0", + "@umbraco-ui/uui-card": "1.8.0", + "@umbraco-ui/uui-symbol-file": "1.8.0", + "@umbraco-ui/uui-symbol-folder": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-card-user": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-card-user/-/uui-card-user-1.8.0.tgz", + "integrity": "sha512-b6LiMCl/oFaW6MnPXBMCPqlVDHF/TT3LByQOTJ8rE+WHzY/zE9toVLy/LccxB62Ur/Hz7XakhU8xHaugH+zs3w==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-avatar": "1.8.0", + "@umbraco-ui/uui-base": "1.8.0", + "@umbraco-ui/uui-card": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-caret": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-caret/-/uui-caret-1.8.0.tgz", + "integrity": "sha512-LxS0vLKZYNKsef/FgNXFh/CBauf+6Dgac+n5VyCu6FElh8UTP+NOeAA/4KEVSJh8gThJ0Fmb8qoMSFop+41wLg==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-checkbox": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-checkbox/-/uui-checkbox-1.8.0.tgz", + "integrity": "sha512-Gf/kZ4q5SuLNEEfcL1/YRzcOI5CZTsMyo2+9LuksCZqRzWtxoo1meB42GZRjE4GTCFZfQOr4Ayz7ZNRaizuM+Q==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0", + "@umbraco-ui/uui-boolean-input": "1.8.0", + "@umbraco-ui/uui-icon-registry-essential": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-color-area": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-color-area/-/uui-color-area-1.8.0.tgz", + "integrity": "sha512-V3+iph2Vq58T9f4FoJvxsjq0LpH5VJhC2P2VkAWvMO1m528QOULDP+b5csYWH1pF78RxSZ5Lm042Dnw9XOqN2w==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0", + "colord": "^2.9.3" + } + }, + "node_modules/@umbraco-ui/uui-color-picker": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-color-picker/-/uui-color-picker-1.8.0.tgz", + "integrity": "sha512-DQtKN4ivIRx54SYUhxdlbFf5ibmnp5/mscOiC98HObYVTeM9ZJUrKfFGTU9Qfekz3q+rPzzv7eec8E0Zb6qfwg==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0", + "@umbraco-ui/uui-popover-container": "1.8.0", + "colord": "^2.9.3" + } + }, + "node_modules/@umbraco-ui/uui-color-slider": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-color-slider/-/uui-color-slider-1.8.0.tgz", + "integrity": "sha512-DHCFX0JZXqI6wp2fRe+lOd9TAJVzTC9ghDNP+qMGattsxRnTf/pxoYucW7F5ee/ggiBIsS8i47kFa2wDmausiA==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-color-swatch": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-color-swatch/-/uui-color-swatch-1.8.0.tgz", + "integrity": "sha512-z9RQ0R5E9SErqiY1/kU6Rnp+LQBM119OKqAexHo32cM/9iyzLIEUY4CwzCYE9/ogeXDgljXLTGX21jddCNCv9A==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0", + "@umbraco-ui/uui-icon-registry-essential": "1.8.0", + "colord": "^2.9.3" + } + }, + "node_modules/@umbraco-ui/uui-color-swatches": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-color-swatches/-/uui-color-swatches-1.8.0.tgz", + "integrity": "sha512-qCHYtHPhPsubrVn/cydmigbAde0fc+kJC7ZBxCcuJjyP+wiUhq2/d6dSfYumCcVw1N3Hyj7BRJ/8ZedUIZQ5/w==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0", + "@umbraco-ui/uui-color-swatch": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-combobox": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-combobox/-/uui-combobox-1.8.0.tgz", + "integrity": "sha512-FVc3PlBYU8S48Zr75pig+5YXh05R3wRKdLl41l7vFBDGGWsgjIsM+vJ6LaM+VoshnTzUTOVvKLE/N0FNTVUvrg==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0", + "@umbraco-ui/uui-button": "1.8.0", + "@umbraco-ui/uui-combobox-list": "1.8.0", + "@umbraco-ui/uui-icon": "1.8.0", + "@umbraco-ui/uui-popover-container": "1.8.0", + "@umbraco-ui/uui-scroll-container": "1.8.0", + "@umbraco-ui/uui-symbol-expand": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-combobox-list": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-combobox-list/-/uui-combobox-list-1.8.0.tgz", + "integrity": "sha512-3w353u7FdYNLCz6YV+Pk+lxlTeHd2H6rmxzwb+0BHCjbwaw9MLojbhE4JGTCpf/qtk54Xc8Dzg++aznKAYpbVw==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0" + } }, "node_modules/@umbraco-ui/uui-css": { - "version": "1.8.0-rc.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-css/-/uui-css-1.8.0-rc.0.tgz", - "integrity": "sha512-trwLCgJtT91iP2b20QlHWjuj44AF4lWCg4CqBZoT2Z8a5IedqflnQstXCZRYm/F5Re32YGTwlR9lF1rAXqq4gg==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-css/-/uui-css-1.8.0.tgz", + "integrity": "sha512-9o9OGUXQK8D9i/VSmH3gUTvH7se+4Ey22dSfbn4Jzcbe6AyGmxKocr/8eZXdkIYwNvK2aUIz/b7GIEbQc4utbA==", "dev": true, "peerDependencies": { "lit": ">=2.8.0" } }, + "node_modules/@umbraco-ui/uui-dialog": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-dialog/-/uui-dialog-1.8.0.tgz", + "integrity": "sha512-QI/Oa7BJ7eEdHcy7hSuFhMPbbPitxnIb2BHVmQzy+YpBShrR3C1GW0FGcYFgJlI/S+jodf3A59VDnVL69gyliQ==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0", + "@umbraco-ui/uui-css": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-dialog-layout": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-dialog-layout/-/uui-dialog-layout-1.8.0.tgz", + "integrity": "sha512-iv4wEfb6QhCOm8lxg/zH7yrJuVSEEBGgAN67qdvZ9Tu2SFzUCGTzrUcBXlL+U10cbIlq7j6KKvSQvE/IHX40Tg==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-file-dropzone": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-file-dropzone/-/uui-file-dropzone-1.8.0.tgz", + "integrity": "sha512-Fpl/aGprUbcdPngB6xpR8/i7o8HKAWNCUze+N1pXiIVBRXmthEWO6fSm4+LkkkRoZwvYqQSaeu6Mw+Sj+MzHGA==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0", + "@umbraco-ui/uui-symbol-file-dropzone": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-file-preview": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-file-preview/-/uui-file-preview-1.8.0.tgz", + "integrity": "sha512-WhE/9klwIUjch6PxF+DuFlu+ql0h9YbefMxj/xU4rGUTE/dK4CgA7eVQ/YfAp+WrdtwUFHyp3fThyJvfrodSgA==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0", + "@umbraco-ui/uui-symbol-file": "1.8.0", + "@umbraco-ui/uui-symbol-file-thumbnail": "1.8.0", + "@umbraco-ui/uui-symbol-folder": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-form": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-form/-/uui-form-1.8.0.tgz", + "integrity": "sha512-9NtavsRoh9X39ezzLtwwbK77mUecavcjxP58Jdba/2/6wXRx+vx7qsWV5Rn3OC9XG4tZi6VLFFKahbV8N/jgjw==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-form-layout-item": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-form-layout-item/-/uui-form-layout-item-1.8.1.tgz", + "integrity": "sha512-QHBZayR4T49MAyS9N2jy1rgQ5Yk1JpwoWvWBpbI/WDE718zTjwUJxbLfukq+NnJx7Q1DplZ+IHTnHZkInMC2GQ==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0", + "@umbraco-ui/uui-form-validation-message": "1.8.1" + } + }, + "node_modules/@umbraco-ui/uui-form-validation-message": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-form-validation-message/-/uui-form-validation-message-1.8.1.tgz", + "integrity": "sha512-o4WfGHRtV+DrN064DtzyljRkUGYMYEkLHxxbawLowpdmdwyvLByaGOkxfuEJvHgPnncR02//gM04EjulEbGitw==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-icon": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-icon/-/uui-icon-1.8.0.tgz", + "integrity": "sha512-hub+KNcwiy+SKxEpI/ei3w1Ficr1SWxcLfwL67MOKS5YyB6RDwSl2FOXx+MkttTAO7PvGBbAgkiiXEkI/rxivg==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-icon-registry": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-icon-registry/-/uui-icon-registry-1.8.0.tgz", + "integrity": "sha512-+55qGgxOBlHmQerxIhKkKJYJgPwnXwsLOBVwF8oYIM1sV58bu7BrGQRUw/K0ytFavrFOb+Easkn62wqzisXsRQ==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0", + "@umbraco-ui/uui-icon": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-icon-registry-essential": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-icon-registry-essential/-/uui-icon-registry-essential-1.8.0.tgz", + "integrity": "sha512-6+bM30t3+YpknxIjcCHi07eS+MXMkDRP+GBfrUgULqH/EqnJN/OuwMzSdbwFuwzqxmC7thx74Zfl6+JBuIs9lw==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0", + "@umbraco-ui/uui-icon-registry": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-input": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-input/-/uui-input-1.8.0.tgz", + "integrity": "sha512-abtQbWWDT+3uo4KVaU+ZbDVKSNtB8r0C/3L7Ql/7xJ2BNI2oSUSAcWoSV6V8Vx0DYh1XWlEQpfSAbk5Fdr7u4w==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-input-file": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-input-file/-/uui-input-file-1.8.0.tgz", + "integrity": "sha512-20fXbIwjyLZWIVsqFt06ldQqA8sHEPm8Y0hmPwbb0nagYfjmIblIE1thT76JA95do7qcU50xGBN9mHt8KvPpQA==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-action-bar": "1.8.0", + "@umbraco-ui/uui-base": "1.8.0", + "@umbraco-ui/uui-button": "1.8.0", + "@umbraco-ui/uui-file-dropzone": "1.8.0", + "@umbraco-ui/uui-icon": "1.8.0", + "@umbraco-ui/uui-icon-registry-essential": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-input-lock": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-input-lock/-/uui-input-lock-1.8.0.tgz", + "integrity": "sha512-ZFBssrhCPrCiSfoS6Y9yA9u2dktzNDCRFQ95Z9nQEthhVm2okyqMS3daGz57Vdm4+LVB0HrqIjpEHC6dOqNTBg==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0", + "@umbraco-ui/uui-button": "1.8.0", + "@umbraco-ui/uui-icon": "1.8.0", + "@umbraco-ui/uui-input": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-input-password": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-input-password/-/uui-input-password-1.8.0.tgz", + "integrity": "sha512-Ra7bR40GzjX11qobHsog2FPGd3FsPzgxPpGLFyMTHzDg2gchYU+KQKkmt6CTzGPgSFuN7DsEW0BMnFnWJ+2moQ==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0", + "@umbraco-ui/uui-icon-registry-essential": "1.8.0", + "@umbraco-ui/uui-input": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-keyboard-shortcut": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-keyboard-shortcut/-/uui-keyboard-shortcut-1.8.0.tgz", + "integrity": "sha512-XBO06bZbo7H39k33pm8EoWxm9MP/NXh7W430dLqB5E3q1EOO24PQw2voLsOyegVM3IqgKyMBTO7xHR8NmeZkyg==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-label": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-label/-/uui-label-1.8.0.tgz", + "integrity": "sha512-Qj3CtfVIv3rVIanTbMvg6UAB2faWGu2mMtvVfmr9kkEP9MGfmA/xgQN94lKlDI7ic54FVkCV4N+1kA5yNkeDSQ==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-loader": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-loader/-/uui-loader-1.8.0.tgz", + "integrity": "sha512-CL+n3Icp4ugfn7SBbhbYC4WTBQ+kT27WwJIesOcjw2lmt2l20RQUyBBbZAABx2ayyDuvIzEWnFEzWW8VyVworw==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-loader-bar": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-loader-bar/-/uui-loader-bar-1.8.0.tgz", + "integrity": "sha512-KuUtQ4r/wkbVl4IJVgUb9DCXvV1yvupDw6AAnWuOu7zexuzPfrl32cKBLlnNmhGqnEGcQonX6jf24Lf8T7W6Nw==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-loader-circle": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-loader-circle/-/uui-loader-circle-1.8.0.tgz", + "integrity": "sha512-L5RyH10Es/stG7CzNzS0bKOzCY+zLSwmqyh0dc8HwzZCvc6XtFHV0KgcxMjmtWWulLsQPgzIOIigf3wefr2VNQ==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-menu-item": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-menu-item/-/uui-menu-item-1.8.0.tgz", + "integrity": "sha512-0zEMhcw35Evw7cEq1W0GYNVCzIorTVMzFquU7Sg2QiZmk3eiF6HvkF/gHr4d0WR7HvztM1k/eVm+RTs6zp+96g==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0", + "@umbraco-ui/uui-loader-bar": "1.8.0", + "@umbraco-ui/uui-symbol-expand": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-modal": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-modal/-/uui-modal-1.8.0.tgz", + "integrity": "sha512-Cqd4pabMLnpnMEa35MKOwXCKQ+5okHYWdvdFRA8x1HqI3AEcz4FNx48nXVH94vhbELv8+fWFAPfrr1v0rvjK6w==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-pagination": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-pagination/-/uui-pagination-1.8.0.tgz", + "integrity": "sha512-SvnWMzbQcTDEN+XQJ0/lVCJ1wL+2L7LA9pfSxJgXIj/pB55Pf3Tt3zMnW8B7RT7i/74atMufaqSSKElZrcPfHQ==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0", + "@umbraco-ui/uui-button": "1.8.0", + "@umbraco-ui/uui-button-group": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-popover": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-popover/-/uui-popover-1.8.0.tgz", + "integrity": "sha512-bHERyYItLAvtWbpOdPgmuPFgYs2TuKImm3h04DtQRatYP4dq8wg0tftVyZK3k9TVfLMvStOy2EyzybTD+1ZmBg==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-popover-container": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-popover-container/-/uui-popover-container-1.8.0.tgz", + "integrity": "sha512-FHaX4sIa7q4HTVzMr9w3Z6zuunPuDROnHjVS6QkovqHLEgQjeTQB8hGAxN6cd3QsFbgbtC9fzBo8zTn9yxJLmA==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-progress-bar": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-progress-bar/-/uui-progress-bar-1.8.0.tgz", + "integrity": "sha512-WX+YjiH0HCljtzTImR6Q8bf06wm+jcCgEOE10pMRri0r4nyBCAN7Zms23sHj6rR+S/oxoYcuREqdWXWP4eRfFA==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-radio": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-radio/-/uui-radio-1.8.0.tgz", + "integrity": "sha512-3kPWyc0ZbtAIeOHxAKudSslomg1zKy4RMgrfDeNumQhuCei0VWhVn76Xyfu/Gl2n4XnLIzZY3zv4XCaxwWvJZw==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-range-slider": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-range-slider/-/uui-range-slider-1.8.0.tgz", + "integrity": "sha512-ThDfLvxRMfP93HQobgLaglR860vQiwCM63PXp2igXWZl69ikDDa7HuraupEpmdL13VLKAv5L1BMue1ItC1x2MA==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-ref": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref/-/uui-ref-1.8.0.tgz", + "integrity": "sha512-mcw7faAXeG/2QfLeuYsB950seU0FQH5Pd+aSfi6zwgWkCasxKWLrlslVTP1U5zD5WFkNt4ls6RnMGZhN6bq7mQ==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-ref-list": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-list/-/uui-ref-list-1.8.0.tgz", + "integrity": "sha512-9iiTYVyw+dpO1gEB2oMJSe2DOVvA3a2uY5FLwkRgpAjvzbhD9hhyGLcVgtvM1rxUYc9SvJbGJXk2tPY4Nit3pA==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-ref-node": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node/-/uui-ref-node-1.8.0.tgz", + "integrity": "sha512-KOGy10QhPUQFNFNPkmqro1YqFg5Y5b2N/DwkcYP/xyu4Kc1f5FuE+Uo5L41i2KdbDP6O+Tz5gsU6HpPvpEAc7Q==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0", + "@umbraco-ui/uui-icon": "1.8.0", + "@umbraco-ui/uui-ref": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-ref-node-data-type": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-data-type/-/uui-ref-node-data-type-1.8.0.tgz", + "integrity": "sha512-x7PicpHE7PPZrVDojKO5ornG7HZoRB9/pjCZWV+8wxPznyGmFvcJpbRQLdIVvvXVkL1a0c4uLY2CsUO+K52Rbg==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0", + "@umbraco-ui/uui-ref-node": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-ref-node-document-type": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-document-type/-/uui-ref-node-document-type-1.8.0.tgz", + "integrity": "sha512-QjeC8DvgA/WFb3wOUXMOALK5SoKeDAvYqNp1wNvU5AMxJY+5CTfi6JNWKZsI4K+Mh3lfzPP+HaWx5MTwt5epNQ==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0", + "@umbraco-ui/uui-ref-node": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-ref-node-form": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-form/-/uui-ref-node-form-1.8.0.tgz", + "integrity": "sha512-sm0xSru6pv8yK9qepzF5Wzzd1jarMBZFtzgqw5H65pGTP6pNhNG4GIkeXLfd2TH9g3e6biJNKAOFJ5w8Tz4O9A==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0", + "@umbraco-ui/uui-ref-node": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-ref-node-member": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-member/-/uui-ref-node-member-1.8.0.tgz", + "integrity": "sha512-z9l44zCER4KmNMSnCy6BFxXZ6awr/L405pJJJzqb3GAXsBip47pCzhYgxCbekB+xSY+E0hS1EuG1JJ5wn05yiQ==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0", + "@umbraco-ui/uui-ref-node": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-ref-node-package": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-package/-/uui-ref-node-package-1.8.0.tgz", + "integrity": "sha512-bpWVBOm39tl5jY3Y+qn5dnikmnaAOrHEg2SBf/11d7aHOauEgDiJZQmM9BzjAeionaZrj4beYJw5szazaVkpDQ==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0", + "@umbraco-ui/uui-ref-node": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-ref-node-user": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-user/-/uui-ref-node-user-1.8.0.tgz", + "integrity": "sha512-Z91Me56phGFyt/hJaRDnXrorlYVjrL6WaUWRDZAi+mqqThFriG1DVeaFEewZ1yeD3Hy6haHanDa7dtwm2FLsBQ==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0", + "@umbraco-ui/uui-ref-node": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-scroll-container": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-scroll-container/-/uui-scroll-container-1.8.0.tgz", + "integrity": "sha512-PHxOZhsM53J3SQaXhH0euUwshr6Ka06iFEmtNKaqVe+nPwTjYHWHDuulvp7/qm/btjzSIrJqKgs02ft8wGqXwA==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-select": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-select/-/uui-select-1.8.0.tgz", + "integrity": "sha512-xFXo7IlAtofZCWIqnhyThIjP8ORRwo6786fv5yarayhqlKeAwJRD5t6QtX4z5z/1b/zW11oF2GkJzrzp4KN4vw==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-slider": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-slider/-/uui-slider-1.8.0.tgz", + "integrity": "sha512-qKgWyVzMKF8RVwwgpdMvKfCS3TEyMbZj/ZKClgTJJfs+3r8Q002F7irx7Lgh+mDww+jLsuMtG/cu0xSXU4HC4w==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-symbol-expand": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-expand/-/uui-symbol-expand-1.8.0.tgz", + "integrity": "sha512-AfpoR4dTOL4gEfP9lnEVymU3mlNfjFSuk8TGbqy0jVMTMbYeol5Bcl6jJFqqPd1npfgT7FPZH9zrMkcFogfSSw==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-symbol-file": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-file/-/uui-symbol-file-1.8.0.tgz", + "integrity": "sha512-/2O0TNl+Sx/cCy2kQlSCOujvRwz+Rxg9JxyMX5Vc14ZqrVJ4FsD2S/jJWLtE2YJ+EtLoc15Zzw2GogZO7aBcLQ==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-symbol-file-dropzone": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-file-dropzone/-/uui-symbol-file-dropzone-1.8.0.tgz", + "integrity": "sha512-n2QRKTEnvQQgiyTQ7uVbz7XsGL0HRwaEtLqEMbaON6oYCsGWFFsbp8QqyHdB8iBQSrlV9I1J4sS0e5Ry+W25YQ==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-symbol-file-thumbnail": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-file-thumbnail/-/uui-symbol-file-thumbnail-1.8.0.tgz", + "integrity": "sha512-KdFOrfVIwtjavoa+S5ro1gi2Er8IPqXnY6gpTRpAgfO/f+/ZRg6AwPKn4SCc7QqJ8ThHpsg8wki8WGiK04rfbA==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-symbol-folder": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-folder/-/uui-symbol-folder-1.8.0.tgz", + "integrity": "sha512-g7FIonq/5wHH2+e/+DhB+t3E4wu7dM4MrKxLsP6b8JmYz7Y0t9OlTBT5J+i3P8YnJKYY6i5V5Eip4QNWJqC+sQ==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-symbol-lock": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-lock/-/uui-symbol-lock-1.8.0.tgz", + "integrity": "sha512-+/K59hTkBJr6bYOrTgPtvZEVsr59MPNwvIHhGm685WZPZrNA9dGPZrO9vnw1eyNq70XYuHkiNkmKUmna9mQmIA==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-symbol-more": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-more/-/uui-symbol-more-1.8.0.tgz", + "integrity": "sha512-BSWQ05XYJSS6WKpA6//QnSnMehV5py5j8bxl7bZzmrthr2SwyejwS+pGYq7rTqWw7BBk1mA8I7Zkl+kVph/7+g==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-symbol-sort": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-sort/-/uui-symbol-sort-1.8.0.tgz", + "integrity": "sha512-+IFJYlPsUvJYNDc8sWB4zan/dTCCj4vkqwXALW3xLSxpsKSvtSvXPzXK/i4YwaT4Azx4hfrWIW2cz6/h5JDonA==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-table": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-table/-/uui-table-1.8.0.tgz", + "integrity": "sha512-nbRoValRn17SadfpGKKT1RfyoRlCLhvij2BRMw1KyldztWlWGozPQSDjqhcEPSzJZCrNV76nIMbg5FLfsTl4iA==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-tabs": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-tabs/-/uui-tabs-1.8.0.tgz", + "integrity": "sha512-xnZvjjmRHJbyC9bd6LMYfHA8Jjb51GTJuFAd4j4S9NVZYomQDBFl7IKVWtUFzQvVzk93zJHVSWO8vmtNLQZreg==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0", + "@umbraco-ui/uui-button": "1.8.0", + "@umbraco-ui/uui-popover-container": "1.8.0", + "@umbraco-ui/uui-symbol-more": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-tag": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-tag/-/uui-tag-1.8.0.tgz", + "integrity": "sha512-5Dt7EsvACfs75bsncIDvqitXYub2Rfntbrc3gzXLHjqOQy2YBL5s/HNGz3xsf5msKuDAR0dmTyxhItaDAi7EkQ==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-textarea": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-textarea/-/uui-textarea-1.8.0.tgz", + "integrity": "sha512-WYWiD3x1DXbsAALmTT2txoeeqiZ9J/FlkTGL1Wasu89jfm8bAgxUG5wuoa8SL4r79rAF+RUDrJPygeUqDm0N8A==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-toast-notification": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-toast-notification/-/uui-toast-notification-1.8.0.tgz", + "integrity": "sha512-62q36/jggXp+GdRSzseQ7d63hUIXtHIXe/5bnSSHXcxxIcnbf9Sy3pkBkOYM7CXgBUUwt9T9IHLZ6eGw/6K9Xw==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0", + "@umbraco-ui/uui-button": "1.8.0", + "@umbraco-ui/uui-css": "1.8.0", + "@umbraco-ui/uui-icon": "1.8.0", + "@umbraco-ui/uui-icon-registry-essential": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-toast-notification-container": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-toast-notification-container/-/uui-toast-notification-container-1.8.0.tgz", + "integrity": "sha512-NdILHgGvKFF+JZGejTJnXt1LdJIl/MxmPUj6+rvEGCO2SDhZmIOHjnIohT8HFytxmJqyWJpryPQjo6KJezRVbQ==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0", + "@umbraco-ui/uui-toast-notification": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-toast-notification-layout": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-toast-notification-layout/-/uui-toast-notification-layout-1.8.0.tgz", + "integrity": "sha512-NyGFv3kAcU8XMxLAyDhy3dt1oIHOwbAYO5+Utm4CFAAvcJzVTNn948Sp0dTdcfSjXjZG+3Ufv/Qb/OpcrybJ/w==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0", + "@umbraco-ui/uui-css": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-toggle": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-toggle/-/uui-toggle-1.8.0.tgz", + "integrity": "sha512-PNk2qeaL7bJXnSkG0T1J5pheNy7yTeaVbdyXuL/9fkoIdb9IkD/h6XLE9niiyWYF1xrdjpgAKZ32u0Oc4qXVyA==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0", + "@umbraco-ui/uui-boolean-input": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-visually-hidden": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-visually-hidden/-/uui-visually-hidden-1.8.0.tgz", + "integrity": "sha512-3a4/B2uM3YfXjI9dyfSL2Z47ziwW7HuYoozNderKO/I7l0CgxgoHIOwF1sRb3QgOlsFhhfeKdndvU7SbD6tazQ==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0" + } + }, "node_modules/ansi-escapes": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", @@ -914,6 +1860,27 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "peer": true + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -965,6 +1932,23 @@ "node": ">=12" } }, + "node_modules/cliui/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==", + "dev": true, + "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/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -983,6 +1967,13 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "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, + "peer": true + }, "node_modules/cookie": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", @@ -993,9 +1984,9 @@ } }, "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", + "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", "dev": true, "dependencies": { "ms": "2.1.2" @@ -1009,6 +2000,30 @@ } } }, + "node_modules/diff": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", + "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", + "dev": true, + "peer": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/dompurify": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.1.5.tgz", + "integrity": "sha512-lwG+n5h8QNpxtyrJW/gJWckL+1/DQiYMX8f7t8Z2AZTPw1esVrqjI63i7Zc2Gz0aKzLVMYC1V1PL/ky+aY/NgA==", + "dev": true, + "peer": true + }, + "node_modules/element-internals-polyfill": { + "version": "1.3.11", + "resolved": "https://registry.npmjs.org/element-internals-polyfill/-/element-internals-polyfill-1.3.11.tgz", + "integrity": "sha512-SQLQNVY4wMdpnP/F/HtalJbpEenQd46Avtjm5hvUdeTs3QU0zHFNX5/AmtQIPPcfzePb0ipCkQGY4GwYJIhLJA==", + "dev": true, + "peer": true + }, "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -1016,9 +2031,9 @@ "dev": true }, "node_modules/esbuild": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz", - "integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", "dev": true, "hasInstallScript": true, "bin": { @@ -1028,64 +2043,40 @@ "node": ">=12" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.20.2", - "@esbuild/android-arm": "0.20.2", - "@esbuild/android-arm64": "0.20.2", - "@esbuild/android-x64": "0.20.2", - "@esbuild/darwin-arm64": "0.20.2", - "@esbuild/darwin-x64": "0.20.2", - "@esbuild/freebsd-arm64": "0.20.2", - "@esbuild/freebsd-x64": "0.20.2", - "@esbuild/linux-arm": "0.20.2", - "@esbuild/linux-arm64": "0.20.2", - "@esbuild/linux-ia32": "0.20.2", - "@esbuild/linux-loong64": "0.20.2", - "@esbuild/linux-mips64el": "0.20.2", - "@esbuild/linux-ppc64": "0.20.2", - "@esbuild/linux-riscv64": "0.20.2", - "@esbuild/linux-s390x": "0.20.2", - "@esbuild/linux-x64": "0.20.2", - "@esbuild/netbsd-x64": "0.20.2", - "@esbuild/openbsd-x64": "0.20.2", - "@esbuild/sunos-x64": "0.20.2", - "@esbuild/win32-arm64": "0.20.2", - "@esbuild/win32-ia32": "0.20.2", - "@esbuild/win32-x64": "0.20.2" + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" } }, "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", + "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", "dev": true, "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==", - "dev": true, - "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==", - "dev": true, - "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", @@ -1116,9 +2107,9 @@ "dev": true }, "node_modules/graphql": { - "version": "16.8.1", - "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.8.1.tgz", - "integrity": "sha512-59LZHPdGZVh695Ud9lRzPBVTtlX9ZCV150Er2W43ro37wVof0ctenSaskPPjN7lVTIN8mSZt8PHUNKZuNQUuxw==", + "version": "16.9.0", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.9.0.tgz", + "integrity": "sha512-GGTKBX4SD7Wdb8mqeDLni2oaRGYQWjWHGKPQ24ZMnUtKfcsVoiv4uX8+LJr1K6U5VW2Lu1BwJnj7uiori0YtRw==", "dev": true, "engines": { "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" @@ -1134,9 +2125,9 @@ } }, "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==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/headers-polyfill/-/headers-polyfill-4.0.3.tgz", + "integrity": "sha512-IScLbePpkvO846sIwOtOTDjutRMWdXdJmXdMvk6gCBHxFO8d+QKOQedyZSxFTTFYRSmlgSTDtXqqq4pcenBXLQ==", "dev": true }, "node_modules/is-fullwidth-code-point": { @@ -1155,9 +2146,9 @@ "dev": true }, "node_modules/lit": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/lit/-/lit-3.1.3.tgz", - "integrity": "sha512-l4slfspEsnCcHVRTvaP7YnkTZEZggNFywLEIhQaGhYDczG+tu/vlgm/KaWIEjIp+ZyV20r2JnZctMb8LeLCG7Q==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/lit/-/lit-3.1.4.tgz", + "integrity": "sha512-q6qKnKXHy2g1kjBaNfcoLlgbI3+aSOZ9Q4tiGa9bGYXq5RBXxkVTqTIVmP2VWMp29L4GyvCFm8ZQ2o56eUAMyA==", "dev": true, "peer": true, "dependencies": { @@ -1167,9 +2158,9 @@ } }, "node_modules/lit-element": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/lit-element/-/lit-element-4.0.5.tgz", - "integrity": "sha512-iTWskWZEtn9SyEf4aBG6rKT8GABZMrTWop1+jopsEOgEcugcXJGKuX5bEbkq9qfzY+XB4MAgCaSPwnNpdsNQ3Q==", + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lit-element/-/lit-element-4.0.6.tgz", + "integrity": "sha512-U4sdJ3CSQip7sLGZ/uJskO5hGiqtlpxndsLr6mt3IQIjheg93UKYeGQjWMRql1s/cXNOaRrCzC2FQwjIwSUqkg==", "dev": true, "peer": true, "dependencies": { @@ -1179,15 +2170,35 @@ } }, "node_modules/lit-html": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-3.1.3.tgz", - "integrity": "sha512-FwIbqDD8O/8lM4vUZ4KvQZjPPNx7V1VhT7vmRB8RBAO0AU6wuTVdoXiu2CivVjEGdugvcbPNBLtPE1y0ifplHA==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-3.1.4.tgz", + "integrity": "sha512-yKKO2uVv7zYFHlWMfZmqc+4hkmSbFp8jgjdZY9vvR9jr4J8fH6FUMXhr+ljfELgmjpvlF7Z1SJ5n5/Jeqtc9YA==", "dev": true, "peer": true, "dependencies": { "@types/trusted-types": "^2.0.2" } }, + "node_modules/marked": { + "version": "12.0.2", + "resolved": "https://registry.npmjs.org/marked/-/marked-12.0.2.tgz", + "integrity": "sha512-qXUm7e/YKFoqFPYPa3Ukg9xlI5cyAtGmyEIzMfW//m6kXwCy2Ps9DYf5ioijFKQ8qyuscrHoY04iJGctu2Kg0Q==", + "dev": true, + "peer": true, + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/monaco-editor": { + "version": "0.48.0", + "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.48.0.tgz", + "integrity": "sha512-goSDElNqFfw7iDHMg8WDATkfcyeLTNpBHQpO8incK6p5qZt5G/1j41X0xdGzpIkGojGXM+QiRQyLjnfDVvrpwA==", + "dev": true, + "peer": true + }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -1195,9 +2206,9 @@ "dev": true }, "node_modules/msw": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/msw/-/msw-2.3.0.tgz", - "integrity": "sha512-cDr1q/QTMzaWhY8n9lpGhceY209k29UZtdTgJ3P8Bzne3TSMchX2EM/ldvn4ATLOktpCefCU2gcEgzHc31GTPw==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/msw/-/msw-2.3.1.tgz", + "integrity": "sha512-ocgvBCLn/5l3jpl1lssIb3cniuACJLoOfZu01e3n5dbJrpA5PeeWn28jCLgQDNt6d7QT8tF2fYRzm9JoEHtiig==", "dev": true, "hasInstallScript": true, "dependencies": { @@ -1271,9 +2282,9 @@ "dev": true }, "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==", + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.2.tgz", + "integrity": "sha512-GQX3SSMokngb36+whdpRXE+3f9V8UzyAorlYvOGx87ufGHehNTn5lCxrKtLyZ4Yl/wEKnNnr98ZzOwwDZV5ogw==", "dev": true }, "node_modules/picocolors": { @@ -1320,9 +2331,9 @@ } }, "node_modules/rollup": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.13.0.tgz", - "integrity": "sha512-3YegKemjoQnYKmsBlOHfMLVPPA5xLkQ8MHLLSw/fBrFaVkEayL51DilPpNNLq1exr98F2B1TzrV0FUlN3gWRPg==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.18.0.tgz", + "integrity": "sha512-QmJz14PX3rzbJCN1SG4Xe/bAAX2a6NpCP8ab2vfu2GiUr8AQcr2nCV/oEO3yneFarB67zk8ShlIyWb2LGTb3Sg==", "dev": true, "dependencies": { "@types/estree": "1.0.5" @@ -1335,29 +2346,33 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.13.0", - "@rollup/rollup-android-arm64": "4.13.0", - "@rollup/rollup-darwin-arm64": "4.13.0", - "@rollup/rollup-darwin-x64": "4.13.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.13.0", - "@rollup/rollup-linux-arm64-gnu": "4.13.0", - "@rollup/rollup-linux-arm64-musl": "4.13.0", - "@rollup/rollup-linux-riscv64-gnu": "4.13.0", - "@rollup/rollup-linux-x64-gnu": "4.13.0", - "@rollup/rollup-linux-x64-musl": "4.13.0", - "@rollup/rollup-win32-arm64-msvc": "4.13.0", - "@rollup/rollup-win32-ia32-msvc": "4.13.0", - "@rollup/rollup-win32-x64-msvc": "4.13.0", + "@rollup/rollup-android-arm-eabi": "4.18.0", + "@rollup/rollup-android-arm64": "4.18.0", + "@rollup/rollup-darwin-arm64": "4.18.0", + "@rollup/rollup-darwin-x64": "4.18.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.18.0", + "@rollup/rollup-linux-arm-musleabihf": "4.18.0", + "@rollup/rollup-linux-arm64-gnu": "4.18.0", + "@rollup/rollup-linux-arm64-musl": "4.18.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.18.0", + "@rollup/rollup-linux-riscv64-gnu": "4.18.0", + "@rollup/rollup-linux-s390x-gnu": "4.18.0", + "@rollup/rollup-linux-x64-gnu": "4.18.0", + "@rollup/rollup-linux-x64-musl": "4.18.0", + "@rollup/rollup-win32-arm64-msvc": "4.18.0", + "@rollup/rollup-win32-ia32-msvc": "4.18.0", + "@rollup/rollup-win32-x64-msvc": "4.18.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==", + "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==", "dev": true, - "engines": { - "node": ">=0.12.0" + "peer": true, + "dependencies": { + "tslib": "^2.1.0" } }, "node_modules/signal-exit": { @@ -1434,10 +2449,24 @@ "node": ">=8" } }, + "node_modules/tinymce": { + "version": "6.8.4", + "resolved": "https://registry.npmjs.org/tinymce/-/tinymce-6.8.4.tgz", + "integrity": "sha512-okoJyxuPv1gzASxQDNgQbnUXOdAIyoOSXcXcZZu7tiW0PSKEdf3SdASxPBupRj+64/E3elHwVRnzSdo82Emqbg==", + "dev": true, + "peer": true + }, + "node_modules/tinymce-i18n": { + "version": "24.6.24", + "resolved": "https://registry.npmjs.org/tinymce-i18n/-/tinymce-i18n-24.6.24.tgz", + "integrity": "sha512-FToQhgKzZLqEg+twKVjUcS8gPJbZprOtiyGhGHhxGZMeqJITgbD0imc2QV7cZ82cZbHTBOkK2aOvgmbhk5uaTw==", + "dev": true, + "peer": true + }, "node_modules/tsconfck": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/tsconfck/-/tsconfck-3.0.3.tgz", - "integrity": "sha512-4t0noZX9t6GcPTfBAbIbbIU4pfpCwh0ueq3S4O/5qXI1VwK1outmxhe9dOiEWqMz3MW2LKgDTpqWV+37IWuVbA==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/tsconfck/-/tsconfck-3.1.0.tgz", + "integrity": "sha512-CMjc5zMnyAjcS9sPLytrbFmj89st2g+JYtY/c02ug4Q+CZaAtCgbyviI0n1YvjZE/pzoc6FbNsINS13DOL1B9w==", "dev": true, "bin": { "tsconfck": "bin/tsconfck.js" @@ -1454,10 +2483,17 @@ } } }, + "node_modules/tslib": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "dev": true, + "peer": true + }, "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==", + "version": "4.20.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.20.1.tgz", + "integrity": "sha512-R6wDsVsoS9xYOpy8vgeBlqpdOyzJ12HNfQhC/aAKWM3YoCV9TtunJzh/QpkMgeDhkoynDcw5f1y+qF9yc/HHyg==", "dev": true, "engines": { "node": ">=16" @@ -1467,9 +2503,9 @@ } }, "node_modules/typescript": { - "version": "5.4.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", - "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.2.tgz", + "integrity": "sha512-NcRtPEOsPFFWjobJEtfihkLCZCXZt/os3zf8nTxjVH3RvTSxjrCamJpbExGvYOF+tFHc3pA65qpdwPbzjohhew==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -1485,13 +2521,27 @@ "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", "dev": true }, + "node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "dev": true, + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "peer": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/vite": { - "version": "5.2.11", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.11.tgz", - "integrity": "sha512-HndV31LWW05i1BLPMUCE1B9E9GFbOu1MbenhS58FuK6owSO5qHm7GiCotrNY1YE5rMeQSFBGmT5ZaLEjFizgiQ==", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.3.1.tgz", + "integrity": "sha512-XBmSKRLXLxiaPYamLv3/hnP/KXDai1NDexN0FpkTaZXTfycHvkRHoenpgl/fvuK/kPbB6xAgoyiryAhQNxYmAQ==", "dev": true, "dependencies": { - "esbuild": "^0.20.1", + "esbuild": "^0.21.3", "postcss": "^8.4.38", "rollup": "^4.13.0" }, @@ -1560,9 +2610,9 @@ } }, "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==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", "dev": true, "dependencies": { "ansi-styles": "^4.0.0", @@ -1570,10 +2620,7 @@ "strip-ansi": "^6.0.0" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + "node": ">=8" } }, "node_modules/y18n": { diff --git a/src/Umbraco.Web.UI.Login/package.json b/src/Umbraco.Web.UI.Login/package.json index 05aaf0a018..7b0e3455c4 100644 --- a/src/Umbraco.Web.UI.Login/package.json +++ b/src/Umbraco.Web.UI.Login/package.json @@ -15,8 +15,8 @@ "dependencies": { }, "devDependencies": { - "@umbraco-cms/backoffice": "file:../Umbraco.Web.UI.Client", - "@umbraco-ui/uui-css": "^1.8.0-rc.0", + "@umbraco-cms/backoffice": "^14.0.0", + "@umbraco-ui/uui-css": "^1.8.0", "msw": "^2.3.0", "typescript": "^5.4.5", "vite": "^5.2.11", diff --git a/src/Umbraco.Web.UI.Login/public/mockServiceWorker.js b/src/Umbraco.Web.UI.Login/public/mockServiceWorker.js index bdfdb11d38..24fe3a25f0 100644 --- a/src/Umbraco.Web.UI.Login/public/mockServiceWorker.js +++ b/src/Umbraco.Web.UI.Login/public/mockServiceWorker.js @@ -8,7 +8,7 @@ * - Please do NOT serve this file on production. */ -const PACKAGE_VERSION = '2.3.0' +const PACKAGE_VERSION = '2.3.1' const INTEGRITY_CHECKSUM = '26357c79639bfa20d64c0efca2a87423' const IS_MOCKED_RESPONSE = Symbol('isMockedResponse') const activeClientIds = new Set() From 1774d4e234b4a6e0919a1f8260bc6fade2d975a2 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Wed, 26 Jun 2024 13:32:28 +0200 Subject: [PATCH 067/113] update backoffice submodule --- src/Umbraco.Web.UI.Client | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client b/src/Umbraco.Web.UI.Client index 9077e80b32..cd798c8858 160000 --- a/src/Umbraco.Web.UI.Client +++ b/src/Umbraco.Web.UI.Client @@ -1 +1 @@ -Subproject commit 9077e80b3298c3ef9ca491fca7a33cc662ea6f5b +Subproject commit cd798c8858b32b15410cd16ab69b5625b17f750e From 195d8b584332ec721c96e970fa168733e97afa71 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Wed, 26 Jun 2024 13:33:07 +0200 Subject: [PATCH 068/113] backoffice submodule release/14.1 --- src/Umbraco.Web.UI.Client | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client b/src/Umbraco.Web.UI.Client index 9077e80b32..4022d30f35 160000 --- a/src/Umbraco.Web.UI.Client +++ b/src/Umbraco.Web.UI.Client @@ -1 +1 @@ -Subproject commit 9077e80b3298c3ef9ca491fca7a33cc662ea6f5b +Subproject commit 4022d30f35bd85e4bc201f2ef02b145be9a9a601 From ed220035efddf8bd320fe81f53689a04ea7401fa Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Wed, 26 Jun 2024 14:40:57 +0200 Subject: [PATCH 069/113] backoffice submodule release/14.1 --- src/Umbraco.Web.UI.Client | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client b/src/Umbraco.Web.UI.Client index 4022d30f35..b316ce3b52 160000 --- a/src/Umbraco.Web.UI.Client +++ b/src/Umbraco.Web.UI.Client @@ -1 +1 @@ -Subproject commit 4022d30f35bd85e4bc201f2ef02b145be9a9a601 +Subproject commit b316ce3b5276d0b08d297df3c46a27d83ffabedd From d8b436193670855be7f95d554b5f891220c0bf6c Mon Sep 17 00:00:00 2001 From: Elitsa Marinovska <21998037+elit0451@users.noreply.github.com> Date: Thu, 27 Jun 2024 09:23:47 +0200 Subject: [PATCH 070/113] V13: Update url scheme for Twitter OEmbedProvider (#16650) * Adds url support for x.com/.*/status/.* * Updated regex * Obsoleting Twitter as OEmbedProvider * Introducing X as its new alternative * Adding X and removing Twitter from EmbedProvidersCollection * Typo --- .../UmbracoBuilder.Collections.cs | 4 +-- .../Media/EmbedProviders/Twitter.cs | 1 + src/Umbraco.Core/Media/EmbedProviders/X.cs | 28 +++++++++++++++++++ 3 files changed, 31 insertions(+), 2 deletions(-) create mode 100644 src/Umbraco.Core/Media/EmbedProviders/X.cs diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Collections.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Collections.cs index e008365709..88b23e7c93 100644 --- a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Collections.cs +++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Collections.cs @@ -126,7 +126,6 @@ public static partial class UmbracoBuilderExtensions // register OEmbed providers - no type scanning - all explicit opt-in of adding types, IEmbedProvider is not IDiscoverable builder.EmbedProviders() .Append() - .Append() .Append() .Append() .Append() @@ -138,7 +137,8 @@ public static partial class UmbracoBuilderExtensions .Append() .Append() .Append() - .Append(); + .Append() + .Append(); builder.SearchableTrees().Add(() => builder.TypeLoader.GetTypes()); builder.BackOfficeAssets(); builder.SelectorHandlers().Add(() => builder.TypeLoader.GetTypes()); diff --git a/src/Umbraco.Core/Media/EmbedProviders/Twitter.cs b/src/Umbraco.Core/Media/EmbedProviders/Twitter.cs index 81aeb36491..54dd03e8ab 100644 --- a/src/Umbraco.Core/Media/EmbedProviders/Twitter.cs +++ b/src/Umbraco.Core/Media/EmbedProviders/Twitter.cs @@ -5,6 +5,7 @@ namespace Umbraco.Cms.Core.Media.EmbedProviders; /// /// Embed Provider for Twitter the popular online service for microblogging and social networking. /// +[Obsolete("Please use X instead, scheduled for removal in v16")] public class Twitter : OEmbedProviderBase { public Twitter(IJsonSerializer jsonSerializer) diff --git a/src/Umbraco.Core/Media/EmbedProviders/X.cs b/src/Umbraco.Core/Media/EmbedProviders/X.cs new file mode 100644 index 0000000000..c4d102e941 --- /dev/null +++ b/src/Umbraco.Core/Media/EmbedProviders/X.cs @@ -0,0 +1,28 @@ +using Umbraco.Cms.Core.Serialization; + +namespace Umbraco.Cms.Core.Media.EmbedProviders; + +/// +/// Embed Provider for X the popular online service for microblogging and social networking. +/// +public class X : OEmbedProviderBase +{ + public X(IJsonSerializer jsonSerializer) + : base(jsonSerializer) + { + } + + public override string ApiEndpoint => "http://publish.twitter.com/oembed"; + + public override string[] UrlSchemeRegex => new[] { @"(https?:\/\/(www\.)?)(twitter|x)\.com\/.*\/status\/.*" }; + + public override Dictionary RequestParams => new(); + + public override string? GetMarkup(string url, int maxWidth = 0, int maxHeight = 0) + { + var requestUrl = base.GetEmbedProviderUrl(url, maxWidth, maxHeight); + OEmbedResponse? oembed = base.GetJsonResponse(requestUrl); + + return oembed?.GetHtml(); + } +} From 1ea65c1fb207df5819760853f68d92aa633d816c Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Thu, 27 Jun 2024 10:47:41 +0200 Subject: [PATCH 071/113] Use the configured backoffice url to initialize openiddict if it is available and just fallback to the one from the first request. (#16660) Fixes https://github.com/umbraco/Umbraco-CMS/issues/16179 --- ...ceAuthorizationInitializationMiddleware.cs | 37 ++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Cms.Api.Management/Middleware/BackOfficeAuthorizationInitializationMiddleware.cs b/src/Umbraco.Cms.Api.Management/Middleware/BackOfficeAuthorizationInitializationMiddleware.cs index c9179215df..4da821337d 100644 --- a/src/Umbraco.Cms.Api.Management/Middleware/BackOfficeAuthorizationInitializationMiddleware.cs +++ b/src/Umbraco.Cms.Api.Management/Middleware/BackOfficeAuthorizationInitializationMiddleware.cs @@ -1,10 +1,15 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Extensions; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.DependencyInjection; +using Umbraco.Cms.Core.Hosting; using Umbraco.Cms.Core.Routing; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Infrastructure.Security; +using Umbraco.Extensions; namespace Umbraco.Cms.Api.Management.Middleware; @@ -16,15 +21,40 @@ public class BackOfficeAuthorizationInitializationMiddleware : IMiddleware private readonly UmbracoRequestPaths _umbracoRequestPaths; private readonly IServiceProvider _serviceProvider; private readonly IRuntimeState _runtimeState; + private readonly IOptions _globalSettings; + private readonly IOptions _webRoutingSettings; + private readonly IHostingEnvironment _hostingEnvironment; + [Obsolete("Use the non-obsolete constructor. This will be removed in Umbraco 16.")] public BackOfficeAuthorizationInitializationMiddleware( UmbracoRequestPaths umbracoRequestPaths, IServiceProvider serviceProvider, IRuntimeState runtimeState) + : this( + umbracoRequestPaths, + serviceProvider, + runtimeState, + StaticServiceProvider.Instance.GetRequiredService>(), + StaticServiceProvider.Instance.GetRequiredService>(), + StaticServiceProvider.Instance.GetRequiredService() + ) + { + } + + public BackOfficeAuthorizationInitializationMiddleware( + UmbracoRequestPaths umbracoRequestPaths, + IServiceProvider serviceProvider, + IRuntimeState runtimeState, + IOptions globalSettings, + IOptions webRoutingSettings, + IHostingEnvironment hostingEnvironment) { _umbracoRequestPaths = umbracoRequestPaths; _serviceProvider = serviceProvider; _runtimeState = runtimeState; + _globalSettings = globalSettings; + _webRoutingSettings = webRoutingSettings; + _hostingEnvironment = hostingEnvironment; } public async Task InvokeAsync(HttpContext context, RequestDelegate next) @@ -47,6 +77,7 @@ public class BackOfficeAuthorizationInitializationMiddleware : IMiddleware return; } + if (_umbracoRequestPaths.IsBackOfficeRequest(context.Request.Path) == false) { return; @@ -55,9 +86,13 @@ public class BackOfficeAuthorizationInitializationMiddleware : IMiddleware await _firstBackOfficeRequestLocker.WaitAsync(); if (_firstBackOfficeRequest == false) { + Uri? backOfficeUrl = string.IsNullOrWhiteSpace(_webRoutingSettings.Value.UmbracoApplicationUrl) is false + ? new Uri($"{_webRoutingSettings.Value.UmbracoApplicationUrl.TrimEnd('/')}{_globalSettings.Value.GetBackOfficePath(_hostingEnvironment).EnsureStartsWith('/')}") + : null; + using IServiceScope scope = _serviceProvider.CreateScope(); IBackOfficeApplicationManager backOfficeApplicationManager = scope.ServiceProvider.GetRequiredService(); - await backOfficeApplicationManager.EnsureBackOfficeApplicationAsync(new Uri(context.Request.GetDisplayUrl())); + await backOfficeApplicationManager.EnsureBackOfficeApplicationAsync(backOfficeUrl ?? new Uri(context.Request.GetDisplayUrl())); _firstBackOfficeRequest = true; } From 2fa4149b62ad23055ef4624369d4f2e8fa74ea53 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Thu, 27 Jun 2024 10:47:41 +0200 Subject: [PATCH 072/113] Use the configured backoffice url to initialize openiddict if it is available and just fallback to the one from the first request. (#16660) Fixes https://github.com/umbraco/Umbraco-CMS/issues/16179 --- ...ceAuthorizationInitializationMiddleware.cs | 37 ++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Cms.Api.Management/Middleware/BackOfficeAuthorizationInitializationMiddleware.cs b/src/Umbraco.Cms.Api.Management/Middleware/BackOfficeAuthorizationInitializationMiddleware.cs index c9179215df..4da821337d 100644 --- a/src/Umbraco.Cms.Api.Management/Middleware/BackOfficeAuthorizationInitializationMiddleware.cs +++ b/src/Umbraco.Cms.Api.Management/Middleware/BackOfficeAuthorizationInitializationMiddleware.cs @@ -1,10 +1,15 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Extensions; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.DependencyInjection; +using Umbraco.Cms.Core.Hosting; using Umbraco.Cms.Core.Routing; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Infrastructure.Security; +using Umbraco.Extensions; namespace Umbraco.Cms.Api.Management.Middleware; @@ -16,15 +21,40 @@ public class BackOfficeAuthorizationInitializationMiddleware : IMiddleware private readonly UmbracoRequestPaths _umbracoRequestPaths; private readonly IServiceProvider _serviceProvider; private readonly IRuntimeState _runtimeState; + private readonly IOptions _globalSettings; + private readonly IOptions _webRoutingSettings; + private readonly IHostingEnvironment _hostingEnvironment; + [Obsolete("Use the non-obsolete constructor. This will be removed in Umbraco 16.")] public BackOfficeAuthorizationInitializationMiddleware( UmbracoRequestPaths umbracoRequestPaths, IServiceProvider serviceProvider, IRuntimeState runtimeState) + : this( + umbracoRequestPaths, + serviceProvider, + runtimeState, + StaticServiceProvider.Instance.GetRequiredService>(), + StaticServiceProvider.Instance.GetRequiredService>(), + StaticServiceProvider.Instance.GetRequiredService() + ) + { + } + + public BackOfficeAuthorizationInitializationMiddleware( + UmbracoRequestPaths umbracoRequestPaths, + IServiceProvider serviceProvider, + IRuntimeState runtimeState, + IOptions globalSettings, + IOptions webRoutingSettings, + IHostingEnvironment hostingEnvironment) { _umbracoRequestPaths = umbracoRequestPaths; _serviceProvider = serviceProvider; _runtimeState = runtimeState; + _globalSettings = globalSettings; + _webRoutingSettings = webRoutingSettings; + _hostingEnvironment = hostingEnvironment; } public async Task InvokeAsync(HttpContext context, RequestDelegate next) @@ -47,6 +77,7 @@ public class BackOfficeAuthorizationInitializationMiddleware : IMiddleware return; } + if (_umbracoRequestPaths.IsBackOfficeRequest(context.Request.Path) == false) { return; @@ -55,9 +86,13 @@ public class BackOfficeAuthorizationInitializationMiddleware : IMiddleware await _firstBackOfficeRequestLocker.WaitAsync(); if (_firstBackOfficeRequest == false) { + Uri? backOfficeUrl = string.IsNullOrWhiteSpace(_webRoutingSettings.Value.UmbracoApplicationUrl) is false + ? new Uri($"{_webRoutingSettings.Value.UmbracoApplicationUrl.TrimEnd('/')}{_globalSettings.Value.GetBackOfficePath(_hostingEnvironment).EnsureStartsWith('/')}") + : null; + using IServiceScope scope = _serviceProvider.CreateScope(); IBackOfficeApplicationManager backOfficeApplicationManager = scope.ServiceProvider.GetRequiredService(); - await backOfficeApplicationManager.EnsureBackOfficeApplicationAsync(new Uri(context.Request.GetDisplayUrl())); + await backOfficeApplicationManager.EnsureBackOfficeApplicationAsync(backOfficeUrl ?? new Uri(context.Request.GetDisplayUrl())); _firstBackOfficeRequest = true; } From d170193a95a6da9f9e01e7d9831e4e8688f445be Mon Sep 17 00:00:00 2001 From: Sven Geusens Date: Fri, 28 Jun 2024 09:28:55 +0200 Subject: [PATCH 073/113] Fixed toolbar not showing for seeded,untouched and then migrated RTE datatypes (#16665) Moved 14.1 migrations into their own namespace Co-authored-by: Sven Geusens --- .../Migrations/Upgrade/UmbracoPlan.cs | 5 ++- .../MigrateOldRichTextSeedConfiguration.cs | 38 +++++++++++++++++++ .../MigrateRichTextConfiguration.cs | 2 +- 3 files changed, 43 insertions(+), 2 deletions(-) create mode 100644 src/Umbraco.Infrastructure/Migrations/Upgrade/V_14_1_0/MigrateOldRichTextSeedConfiguration.cs rename src/Umbraco.Infrastructure/Migrations/Upgrade/{V_14_0_0 => V_14_1_0}/MigrateRichTextConfiguration.cs (93%) diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs index 91ca3b4d58..8502e4c529 100644 --- a/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs +++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs @@ -84,6 +84,9 @@ public class UmbracoPlan : MigrationPlan // we need to re-run this migration, as it was flawed for V14 RC3 (the migration can run twice without any issues) To("{6FB5CA9E-C823-473B-A14C-FE760D75943C}"); To("{827360CA-0855-42A5-8F86-A51F168CB559}"); - To("{FEF2DAF4-5408-4636-BB0E-B8798DF8F095}"); + + // To 14.1.0 + To("{FEF2DAF4-5408-4636-BB0E-B8798DF8F095}"); + To("{A385C5DF-48DC-46B4-A742-D5BB846483BC}"); } } diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_14_1_0/MigrateOldRichTextSeedConfiguration.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_14_1_0/MigrateOldRichTextSeedConfiguration.cs new file mode 100644 index 0000000000..765fba03d1 --- /dev/null +++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_14_1_0/MigrateOldRichTextSeedConfiguration.cs @@ -0,0 +1,38 @@ +using NPoco; +using Umbraco.Cms.Core; +using Umbraco.Cms.Infrastructure.Persistence; +using Umbraco.Cms.Infrastructure.Persistence.Dtos; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_14_1_0; + +public class MigrateOldRichTextSeedConfiguration : MigrationBase +{ + private const string OldSeedValue = + "{\"value\":\",code,undo,redo,cut,copy,mcepasteword,stylepicker,bold,italic,bullist,numlist,outdent,indent,mcelink,unlink,mceinsertanchor,mceimage,umbracomacro,mceinserttable,umbracoembed,mcecharmap,|1|1,2,3,|0|500,400|1049,|true|\"}"; + + private const string NewDefaultValue = + "{\"toolbar\":[\"styles\",\"bold\",\"italic\",\"alignleft\",\"aligncenter\",\"alignright\",\"bullist\",\"numlist\",\"outdent\",\"indent\",\"sourcecode\",\"link\",\"umbmediapicker\",\"umbembeddialog\"],\"mode\":\"Classic\",\"maxImageSize\":500}"; + public MigrateOldRichTextSeedConfiguration(IMigrationContext context) : base(context) + { + } + + protected override void Migrate() + { + Sql sql = Sql() + .Select() + .From() + .Where(x => + x.EditorAlias.Equals(Constants.PropertyEditors.Aliases.RichText) + && x.Configuration == OldSeedValue); + + List dataTypeDtos = Database.Fetch(sql); + + foreach (DataTypeDto dataTypeDto in dataTypeDtos) + { + // Update the configuration + dataTypeDto.Configuration = NewDefaultValue; + Database.Update(dataTypeDto); + } + } +} diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_14_0_0/MigrateRichTextConfiguration.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_14_1_0/MigrateRichTextConfiguration.cs similarity index 93% rename from src/Umbraco.Infrastructure/Migrations/Upgrade/V_14_0_0/MigrateRichTextConfiguration.cs rename to src/Umbraco.Infrastructure/Migrations/Upgrade/V_14_1_0/MigrateRichTextConfiguration.cs index 950259f575..bca328eb3f 100644 --- a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_14_0_0/MigrateRichTextConfiguration.cs +++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_14_1_0/MigrateRichTextConfiguration.cs @@ -4,7 +4,7 @@ using Umbraco.Cms.Infrastructure.Persistence; using Umbraco.Cms.Infrastructure.Persistence.Dtos; using Umbraco.Extensions; -namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_14_0_0; +namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_14_1_0; public class MigrateRichTextConfiguration : MigrationBase { From 921598b6677867f468bc9296cb44754399f20184 Mon Sep 17 00:00:00 2001 From: Sven Geusens Date: Fri, 28 Jun 2024 09:28:55 +0200 Subject: [PATCH 074/113] Fixed toolbar not showing for seeded,untouched and then migrated RTE datatypes (#16665) Moved 14.1 migrations into their own namespace Co-authored-by: Sven Geusens (cherry picked from commit d170193a95a6da9f9e01e7d9831e4e8688f445be) --- .../Migrations/Upgrade/UmbracoPlan.cs | 5 ++- .../MigrateOldRichTextSeedConfiguration.cs | 38 +++++++++++++++++++ .../MigrateRichTextConfiguration.cs | 2 +- 3 files changed, 43 insertions(+), 2 deletions(-) create mode 100644 src/Umbraco.Infrastructure/Migrations/Upgrade/V_14_1_0/MigrateOldRichTextSeedConfiguration.cs rename src/Umbraco.Infrastructure/Migrations/Upgrade/{V_14_0_0 => V_14_1_0}/MigrateRichTextConfiguration.cs (93%) diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs index 91ca3b4d58..8502e4c529 100644 --- a/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs +++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs @@ -84,6 +84,9 @@ public class UmbracoPlan : MigrationPlan // we need to re-run this migration, as it was flawed for V14 RC3 (the migration can run twice without any issues) To("{6FB5CA9E-C823-473B-A14C-FE760D75943C}"); To("{827360CA-0855-42A5-8F86-A51F168CB559}"); - To("{FEF2DAF4-5408-4636-BB0E-B8798DF8F095}"); + + // To 14.1.0 + To("{FEF2DAF4-5408-4636-BB0E-B8798DF8F095}"); + To("{A385C5DF-48DC-46B4-A742-D5BB846483BC}"); } } diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_14_1_0/MigrateOldRichTextSeedConfiguration.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_14_1_0/MigrateOldRichTextSeedConfiguration.cs new file mode 100644 index 0000000000..765fba03d1 --- /dev/null +++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_14_1_0/MigrateOldRichTextSeedConfiguration.cs @@ -0,0 +1,38 @@ +using NPoco; +using Umbraco.Cms.Core; +using Umbraco.Cms.Infrastructure.Persistence; +using Umbraco.Cms.Infrastructure.Persistence.Dtos; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_14_1_0; + +public class MigrateOldRichTextSeedConfiguration : MigrationBase +{ + private const string OldSeedValue = + "{\"value\":\",code,undo,redo,cut,copy,mcepasteword,stylepicker,bold,italic,bullist,numlist,outdent,indent,mcelink,unlink,mceinsertanchor,mceimage,umbracomacro,mceinserttable,umbracoembed,mcecharmap,|1|1,2,3,|0|500,400|1049,|true|\"}"; + + private const string NewDefaultValue = + "{\"toolbar\":[\"styles\",\"bold\",\"italic\",\"alignleft\",\"aligncenter\",\"alignright\",\"bullist\",\"numlist\",\"outdent\",\"indent\",\"sourcecode\",\"link\",\"umbmediapicker\",\"umbembeddialog\"],\"mode\":\"Classic\",\"maxImageSize\":500}"; + public MigrateOldRichTextSeedConfiguration(IMigrationContext context) : base(context) + { + } + + protected override void Migrate() + { + Sql sql = Sql() + .Select() + .From() + .Where(x => + x.EditorAlias.Equals(Constants.PropertyEditors.Aliases.RichText) + && x.Configuration == OldSeedValue); + + List dataTypeDtos = Database.Fetch(sql); + + foreach (DataTypeDto dataTypeDto in dataTypeDtos) + { + // Update the configuration + dataTypeDto.Configuration = NewDefaultValue; + Database.Update(dataTypeDto); + } + } +} diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_14_0_0/MigrateRichTextConfiguration.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_14_1_0/MigrateRichTextConfiguration.cs similarity index 93% rename from src/Umbraco.Infrastructure/Migrations/Upgrade/V_14_0_0/MigrateRichTextConfiguration.cs rename to src/Umbraco.Infrastructure/Migrations/Upgrade/V_14_1_0/MigrateRichTextConfiguration.cs index 950259f575..bca328eb3f 100644 --- a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_14_0_0/MigrateRichTextConfiguration.cs +++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_14_1_0/MigrateRichTextConfiguration.cs @@ -4,7 +4,7 @@ using Umbraco.Cms.Infrastructure.Persistence; using Umbraco.Cms.Infrastructure.Persistence.Dtos; using Umbraco.Extensions; -namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_14_0_0; +namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_14_1_0; public class MigrateRichTextConfiguration : MigrationBase { From 2010a241fe33da55c4279ad0f9a39836391813f3 Mon Sep 17 00:00:00 2001 From: Zeegaan Date: Fri, 28 Jun 2024 09:30:06 +0200 Subject: [PATCH 075/113] Bumb version --- version.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.json b/version.json index cf30f7b7d2..31fd8e32c7 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": "14.1.0-rc", + "version": "14.1.0-rc2", "assemblyVersion": { "precision": "build" }, From 2185be1b0323f4f0b6de5ad4f1c15a92212fabd3 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Fri, 28 Jun 2024 15:26:28 +0200 Subject: [PATCH 076/113] update backoffice submodule --- src/Umbraco.Web.UI.Client | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client b/src/Umbraco.Web.UI.Client index b316ce3b52..144378a4cb 160000 --- a/src/Umbraco.Web.UI.Client +++ b/src/Umbraco.Web.UI.Client @@ -1 +1 @@ -Subproject commit b316ce3b5276d0b08d297df3c46a27d83ffabedd +Subproject commit 144378a4cb16be8499b38db8e217b1d24050db75 From 2122af7e9600ad8d32f7d9f48989b8b669e342b6 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Mon, 1 Jul 2024 12:23:58 +0200 Subject: [PATCH 077/113] V14: Build and release the type declarations of the Backoffice to npm (#16718) * separate backoffice install into its own template * add new job to build the backoffice npm package * move location of templates * set version, then build, then pack * use nbgv to get just the NpmPackageVersion * calculate version earlier and for both builds * merge two tasks * add Build keyword to the display name * get the node version from the backoffice * update backoffice submodule * bump npm version before restoring cache to ensure the cache key doesn't change * add template to umbraco.sln --- build/azure-pipelines.yml | 64 +++++++++----------------- build/templates/backoffice-install.yml | 31 +++++++++++++ src/Umbraco.Web.UI.Client | 2 +- umbraco.sln | 1 + 4 files changed, 54 insertions(+), 44 deletions(-) create mode 100644 build/templates/backoffice-install.yml diff --git a/build/azure-pipelines.yml b/build/azure-pipelines.yml index aa7e1845f7..e794cb844a 100644 --- a/build/azure-pipelines.yml +++ b/build/azure-pipelines.yml @@ -63,15 +63,14 @@ variables: DOTNET_GENERATE_ASPNET_CERTIFICATE: false DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true DOTNET_CLI_TELEMETRY_OPTOUT: true + npm_config_cache: $(Pipeline.Workspace)/.npm_client + NODE_OPTIONS: --max_old_space_size=16384 stages: ############################################### ## Build ############################################### - stage: Build - variables: - npm_config_cache: $(Pipeline.Workspace)/.npm_client - NODE_OPTIONS: --max_old_space_size=16384 jobs: - job: A displayName: Build Umbraco CMS @@ -80,18 +79,11 @@ stages: steps: - checkout: self submodules: true - - task: NodeTool@0 - displayName: Use Node.js $(nodeVersion) - retryCountOnTaskFailure: 3 + - task: UseDotNet@2 + displayName: Use .NET SDK from global.json inputs: - versionSpec: $(nodeVersion) - - script: npm ci --no-fund --no-audit --prefer-offline - displayName: Run npm ci (Bellissima) - workingDirectory: src/Umbraco.Web.UI.Client - - script: npm run generate:api-local - displayName: Generate API models (Bellissima) - workingDirectory: src/Umbraco.Web.UI.Client - enabled: false + useGlobalJson: true + - template: templates/backoffice-install.yml - script: npm run build:for:cms displayName: Run build (Bellissima) workingDirectory: src/Umbraco.Web.UI.Client @@ -101,10 +93,6 @@ stages: - script: npm run build displayName: Run npm build (Login) workingDirectory: src/Umbraco.Web.UI.Login - - task: UseDotNet@2 - displayName: Use .NET SDK from global.json - inputs: - useGlobalJson: true - task: DotNetCoreCLI@2 displayName: Run dotnet restore inputs: @@ -127,18 +115,25 @@ stages: inputs: targetPath: $(Build.SourcesDirectory) artifactName: build_output + + - job: B + displayName: Build Bellissima Package + pool: + vmImage: 'ubuntu-latest' + steps: + - checkout: self + submodules: true + - template: templates/backoffice-install.yml + - script: npm run build:for:npm + displayName: Run build:for:npm + workingDirectory: src/Umbraco.Web.UI.Client - bash: | - echo "##[command]Running npm version" - echo "##[debug]Version: $PACKAGE_VERSION" echo "##[command]Running npm pack" echo "##[debug]Output directory: $(Build.ArtifactStagingDirectory)" - npm version $PACKAGE_VERSION --allow-same-version --no-git-tag-version mkdir $(Build.ArtifactStagingDirectory)/npm npm pack --pack-destination $(Build.ArtifactStagingDirectory)/npm mv .npmrc $(Build.ArtifactStagingDirectory)/npm/ - displayName: Prepare Bellissima npm package - env: - PACKAGE_VERSION: $(build.NBGV_NpmPackageVersion) + displayName: Run npm pack workingDirectory: src/Umbraco.Web.UI.Client - task: PublishPipelineArtifact@1 displayName: Publish Bellissima npm artifact @@ -208,28 +203,11 @@ stages: pool: vmImage: 'ubuntu-latest' variables: - npm_config_cache: $(Pipeline.Workspace)/.npm_client - NODE_OPTIONS: --max_old_space_size=16384 BASE_PATH: /v$(umbracoMajorVersion)/ui steps: - checkout: self submodules: true - - task: NodeTool@0 - displayName: Use Node.js $(nodeVersion) - retryCountOnTaskFailure: 3 - inputs: - versionSpec: $(nodeVersion) - - task: Cache@2 - displayName: Cache node_modules - inputs: - key: '"npm_client" | "$(Agent.OS)"| $(Build.SourcesDirectory)/src/Umbraco.Web.UI.Client/package-lock.json' - restoreKeys: | - "npm_client" | "$(Agent.OS)" - "npm_client" - path: $(npm_config_cache) - - script: npm ci --no-fund --no-audit --prefer-offline - workingDirectory: src/Umbraco.Web.UI.Client - displayName: Run npm ci + - template: templates/backoffice-install.yml - script: npm run storybook:build displayName: Build Storybook env: @@ -528,7 +506,7 @@ stages: - ${{ if eq(parameters.isNightly, true) }}: pwsh: npm run test --ignore-certificate-errors ${{ else }}: - pwsh: npm run smokeTest --ignore-certificate-errors + pwsh: npm run smokeTest --ignore-certificate-errors displayName: Run Playwright tests continueOnError: true workingDirectory: tests/Umbraco.Tests.AcceptanceTest diff --git a/build/templates/backoffice-install.yml b/build/templates/backoffice-install.yml new file mode 100644 index 0000000000..2c22cca7e3 --- /dev/null +++ b/build/templates/backoffice-install.yml @@ -0,0 +1,31 @@ +steps: + - task: NodeTool@0 + displayName: Use Node.js + retryCountOnTaskFailure: 3 + inputs: + versionSource: 'fromFile' + versionFilePath: src/Umbraco.Web.UI.Client/.nvmrc + + - bash: | + echo "##[command]Install nbgv" + dotnet tool install --tool-path . nbgv + echo "##[command]Running nbgv get-version" + PACKAGE_VERSION=$(nbgv get-version -v NpmPackageVersion) + echo "##[command]Running npm version" + echo "##[debug]Version: $PACKAGE_VERSION" + cd src/Umbraco.Web.UI.Client + npm version $PACKAGE_VERSION --allow-same-version --no-git-tag-version + displayName: Set NPM Version + + - task: Cache@2 + displayName: Cache node_modules + inputs: + key: '"npm_client" | "$(Agent.OS)"| $(Build.SourcesDirectory)/src/Umbraco.Web.UI.Client/package-lock.json' + restoreKeys: | + "npm_client" | "$(Agent.OS)" + "npm_client" + path: $(npm_config_cache) + + - script: npm ci --no-fund --no-audit --prefer-offline + displayName: Run npm ci (Bellissima) + workingDirectory: src/Umbraco.Web.UI.Client diff --git a/src/Umbraco.Web.UI.Client b/src/Umbraco.Web.UI.Client index 144378a4cb..9ec8c79227 160000 --- a/src/Umbraco.Web.UI.Client +++ b/src/Umbraco.Web.UI.Client @@ -1 +1 @@ -Subproject commit 144378a4cb16be8499b38db8e217b1d24050db75 +Subproject commit 9ec8c79227312d97e5dcd6b61a6209752e48d76f diff --git a/umbraco.sln b/umbraco.sln index 1e7a6e3326..59015768a8 100644 --- a/umbraco.sln +++ b/umbraco.sln @@ -140,6 +140,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{20CE9C97 ProjectSection(SolutionItems) = preProject build\azure-pipelines.yml = build\azure-pipelines.yml build\nightly-build-trigger.yml = build\nightly-build-trigger.yml + build\templates\backoffice-install.yml = build\templates\backoffice-install.yml EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "csharp-docs", "csharp-docs", "{F2BF84D9-0A14-40AF-A0F3-B9BBBBC16A44}" From d2707acd3b6b7e10dca50a06da1fc4b9505bb281 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Mon, 1 Jul 2024 15:29:42 +0200 Subject: [PATCH 078/113] V14: Create seperate Create and Update in ContentTypeServiceBase (#16714) * Create seperate Create and Update in ContentTypeServiceBase * Fix up notification for update --------- Co-authored-by: nikolajlauridsen --- .../DocumentTypeControllerBase.cs | 12 ++++ .../ContentTypeEditingService.cs | 16 +++-- ...peServiceBaseOfTRepositoryTItemTService.cs | 69 +++++++++++++++++++ .../Services/IContentTypeServiceBase.cs | 15 ++++ .../ContentTypeOperationStatus.cs | 5 +- 5 files changed, 110 insertions(+), 7 deletions(-) diff --git a/src/Umbraco.Cms.Api.Management/Controllers/DocumentType/DocumentTypeControllerBase.cs b/src/Umbraco.Cms.Api.Management/Controllers/DocumentType/DocumentTypeControllerBase.cs index 2e5b7385ae..c65ed1631d 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/DocumentType/DocumentTypeControllerBase.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/DocumentType/DocumentTypeControllerBase.cs @@ -86,6 +86,18 @@ public abstract class DocumentTypeControllerBase : ManagementApiControllerBase .WithTitle("Operation not permitted") .WithDetail("The attempted operation was not permitted, likely due to a permission/configuration mismatch with the operation.") .Build()), + ContentTypeOperationStatus.CancelledByNotification => new BadRequestObjectResult(problemDetailsBuilder + .WithTitle("Cancelled by notification") + .WithDetail("The attempted operation was cancelled by a notification.") + .Build()), + ContentTypeOperationStatus.NameCannotBeEmpty => new BadRequestObjectResult(problemDetailsBuilder + .WithTitle("Name cannot be empty") + .WithDetail("The name of the content type cannot be empty") + .Build()), + ContentTypeOperationStatus.NameTooLong => new BadRequestObjectResult(problemDetailsBuilder + .WithTitle("Name was too long") + .WithDetail("Name cannot be more than 255 characters in length.") + .Build()), _ => new ObjectResult("Unknown content type operation status") { StatusCode = StatusCodes.Status500InternalServerError }, }); diff --git a/src/Umbraco.Core/Services/ContentTypeEditing/ContentTypeEditingService.cs b/src/Umbraco.Core/Services/ContentTypeEditing/ContentTypeEditingService.cs index f366a75124..9c96fe64a4 100644 --- a/src/Umbraco.Core/Services/ContentTypeEditing/ContentTypeEditingService.cs +++ b/src/Umbraco.Core/Services/ContentTypeEditing/ContentTypeEditingService.cs @@ -40,7 +40,12 @@ internal sealed class ContentTypeEditingService : ContentTypeEditingServiceBase< UpdateTemplates(contentType, model); // save content type - await SaveAsync(contentType, userKey); + Attempt creationAttempt = await _contentTypeService.CreateAsync(contentType, userKey); + + if(creationAttempt.Success is false) + { + return Attempt.FailWithStatus(creationAttempt.Result, contentType); + } return Attempt.SucceedWithStatus(ContentTypeOperationStatus.Success, contentType); } @@ -58,9 +63,11 @@ internal sealed class ContentTypeEditingService : ContentTypeEditingServiceBase< UpdateHistoryCleanup(contentType, model); UpdateTemplates(contentType, model); - await SaveAsync(contentType, userKey); + Attempt attempt = await _contentTypeService.UpdateAsync(contentType, userKey); - return Attempt.SucceedWithStatus(ContentTypeOperationStatus.Success, contentType); + return attempt.Success + ? Attempt.SucceedWithStatus(ContentTypeOperationStatus.Success, contentType) + : Attempt.FailWithStatus(attempt.Result, null); } public async Task> GetAvailableCompositionsAsync( @@ -93,9 +100,6 @@ internal sealed class ContentTypeEditingService : ContentTypeEditingServiceBase< contentType.SetDefaultTemplate(allowedTemplates.FirstOrDefault(t => t.Key == model.DefaultTemplateKey)); } - private async Task SaveAsync(IContentType contentType, Guid userKey) - => await _contentTypeService.SaveAsync(contentType, userKey); - protected override IContentType CreateContentType(IShortStringHelper shortStringHelper, int parentId) => new ContentType(shortStringHelper, parentId); diff --git a/src/Umbraco.Core/Services/ContentTypeServiceBaseOfTRepositoryTItemTService.cs b/src/Umbraco.Core/Services/ContentTypeServiceBaseOfTRepositoryTItemTService.cs index 8340f3c584..5d91c29b27 100644 --- a/src/Umbraco.Core/Services/ContentTypeServiceBaseOfTRepositoryTItemTService.cs +++ b/src/Umbraco.Core/Services/ContentTypeServiceBaseOfTRepositoryTItemTService.cs @@ -603,6 +603,75 @@ public abstract class ContentTypeServiceBase : ContentTypeSe } } + public async Task> CreateAsync(TItem item, Guid performingUserKey) => await InternalSaveAsync(item, performingUserKey); + + public async Task> UpdateAsync(TItem item, Guid performingUserKey) => await InternalSaveAsync(item, performingUserKey); + + private async Task> InternalSaveAsync(TItem item, Guid performingUserKey) + { + using ICoreScope scope = ScopeProvider.CreateCoreScope(); + EventMessages eventMessages = EventMessagesFactory.Get(); + + Attempt validationAttempt = ValidateCommon(item); + if (validationAttempt.Success is false) + { + return Attempt.Fail(validationAttempt.Result); + } + + SavingNotification savingNotification = GetSavingNotification(item, eventMessages); + if (await scope.Notifications.PublishCancelableAsync(savingNotification)) + { + scope.Complete(); + return Attempt.Fail(ContentTypeOperationStatus.CancelledByNotification); + } + + scope.WriteLock(WriteLockIds); + + // validate the DAG transform, within the lock + ValidateLocked(item); // throws if invalid + + int userId = await _userIdKeyResolver.GetAsync(performingUserKey); + item.CreatorId = userId; + if (item.Description == string.Empty) + { + item.Description = null; + } + + Repository.Save(item); // also updates content/media/member items + + // figure out impacted content types + ContentTypeChange[] changes = ComposeContentTypeChanges(item).ToArray(); + + // Publish this in scope, see comment at GetContentTypeRefreshedNotification for more info. + await _eventAggregator.PublishAsync(GetContentTypeRefreshedNotification(changes, eventMessages)); + + scope.Notifications.Publish(GetContentTypeChangedNotification(changes, eventMessages)); + + SavedNotification savedNotification = GetSavedNotification(item, eventMessages); + savedNotification.WithStateFrom(savingNotification); + scope.Notifications.Publish(savedNotification); + + Audit(AuditType.Save, userId, item.Id); + scope.Complete(); + + return Attempt.Succeed(ContentTypeOperationStatus.Success); + } + + private Attempt ValidateCommon(TItem item) + { + if (string.IsNullOrWhiteSpace(item.Name)) + { + return Attempt.Fail(ContentTypeOperationStatus.NameCannotBeEmpty); + } + + if (item.Name.Length > 255) + { + return Attempt.Fail(ContentTypeOperationStatus.NameTooLong); + } + + return Attempt.Succeed(ContentTypeOperationStatus.Success); + } + #endregion #region Delete diff --git a/src/Umbraco.Core/Services/IContentTypeServiceBase.cs b/src/Umbraco.Core/Services/IContentTypeServiceBase.cs index f3707a5059..bee084fbf9 100644 --- a/src/Umbraco.Core/Services/IContentTypeServiceBase.cs +++ b/src/Umbraco.Core/Services/IContentTypeServiceBase.cs @@ -67,16 +67,31 @@ public interface IContentTypeBaseService : IContentTypeBaseService, IServ bool HasChildren(Guid id); + [Obsolete("Please use the respective Create or Update instead")] void Save(TItem? item, int userId = Constants.Security.SuperUserId); + [Obsolete("Please use the respective Create or Update instead")] Task SaveAsync(TItem item, Guid performingUserKey) { Save(item); return Task.CompletedTask; } + [Obsolete("Please use the respective Create or Update instead")] void Save(IEnumerable items, int userId = Constants.Security.SuperUserId); + Task> CreateAsync(TItem item, Guid performingUserKey) + { + Save(item); + return Task.FromResult(Attempt.Succeed(ContentTypeOperationStatus.Success)); + } + + Task> UpdateAsync(TItem item, Guid performingUserKey) + { + Save(item); + return Task.FromResult(Attempt.Succeed(ContentTypeOperationStatus.Success)); + } + void Delete(TItem item, int userId = Constants.Security.SuperUserId); /// diff --git a/src/Umbraco.Core/Services/OperationStatus/ContentTypeOperationStatus.cs b/src/Umbraco.Core/Services/OperationStatus/ContentTypeOperationStatus.cs index 21eeedb1d5..8b52d921ee 100644 --- a/src/Umbraco.Core/Services/OperationStatus/ContentTypeOperationStatus.cs +++ b/src/Umbraco.Core/Services/OperationStatus/ContentTypeOperationStatus.cs @@ -5,6 +5,8 @@ public enum ContentTypeOperationStatus Success, DuplicateAlias, InvalidAlias, + NameCannotBeEmpty, + NameTooLong, InvalidPropertyTypeAlias, PropertyTypeAliasCannotEqualContentTypeAlias, DuplicatePropertyTypeAlias, @@ -17,5 +19,6 @@ public enum ContentTypeOperationStatus MissingContainer, DuplicateContainer, NotFound, - NotAllowed + NotAllowed, + CancelledByNotification, } From 13b77d35df31b027cf279a9ee29ca2f1e856d0f5 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Tue, 2 Jul 2024 09:40:53 +0200 Subject: [PATCH 079/113] V14: Build and publish Typedoc docs (#16724) * rename display names of 'ui docs' to 'storybook' * add new jobs to build and publish the ui-api docs (typedoc) to the blob storage --- build/azure-pipelines.yml | 45 +++++++++++++++++++++++++++++++++++---- 1 file changed, 41 insertions(+), 4 deletions(-) diff --git a/build/azure-pipelines.yml b/build/azure-pipelines.yml index e794cb844a..977ce443d0 100644 --- a/build/azure-pipelines.yml +++ b/build/azure-pipelines.yml @@ -217,16 +217,30 @@ stages: displayName: Replace BASE_PATH on assets workingDirectory: $(Build.SourcesDirectory)/src/Umbraco.Web.UI.Client/storybook-static - task: ArchiveFiles@2 - displayName: Archive js Docs + displayName: Archive Storybook inputs: rootFolderOrFile: $(Build.SourcesDirectory)/src/Umbraco.Web.UI.Client/storybook-static includeRootFolder: false archiveFile: $(Build.ArtifactStagingDirectory)/ui-docs.zip - task: PublishPipelineArtifact@1 - displayName: Publish js Docs + displayName: Publish Storybook inputs: targetPath: $(Build.ArtifactStagingDirectory)/ui-docs.zip artifact: ui-docs + - script: npm run generate:ui-api-docs + displayName: Generate API Docs + workingDirectory: $(Build.SourcesDirectory)/src/Umbraco.Web.UI.Client + - task: ArchiveFiles@2 + displayName: Archive UI API Docs + inputs: + rootFolderOrFile: $(Build.SourcesDirectory)/src/Umbraco.Web.UI.Client/ui-api + includeRootFolder: false + archiveFile: $(Build.ArtifactStagingDirectory)/ui-api-docs.zip + - task: PublishPipelineArtifact@1 + displayName: Publish UI API Docs + inputs: + targetPath: $(Build.ArtifactStagingDirectory)/ui-api-docs.zip + artifact: ui-api-docs ############################################### ## Test @@ -846,7 +860,7 @@ stages: BlobPrefix: v$(umbracoMajorVersion)/csharp CleanTargetBeforeCopy: true - job: - displayName: Upload js Docs + displayName: Upload Storybook steps: - checkout: none - task: DownloadPipelineArtifact@2 @@ -859,7 +873,7 @@ stages: archiveFilePatterns: $(Build.SourcesDirectory)/ui-docs.zip destinationFolder: $(Build.ArtifactStagingDirectory)/ui-docs - task: AzureFileCopy@4 - displayName: 'Copy UI Docs to blob storage' + displayName: 'Copy Storybook to blob storage' inputs: SourcePath: '$(Build.ArtifactStagingDirectory)/ui-docs/*' azureSubscription: umbraco-storage @@ -868,3 +882,26 @@ stages: ContainerName: '$web' BlobPrefix: v$(umbracoMajorVersion)/ui CleanTargetBeforeCopy: true + - job: + displayName: Upload UI API Docs + steps: + - checkout: none + - task: DownloadPipelineArtifact@2 + displayName: Download artifact + inputs: + artifact: ui-api-docs + path: $(Build.SourcesDirectory) + - task: ExtractFiles@1 + inputs: + archiveFilePatterns: $(Build.SourcesDirectory)/ui-api-docs.zip + destinationFolder: $(Build.ArtifactStagingDirectory)/ui-api-docs + - task: AzureFileCopy@4 + displayName: 'Copy UI API Docs to blob storage' + inputs: + SourcePath: '$(Build.ArtifactStagingDirectory)/ui-api-docs/*' + azureSubscription: umbraco-storage + Destination: AzureBlob + storage: umbracoapidocs + ContainerName: '$web' + BlobPrefix: v$(umbracoMajorVersion)/ui-api + CleanTargetBeforeCopy: true From ef01c27fadd362eda2d6099af34caa725be5bfcc Mon Sep 17 00:00:00 2001 From: Elitsa Marinovska <21998037+elit0451@users.noreply.github.com> Date: Tue, 2 Jul 2024 10:55:03 +0300 Subject: [PATCH 080/113] Respect user start nodes in GetPagedChildren (used in List View) (#16722) --- .../Controllers/EntityController.cs | 72 +++++++++++-------- 1 file changed, 44 insertions(+), 28 deletions(-) diff --git a/src/Umbraco.Web.BackOffice/Controllers/EntityController.cs b/src/Umbraco.Web.BackOffice/Controllers/EntityController.cs index f37031d841..c54344a239 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/EntityController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/EntityController.cs @@ -1,16 +1,11 @@ -using System.Collections.Concurrent; using System.Dynamic; using System.Globalization; -using System.IO; using System.Linq.Expressions; using System.Reflection; -using System.Security.Cryptography; -using Examine.Search; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.Extensions.DependencyInjection; -using Newtonsoft.Json; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.DependencyInjection; @@ -21,7 +16,6 @@ using Umbraco.Cms.Core.Models.Entities; using Umbraco.Cms.Core.Models.Membership; using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Cms.Core.Models.TemplateQuery; -using Umbraco.Cms.Core.Persistence; using Umbraco.Cms.Core.PropertyEditors; using Umbraco.Cms.Core.Routing; using Umbraco.Cms.Core.Security; @@ -718,15 +712,15 @@ public class EntityController : UmbracoAuthorizedJsonController { //TODO: Need to check for Object types that support hierarchy here, some might not. - var startNodes = GetStartNodes(type); + var startNodeIds = GetStartNodeIds(type); var ignoreUserStartNodes = IsDataTypeIgnoringUserStartNodes(dataTypeKey); // root is special: we reduce it to start nodes if the user's start node is not the default, then we need to return their start nodes - if (id == Constants.System.Root && startNodes.Length > 0 && - startNodes.Contains(Constants.System.Root) == false && !ignoreUserStartNodes) + if (id == Constants.System.Root && startNodeIds.Length > 0 && + startNodeIds.Contains(Constants.System.Root) == false && !ignoreUserStartNodes) { - IEntitySlim[] nodes = _entityService.GetAll(objectType.Value, startNodes).ToArray(); + IEntitySlim[] nodes = _entityService.GetAll(objectType.Value, startNodeIds).ToArray(); if (nodes.Length == 0) { return Enumerable.Empty(); @@ -858,13 +852,14 @@ public class EntityController : UmbracoAuthorizedJsonController { IEnumerable entities; - var startNodes = GetStartNodes(type); + var startNodeIds = GetStartNodeIds(type); + var startNodePaths = GetStartNodePaths(type); var ignoreUserStartNodes = IsDataTypeIgnoringUserStartNodes(dataTypeKey); // root is special: we reduce it to start nodes if the user's start node is not the default, then we need to return their start nodes - if (id == Constants.System.Root && startNodes.Length > 0 && - startNodes.Contains(Constants.System.Root) == false && !ignoreUserStartNodes) + if (id == Constants.System.Root && startNodeIds.Length > 0 && + startNodeIds.Contains(Constants.System.Root) == false && !ignoreUserStartNodes) { return new PagedResult(0, 0, 0); } @@ -896,22 +891,28 @@ public class EntityController : UmbracoAuthorizedJsonController var culture = ClientCulture(); var pagedResult = new PagedResult(totalRecords, pageNumber, pageSize) { - Items = entities.Select(source => - { - EntityBasic? target = _umbracoMapper.Map(source, context => + Items = entities + // Filtering out child nodes after getting a paged result is an active choice here, even though the pagination might get off. + // This has been the case with this functionality in Umbraco for a long time. + .Where(entity => ignoreUserStartNodes || + (ContentPermissions.IsInBranchOfStartNode(entity.Path, startNodeIds, startNodePaths, out var hasPathAccess) && + hasPathAccess)) + .Select(source => { - context.SetCulture(culture); - context.SetCulture(culture); - }); + EntityBasic? target = _umbracoMapper.Map(source, context => + { + context.SetCulture(culture); + context.SetCulture(culture); + }); - if (target is not null) - { - //TODO: Why is this here and not in the mapping? - target.AdditionalData["hasChildren"] = source.HasChildren; - } + if (target is not null) + { + //TODO: Why is this here and not in the mapping? + target.AdditionalData["hasChildren"] = source.HasChildren; + } - return target; - }).WhereNotNull() + return target; + }).WhereNotNull() }; return pagedResult; @@ -931,7 +932,7 @@ public class EntityController : UmbracoAuthorizedJsonController } } - private int[] GetStartNodes(UmbracoEntityTypes type) + private int[] GetStartNodeIds(UmbracoEntityTypes type) { switch (type) { @@ -946,6 +947,21 @@ public class EntityController : UmbracoAuthorizedJsonController } } + private string[] GetStartNodePaths(UmbracoEntityTypes type) + { + switch (type) + { + case UmbracoEntityTypes.Document: + return _backofficeSecurityAccessor.BackOfficeSecurity?.CurrentUser?.GetContentStartNodePaths( + _entityService, _appCaches) ?? Array.Empty(); + case UmbracoEntityTypes.Media: + return _backofficeSecurityAccessor.BackOfficeSecurity?.CurrentUser?.GetMediaStartNodePaths( + _entityService, _appCaches) ?? Array.Empty(); + default: + return Array.Empty(); + } + } + public ActionResult> GetPagedDescendants( int id, UmbracoEntityTypes type, @@ -979,7 +995,7 @@ public class EntityController : UmbracoAuthorizedJsonController { // root is special: we reduce it to start nodes - var aids = GetStartNodes(type); + var aids = GetStartNodeIds(type); var ignoreUserStartNodes = IsDataTypeIgnoringUserStartNodes(dataTypeKey); entities = aids == null || aids.Contains(Constants.System.Root) || ignoreUserStartNodes From 46acd51759c4d02493b6bbb5a68e77c1324742c3 Mon Sep 17 00:00:00 2001 From: Sven Geusens Date: Tue, 2 Jul 2024 14:22:19 +0200 Subject: [PATCH 081/113] [V14] Make the backend work with the new localLinks format (#16661) * Support new localLink format in core link parsing * Updated devliery api to work with the new locallinks format Added tests for old and new format handling. * Fix error regarding type attribute not always being present (for example old format or non local links) --- .../Templates/HtmlLocalLinkParser.cs | 33 +++ .../DeliveryApi/ApiRichTextElementParser.cs | 6 + .../DeliveryApi/ApiRichTextMarkupParser.cs | 3 +- .../DeliveryApi/ApiRichTextParserBase.cs | 83 +++++++- .../Templates/HtmlLocalLinkParserTests.cs | 80 ++++++- .../ApiRichTextMarkupParserTests.cs | 200 ++++++++++++++++++ 6 files changed, 392 insertions(+), 13 deletions(-) create mode 100644 tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/DeliveryApi/ApiRichTextMarkupParserTests.cs diff --git a/src/Umbraco.Core/Templates/HtmlLocalLinkParser.cs b/src/Umbraco.Core/Templates/HtmlLocalLinkParser.cs index 059f5f9cef..74105da511 100644 --- a/src/Umbraco.Core/Templates/HtmlLocalLinkParser.cs +++ b/src/Umbraco.Core/Templates/HtmlLocalLinkParser.cs @@ -10,6 +10,13 @@ namespace Umbraco.Cms.Core.Templates; /// public sealed class HtmlLocalLinkParser { + // needs to support media and document links, order of attributes should not matter nor should other attributes mess with things + // media + // other page + internal static readonly Regex LocalLinkTagPattern = new( + @"document|media)['""].*?(?href=[""']/{localLink:(?[a-fA-F0-9-]+)})[""'])|((?href=[""']/{localLink:(?[a-fA-F0-9-]+)})[""'].*?type=(['""])(?document|media)(?:['""])))|(?:(?:type=['""](?document|media)['""])|(?:(?href=[""']/{localLink:[a-fA-F0-9-]+})[""'])))[^>]*>", + RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); + internal static readonly Regex LocalLinkPattern = new( @"href=""[/]?(?:\{|\%7B)localLink:([a-zA-Z0-9-://]+)(?:\}|\%7D)", RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); @@ -105,6 +112,32 @@ public sealed class HtmlLocalLinkParser } private IEnumerable<(int? intId, GuidUdi? udi, string tagValue)> FindLocalLinkIds(string text) + { + MatchCollection localLinkTagMatches = LocalLinkTagPattern.Matches(text); + foreach (Match linkTag in localLinkTagMatches) + { + if (linkTag.Groups.Count < 1) + { + continue; + } + + if (Guid.TryParse(linkTag.Groups["guid"].Value, out Guid guid) is false) + { + continue; + } + + yield return (null, new GuidUdi(linkTag.Groups["type"].Value, guid), linkTag.Groups["locallink"].Value); + } + + // also return legacy results for values that have not been migrated + foreach ((int? intId, GuidUdi? udi, string tagValue) legacyResult in FindLegacyLocalLinkIds(text)) + { + yield return legacyResult; + } + } + + // todo remove at some point? + private IEnumerable<(int? intId, GuidUdi? udi, string tagValue)> FindLegacyLocalLinkIds(string text) { // Parse internal links MatchCollection tags = LocalLinkPattern.Matches(text); diff --git a/src/Umbraco.Infrastructure/DeliveryApi/ApiRichTextElementParser.cs b/src/Umbraco.Infrastructure/DeliveryApi/ApiRichTextElementParser.cs index eed3b848eb..61b6418ae7 100644 --- a/src/Umbraco.Infrastructure/DeliveryApi/ApiRichTextElementParser.cs +++ b/src/Umbraco.Infrastructure/DeliveryApi/ApiRichTextElementParser.cs @@ -132,9 +132,15 @@ internal sealed class ApiRichTextElementParser : ApiRichTextParserBase, IApiRich return; } + if (attributes.ContainsKey("type") is false || attributes["type"] is not string type) + { + type = "unknown"; + } + ReplaceLocalLinks( publishedSnapshot, href, + type, route => { attributes["route"] = route; diff --git a/src/Umbraco.Infrastructure/DeliveryApi/ApiRichTextMarkupParser.cs b/src/Umbraco.Infrastructure/DeliveryApi/ApiRichTextMarkupParser.cs index 42c8829868..338966dc39 100644 --- a/src/Umbraco.Infrastructure/DeliveryApi/ApiRichTextMarkupParser.cs +++ b/src/Umbraco.Infrastructure/DeliveryApi/ApiRichTextMarkupParser.cs @@ -52,8 +52,9 @@ internal sealed class ApiRichTextMarkupParser : ApiRichTextParserBase, IApiRichT foreach (HtmlNode link in links) { ReplaceLocalLinks( - publishedSnapshot, + publishedSnapshot, link.GetAttributeValue("href", string.Empty), + link.GetAttributeValue("type", "unknown"), route => { link.SetAttributeValue("href", route.Path); diff --git a/src/Umbraco.Infrastructure/DeliveryApi/ApiRichTextParserBase.cs b/src/Umbraco.Infrastructure/DeliveryApi/ApiRichTextParserBase.cs index 7723fc835c..407bc1a022 100644 --- a/src/Umbraco.Infrastructure/DeliveryApi/ApiRichTextParserBase.cs +++ b/src/Umbraco.Infrastructure/DeliveryApi/ApiRichTextParserBase.cs @@ -4,6 +4,7 @@ using Umbraco.Cms.Core.DeliveryApi; using Umbraco.Cms.Core.Models.DeliveryApi; using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Cms.Core.PublishedCache; +using Umbraco.Cms.Core.Templates; namespace Umbraco.Cms.Infrastructure.DeliveryApi; @@ -18,20 +19,35 @@ internal abstract partial class ApiRichTextParserBase _apiMediaUrlProvider = apiMediaUrlProvider; } - protected void ReplaceLocalLinks(IPublishedSnapshot publishedSnapshot, string href, Action handleContentRoute, Action handleMediaUrl, Action handleInvalidLink) + protected void ReplaceLocalLinks(IPublishedSnapshot publishedSnapshot, string href, string type, Action handleContentRoute, Action handleMediaUrl, Action handleInvalidLink) + { + ReplaceStatus replaceAttempt = ReplaceLocalLink(publishedSnapshot, href, type, handleContentRoute, handleMediaUrl); + if (replaceAttempt == ReplaceStatus.Success) + { + return; + } + + if (replaceAttempt == ReplaceStatus.InvalidEntityType || ReplaceLegacyLocalLink(publishedSnapshot, href, handleContentRoute, handleMediaUrl) == ReplaceStatus.InvalidEntityType) + { + handleInvalidLink(); + } + } + + private ReplaceStatus ReplaceLocalLink(IPublishedSnapshot publishedSnapshot, string href, string type, Action handleContentRoute, Action handleMediaUrl) { Match match = LocalLinkRegex().Match(href); if (match.Success is false) { - return; + return ReplaceStatus.NoMatch; } - if (UdiParser.TryParse(match.Groups["udi"].Value, out Udi? udi) is false) + if (Guid.TryParse(match.Groups["guid"].Value, out Guid guid) is false) { - return; + return ReplaceStatus.NoMatch; } - bool handled = false; + var udi = new GuidUdi(type, guid); + switch (udi.EntityType) { case Constants.UdiEntityType.Document: @@ -41,8 +57,8 @@ internal abstract partial class ApiRichTextParserBase : null; if (route != null) { - handled = true; handleContentRoute(route); + return ReplaceStatus.Success; } break; @@ -50,17 +66,56 @@ internal abstract partial class ApiRichTextParserBase IPublishedContent? media = publishedSnapshot.Media?.GetById(udi); if (media != null) { - handled = true; handleMediaUrl(_apiMediaUrlProvider.GetUrl(media)); + return ReplaceStatus.Success; } break; } - if(handled is false) + return ReplaceStatus.InvalidEntityType; + } + + private ReplaceStatus ReplaceLegacyLocalLink(IPublishedSnapshot publishedSnapshot, string href, Action handleContentRoute, Action handleMediaUrl) + { + Match match = LegacyLocalLinkRegex().Match(href); + if (match.Success is false) { - handleInvalidLink(); + return ReplaceStatus.NoMatch; } + + if (UdiParser.TryParse(match.Groups["udi"].Value, out Udi? udi) is false) + { + return ReplaceStatus.NoMatch; + } + + + switch (udi.EntityType) + { + case Constants.UdiEntityType.Document: + IPublishedContent? content = publishedSnapshot.Content?.GetById(udi); + IApiContentRoute? route = content != null + ? _apiContentRouteBuilder.Build(content) + : null; + if (route != null) + { + handleContentRoute(route); + return ReplaceStatus.Success; + } + + break; + case Constants.UdiEntityType.Media: + IPublishedContent? media = publishedSnapshot.Media?.GetById(udi); + if (media != null) + { + handleMediaUrl(_apiMediaUrlProvider.GetUrl(media)); + return ReplaceStatus.Success; + } + + break; + } + + return ReplaceStatus.InvalidEntityType; } protected void ReplaceLocalImages(IPublishedSnapshot publishedSnapshot, string udi, Action handleMediaUrl) @@ -80,5 +135,15 @@ internal abstract partial class ApiRichTextParserBase } [GeneratedRegex("{localLink:(?umb:.+)}")] + private static partial Regex LegacyLocalLinkRegex(); + + [GeneratedRegex("{localLink:(?.+)}")] private static partial Regex LocalLinkRegex(); + + private enum ReplaceStatus + { + NoMatch, + Success, + InvalidEntityType + } } diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Templates/HtmlLocalLinkParserTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Templates/HtmlLocalLinkParserTests.cs index c9d3f3ab9b..93b0d5bba3 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Templates/HtmlLocalLinkParserTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Templates/HtmlLocalLinkParserTests.cs @@ -13,6 +13,7 @@ using Umbraco.Cms.Core.Routing; using Umbraco.Cms.Core.Templates; using Umbraco.Cms.Tests.Common; using Umbraco.Cms.Tests.UnitTests.TestHelpers.Objects; +using Umbraco.Extensions; namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Templates; @@ -21,6 +22,32 @@ public class HtmlLocalLinkParserTests { [Test] public void Returns_Udis_From_LocalLinks() + { + var input = @"

    +

    + + other page +
    +

    +media +

    "; + + var umbracoContextAccessor = new TestUmbracoContextAccessor(); + var parser = new HtmlLocalLinkParser(umbracoContextAccessor, Mock.Of()); + + var result = parser.FindUdisFromLocalLinks(input).ToList(); + + Assert.Multiple(() => + { + Assert.AreEqual(2, result.Count); + Assert.Contains(UdiParser.Parse("umb://document/eed5fc6b-96fd-45a5-a0f1-b1adfb483c2f"), result); + Assert.Contains(UdiParser.Parse("umb://media/7e21a725-b905-4c5f-86dc-8c41ec116e39"), result); + }); + } + + // todo remove at some point and the implementation. + [Test] + public void Returns_Udis_From_Legacy_LocalLinks() { var input = @"

    @@ -36,12 +63,59 @@ public class HtmlLocalLinkParserTests var result = parser.FindUdisFromLocalLinks(input).ToList(); - Assert.AreEqual(2, result.Count); - Assert.AreEqual(UdiParser.Parse("umb://document/C093961595094900AAF9170DDE6AD442"), result[0]); - Assert.AreEqual(UdiParser.Parse("umb://document-type/2D692FCB070B4CDA92FB6883FDBFD6E2"), result[1]); + Assert.Multiple(() => + { + Assert.AreEqual(2, result.Count); + Assert.Contains(UdiParser.Parse("umb://document/C093961595094900AAF9170DDE6AD442"), result); + Assert.Contains(UdiParser.Parse("umb://document-type/2D692FCB070B4CDA92FB6883FDBFD6E2"), result); + }); + } + + // todo remove at some point and the implementation. + [Test] + public void Returns_Udis_From_Legacy_And_Current_LocalLinks() + { + var input = @"

    +

    + + hello +
    +

    +hello +

    +

    +

    + + other page +
    +

    +media +

    "; + + var umbracoContextAccessor = new TestUmbracoContextAccessor(); + var parser = new HtmlLocalLinkParser(umbracoContextAccessor, Mock.Of()); + + var result = parser.FindUdisFromLocalLinks(input).ToList(); + + Assert.Multiple(() => + { + Assert.AreEqual(4, result.Count); + Assert.Contains(UdiParser.Parse("umb://document/eed5fc6b-96fd-45a5-a0f1-b1adfb483c2f"), result); + Assert.Contains(UdiParser.Parse("umb://media/7e21a725-b905-4c5f-86dc-8c41ec116e39"), result); + Assert.Contains(UdiParser.Parse("umb://document/C093961595094900AAF9170DDE6AD442"), result); + Assert.Contains(UdiParser.Parse("umb://document-type/2D692FCB070B4CDA92FB6883FDBFD6E2"), result); + }); } [TestCase("", "")] + // current + [TestCase( + "world", + "world")] + [TestCase( + "world", + "world")] + // legacy [TestCase( "hello href=\"{localLink:1234}\" world ", "hello href=\"/my-test-url\" world ")] diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/DeliveryApi/ApiRichTextMarkupParserTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/DeliveryApi/ApiRichTextMarkupParserTests.cs new file mode 100644 index 0000000000..43ec2136f7 --- /dev/null +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/DeliveryApi/ApiRichTextMarkupParserTests.cs @@ -0,0 +1,200 @@ +using Microsoft.Extensions.Logging; +using Moq; +using NUnit.Framework; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.DeliveryApi; +using Umbraco.Cms.Core.Models.DeliveryApi; +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.PublishedCache; +using Umbraco.Cms.Infrastructure.DeliveryApi; + +namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.DeliveryApi; + +[TestFixture] +public class ApiRichTextMarkupParserTests +{ + private Mock _apiContentRouteBuilder; + private Mock _apiMediaUrlProvider; + private Mock _publishedSnapshotAccessor; + + [Test] + public void Can_Parse_Legacy_LocalLinks() + { + var key1 = Guid.Parse("a1c5d649977f4ea59b1cb26055f3eed3"); + var data1 = new MockData() + .WithKey(key1) + .WithRoutePath("/inline/") + .WithRouteStartPath("inline"); + + var mockData = new Dictionary + { + { key1, data1 }, + }; + + var parser = BuildDefaultSut(mockData); + + var legacyHtml = + "

    link to another page

    "; + + var expectedOutput = + "

    link to another page

    "; + + var parsedHtml = parser.Parse(legacyHtml); + + Assert.AreEqual(expectedOutput, parsedHtml); + } + + [Test] + public void Can_Parse_LocalLinks() + { + var key1 = Guid.Parse("eed5fc6b-96fd-45a5-a0f1-b1adfb483c2f"); + var data1 = new MockData() + .WithKey(key1) + .WithRoutePath("/self/") + .WithRouteStartPath("self"); + + var key2 = Guid.Parse("cc143afe-4cbf-46e5-b399-c9f451384373"); + var data2 = new MockData() + .WithKey(key2) + .WithRoutePath("/other/") + .WithRouteStartPath("other"); + + var mockData = new Dictionary + { + { key1, data1 }, + { key2, data2 }, + }; + + var parser = BuildDefaultSut(mockData); + + var html = + @"

    Rich text outside of the blocks with a link to itself

    +

    and to the other page

    "; + + var expectedOutput = + @"

    Rich text outside of the blocks with a link to itself

    +

    and to the other page

    "; + + var parsedHtml = parser.Parse(html); + + Assert.AreEqual(expectedOutput, parsedHtml); + } + + [Test] + public void Can_Parse_Legacy_LocalImages() + { + var key1 = Guid.Parse("395bdc0e8f4d4ad4af7f3a3f6265651e"); + var data1 = new MockData() + .WithKey(key1) + .WithMediaUrl("https://localhost:44331/media/bdofwokn/77gtp8fbrxmgkefatp10aw.webp"); + + var mockData = new Dictionary + { + { key1, data1 }, + }; + var parser = BuildDefaultSut(mockData); + + var legacyHtml = + @"

    An image

    \n

    "; + + var expectedOutput = + @"

    An image

    \n

    "; + + var parsedHtml = parser.Parse(legacyHtml); + + Assert.AreEqual(expectedOutput, parsedHtml); + } + + private ApiRichTextMarkupParser BuildDefaultSut(Dictionary mockData) + { + var contentCacheMock = new Mock(); + + contentCacheMock.Setup(cc => cc.GetById(It.IsAny(), It.IsAny())) + .Returns((preview, key) => mockData[key].PublishedContent); + contentCacheMock.Setup(cc => cc.GetById(It.IsAny())) + .Returns(key => mockData[key].PublishedContent); + contentCacheMock.Setup(cc => cc.GetById(It.IsAny(), It.IsAny())) + .Returns((preview, udi) => mockData[((GuidUdi)udi).Guid].PublishedContent); + contentCacheMock.Setup(cc => cc.GetById(It.IsAny())) + .Returns(udi => mockData[((GuidUdi)udi).Guid].PublishedContent); + + var mediaCacheMock = new Mock(); + mediaCacheMock.Setup(cc => cc.GetById(It.IsAny(), It.IsAny())) + .Returns((preview, key) => mockData[key].PublishedContent); + mediaCacheMock.Setup(cc => cc.GetById(It.IsAny())) + .Returns(key => mockData[key].PublishedContent); + mediaCacheMock.Setup(cc => cc.GetById(It.IsAny(), It.IsAny())) + .Returns((preview, udi) => mockData[((GuidUdi)udi).Guid].PublishedContent); + mediaCacheMock.Setup(cc => cc.GetById(It.IsAny())) + .Returns(udi => mockData[((GuidUdi)udi).Guid].PublishedContent); + + var snapshotMock = new Mock(); + snapshotMock.SetupGet(ss => ss.Content) + .Returns(contentCacheMock.Object); + snapshotMock.SetupGet(ss => ss.Media) + .Returns(mediaCacheMock.Object); + + var snapShot = snapshotMock.Object; + + _publishedSnapshotAccessor = new Mock(); + _publishedSnapshotAccessor.Setup(psa => psa.TryGetPublishedSnapshot(out snapShot)) + .Returns(true); + + _apiMediaUrlProvider = new Mock(); + _apiMediaUrlProvider.Setup(mup => mup.GetUrl(It.IsAny())) + .Returns(ipc => mockData[ipc.Key].MediaUrl); + + _apiContentRouteBuilder = new Mock(); + _apiContentRouteBuilder.Setup(acrb => acrb.Build(It.IsAny(), It.IsAny())) + .Returns((content, culture) => mockData[content.Key].ApiContentRoute); + + return new ApiRichTextMarkupParser( + _apiContentRouteBuilder.Object, + _apiMediaUrlProvider.Object, + _publishedSnapshotAccessor.Object, + Mock.Of>()); + } + + private class MockData + { + private Mock _publishedContentMock = new Mock(); + private Mock _apiContentRouteMock = new Mock(); + private Mock _apiContentStartItem = new Mock(); + + public IPublishedContent PublishedContent => _publishedContentMock.Object; + + public IApiContentRoute ApiContentRoute => _apiContentRouteMock.Object; + + public string MediaUrl { get; set; } = string.Empty; + + public MockData() + { + _apiContentRouteMock.SetupGet(r => r.StartItem).Returns(_apiContentStartItem.Object); + } + + public MockData WithKey(Guid key) + { + _publishedContentMock.SetupGet(i => i.Key).Returns(key); + _apiContentStartItem.SetupGet(rsi => rsi.Id).Returns(key); + return this; + } + + public MockData WithRoutePath(string path) + { + _apiContentRouteMock.SetupGet(r => r.Path).Returns(path); + return this; + } + + public MockData WithRouteStartPath(string path) + { + _apiContentStartItem.SetupGet(rsi => rsi.Path).Returns(path); + return this; + } + + public MockData WithMediaUrl(string url) + { + MediaUrl = url; + return this; + } + } +} From 6069610f835f0a6537125f85c6c12daa63f36da1 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 2 Jul 2024 17:14:43 +0200 Subject: [PATCH 082/113] v14: Implement backoffice signalR hub (#16723) * Implement backoffice signalR hub * Fix test * Refactor to use object instead of string * Update src/Umbraco.Cms.Api.Management/Routing/BackofficeHub.cs Co-authored-by: Bjarke Berg --------- Co-authored-by: Bjarke Berg --- .../Routing/BackOfficeAreaRoutes.cs | 4 +--- src/Umbraco.Cms.Api.Management/Routing/BackofficeHub.cs | 8 ++++++++ src/Umbraco.Core/Constants-Web.cs | 1 + .../Routing/BackOfficeAreaRoutesTests.cs | 2 +- 4 files changed, 11 insertions(+), 4 deletions(-) create mode 100644 src/Umbraco.Cms.Api.Management/Routing/BackofficeHub.cs diff --git a/src/Umbraco.Cms.Api.Management/Routing/BackOfficeAreaRoutes.cs b/src/Umbraco.Cms.Api.Management/Routing/BackOfficeAreaRoutes.cs index a854be29f0..a12e1acb2e 100644 --- a/src/Umbraco.Cms.Api.Management/Routing/BackOfficeAreaRoutes.cs +++ b/src/Umbraco.Cms.Api.Management/Routing/BackOfficeAreaRoutes.cs @@ -7,8 +7,6 @@ using Umbraco.Cms.Core; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Hosting; using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Core.Web.Mvc; -using Umbraco.Cms.Web.Common.Controllers; using Umbraco.Cms.Web.Common.Routing; using Umbraco.Extensions; @@ -54,8 +52,8 @@ public sealed class BackOfficeAreaRoutes : IAreaRoutes case RuntimeLevel.Install: case RuntimeLevel.Upgrade: case RuntimeLevel.Run: - MapMinimalBackOffice(endpoints); + endpoints.MapHub(_umbracoPathSegment + Constants.Web.BackofficeSignalRHub); break; case RuntimeLevel.BootFailed: case RuntimeLevel.Unknown: diff --git a/src/Umbraco.Cms.Api.Management/Routing/BackofficeHub.cs b/src/Umbraco.Cms.Api.Management/Routing/BackofficeHub.cs new file mode 100644 index 0000000000..47a2471a4b --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Routing/BackofficeHub.cs @@ -0,0 +1,8 @@ +using Microsoft.AspNetCore.SignalR; + +namespace Umbraco.Cms.Api.Management.Routing; + +public class BackofficeHub : Hub +{ + public async Task SendPayload(object payload) => await Clients.All.SendAsync("payloadReceived", payload); +} diff --git a/src/Umbraco.Core/Constants-Web.cs b/src/Umbraco.Core/Constants-Web.cs index 283ec6eaa3..65b460ba69 100644 --- a/src/Umbraco.Core/Constants-Web.cs +++ b/src/Umbraco.Core/Constants-Web.cs @@ -56,6 +56,7 @@ public static partial class Constants /// The "base" path to the Management API /// public const string ManagementApiPath = "/management/api/"; + public const string BackofficeSignalRHub = "/backofficeHub"; public static class Routing { diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Routing/BackOfficeAreaRoutesTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Routing/BackOfficeAreaRoutesTests.cs index c444e591ab..e1a0638fc6 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Routing/BackOfficeAreaRoutesTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Routing/BackOfficeAreaRoutesTests.cs @@ -45,7 +45,7 @@ public class BackOfficeAreaRoutesTests var endpoints = new TestRouteBuilder(); routes.CreateRoutes(endpoints); - Assert.AreEqual(1, endpoints.DataSources.Count); + Assert.AreEqual(2, endpoints.DataSources.Count); var route = endpoints.DataSources.First(); Assert.AreEqual(2, route.Endpoints.Count); From 32fe23ba41e9d8acc9a36c6e90cad718bf723ac9 Mon Sep 17 00:00:00 2001 From: Lee Kelleher Date: Wed, 3 Jul 2024 10:25:44 +0100 Subject: [PATCH 083/113] V14: Expose `Umb-Notifications` HTTP header (#16728) Enables client-side dev env to access the custom header. --- .../BackOfficeCorsPolicyBuilderExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Cms.Api.Management/DependencyInjection/BackOfficeCorsPolicyBuilderExtensions.cs b/src/Umbraco.Cms.Api.Management/DependencyInjection/BackOfficeCorsPolicyBuilderExtensions.cs index da81e5de7f..0f8a57f5d8 100644 --- a/src/Umbraco.Cms.Api.Management/DependencyInjection/BackOfficeCorsPolicyBuilderExtensions.cs +++ b/src/Umbraco.Cms.Api.Management/DependencyInjection/BackOfficeCorsPolicyBuilderExtensions.cs @@ -34,7 +34,7 @@ internal static class BackOfficeCorsPolicyBuilderExtensions { policy .WithOrigins(customOrigin) - .WithExposedHeaders(Constants.Headers.Location, Constants.Headers.GeneratedResource) + .WithExposedHeaders(Constants.Headers.Location, Constants.Headers.GeneratedResource, Constants.Headers.Notifications) .AllowAnyHeader() .AllowAnyMethod() .AllowCredentials(); From 0a9df3c105434e865512d93e0a77c9f2f6fc32fe Mon Sep 17 00:00:00 2001 From: Lee Kelleher Date: Wed, 3 Jul 2024 11:54:31 +0100 Subject: [PATCH 084/113] V14: OpenAPI: Explicitly set `ByRelationTypeKey` endpoint name (#16729) This is because of an `operationId` name clash with `ByKeyRelationTypeController.ByKey`, ref: https://github.com/umbraco/Umbraco-CMS/blob/release-14.0.0/src/Umbraco.Cms.Api.Management/Controllers/RelationType/ByKeyRelationTypeController.cs#L27 This change attempts to ensure a unique `operationId` in the OpenAPI/Swagger JSON. --- .../ByRelationTypeKeyRelationController.cs | 4 +- src/Umbraco.Cms.Api.Management/OpenApi.json | 772 +++++++++--------- 2 files changed, 388 insertions(+), 388 deletions(-) diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Relation/ByRelationTypeKeyRelationController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Relation/ByRelationTypeKeyRelationController.cs index f0aee0317f..cde52b5884 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Relation/ByRelationTypeKeyRelationController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Relation/ByRelationTypeKeyRelationController.cs @@ -1,4 +1,4 @@ -using Asp.Versioning; +using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Umbraco.Cms.Api.Common.ViewModels.Pagination; @@ -29,7 +29,7 @@ public class ByRelationTypeKeyRelationController : RelationControllerBase /// /// Use case: On a relation type page you can see all created relations of this type. /// - [HttpGet("type/{id:guid}")] + [HttpGet("type/{id:guid}", Name = "GetRelationByRelationTypeId")] [MapToApiVersion("1.0")] [ProducesResponseType(typeof(PagedViewModel), StatusCodes.Status200OK)] [ProducesResponseType(typeof(PagedViewModel), StatusCodes.Status404NotFound)] diff --git a/src/Umbraco.Cms.Api.Management/OpenApi.json b/src/Umbraco.Cms.Api.Management/OpenApi.json index 11dee452ca..2799d1bd61 100644 --- a/src/Umbraco.Cms.Api.Management/OpenApi.json +++ b/src/Umbraco.Cms.Api.Management/OpenApi.json @@ -53,7 +53,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -199,7 +199,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -259,7 +259,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] }, @@ -368,7 +368,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] }, @@ -508,7 +508,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -639,7 +639,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -695,7 +695,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -811,7 +811,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -874,7 +874,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -909,7 +909,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -1055,7 +1055,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -1115,7 +1115,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] }, @@ -1224,7 +1224,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] }, @@ -1364,7 +1364,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -1441,7 +1441,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -1490,7 +1490,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -1549,7 +1549,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -1597,7 +1597,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -1668,7 +1668,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -1731,7 +1731,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -1793,7 +1793,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] }, @@ -1963,7 +1963,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -2023,7 +2023,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] }, @@ -2132,7 +2132,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] }, @@ -2272,7 +2272,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -2341,7 +2341,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -2483,7 +2483,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -2629,7 +2629,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -2678,7 +2678,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -2726,7 +2726,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -2789,7 +2789,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -2844,7 +2844,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -2990,7 +2990,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -3050,7 +3050,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] }, @@ -3159,7 +3159,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] }, @@ -3299,7 +3299,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -3415,7 +3415,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -3561,7 +3561,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -3621,7 +3621,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] }, @@ -3730,7 +3730,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] }, @@ -3870,7 +3870,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -3990,7 +3990,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -4039,7 +4039,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -4087,7 +4087,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -4158,7 +4158,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -4221,7 +4221,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -4367,7 +4367,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -4427,7 +4427,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] }, @@ -4510,7 +4510,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] }, @@ -4650,7 +4650,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -4728,7 +4728,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -4806,7 +4806,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -4883,7 +4883,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -5040,7 +5040,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -5101,7 +5101,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -5243,7 +5243,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -5385,7 +5385,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -5440,7 +5440,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -5533,7 +5533,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -5568,7 +5568,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -5714,7 +5714,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -5774,7 +5774,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] }, @@ -5883,7 +5883,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] }, @@ -6023,7 +6023,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -6169,7 +6169,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -6218,7 +6218,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -6277,7 +6277,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -6325,7 +6325,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -6396,7 +6396,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -6459,7 +6459,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -6558,7 +6558,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -6632,7 +6632,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -6750,7 +6750,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -6868,7 +6868,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -6997,7 +6997,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -7143,7 +7143,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -7203,7 +7203,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] }, @@ -7312,7 +7312,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] }, @@ -7452,7 +7452,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -7531,7 +7531,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -7662,7 +7662,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -7722,7 +7722,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] }, @@ -7888,7 +7888,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -8004,7 +8004,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -8115,7 +8115,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -8178,7 +8178,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] }, @@ -8292,7 +8292,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -8423,7 +8423,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] }, @@ -8506,7 +8506,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] }, @@ -8564,7 +8564,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] }, @@ -8678,7 +8678,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -8820,7 +8820,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -8962,7 +8962,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -9026,7 +9026,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -9090,7 +9090,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -9232,7 +9232,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -9374,7 +9374,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -9441,7 +9441,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -9476,7 +9476,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -9607,7 +9607,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -9659,7 +9659,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -9790,7 +9790,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -9839,7 +9839,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -9898,7 +9898,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -9972,7 +9972,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -10083,7 +10083,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -10157,7 +10157,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -10299,7 +10299,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -10362,7 +10362,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -10417,7 +10417,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -10465,7 +10465,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -10536,7 +10536,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -10599,7 +10599,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -10689,7 +10689,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -10723,7 +10723,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -10778,7 +10778,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -10837,7 +10837,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -10932,7 +10932,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -11048,7 +11048,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -11136,7 +11136,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -11213,7 +11213,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -11283,7 +11283,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -11335,7 +11335,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -11391,7 +11391,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -11512,7 +11512,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -11762,7 +11762,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -11794,7 +11794,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -11846,7 +11846,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] }, @@ -11990,7 +11990,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -12046,7 +12046,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] }, @@ -12154,7 +12154,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] }, @@ -12293,7 +12293,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -12348,7 +12348,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -12415,7 +12415,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -12510,7 +12510,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -12595,7 +12595,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -12650,7 +12650,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] }, @@ -12768,7 +12768,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -12827,7 +12827,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] }, @@ -12909,7 +12909,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -12965,7 +12965,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -13003,7 +13003,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -13041,7 +13041,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -13117,7 +13117,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -13176,7 +13176,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -13228,7 +13228,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -13287,7 +13287,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -13433,7 +13433,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -13493,7 +13493,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] }, @@ -13576,7 +13576,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] }, @@ -13716,7 +13716,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -13794,7 +13794,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -13871,7 +13871,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -14028,7 +14028,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -14089,7 +14089,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -14231,7 +14231,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -14373,7 +14373,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -14428,7 +14428,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -14521,7 +14521,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -14667,7 +14667,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -14727,7 +14727,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] }, @@ -14836,7 +14836,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] }, @@ -14976,7 +14976,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -15122,7 +15122,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -15170,7 +15170,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -15241,7 +15241,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -15304,7 +15304,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -15425,7 +15425,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -15474,7 +15474,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -15533,7 +15533,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -15679,7 +15679,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -15739,7 +15739,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] }, @@ -15848,7 +15848,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] }, @@ -15988,7 +15988,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -16067,7 +16067,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -16183,7 +16183,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -16294,7 +16294,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -16358,7 +16358,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -16422,7 +16422,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -16564,7 +16564,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -16631,7 +16631,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -16666,7 +16666,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -16797,7 +16797,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -16849,7 +16849,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -16980,7 +16980,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -17054,7 +17054,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -17165,7 +17165,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -17239,7 +17239,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -17381,7 +17381,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -17444,7 +17444,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -17499,7 +17499,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -17547,7 +17547,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -17618,7 +17618,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -17681,7 +17681,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -17730,7 +17730,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -17785,7 +17785,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] }, @@ -17903,7 +17903,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -17952,7 +17952,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] }, @@ -18061,7 +18061,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] }, @@ -18201,7 +18201,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -18256,7 +18256,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -18305,7 +18305,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -18364,7 +18364,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -18510,7 +18510,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -18570,7 +18570,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] }, @@ -18653,7 +18653,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] }, @@ -18793,7 +18793,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -18870,7 +18870,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -18996,7 +18996,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -19089,7 +19089,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -19144,7 +19144,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -19264,7 +19264,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -19313,7 +19313,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -19372,7 +19372,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -19518,7 +19518,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -19578,7 +19578,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] }, @@ -19687,7 +19687,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] }, @@ -19827,7 +19827,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -19969,7 +19969,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -20004,7 +20004,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -20135,7 +20135,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -20209,7 +20209,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -20244,7 +20244,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -20279,7 +20279,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -20331,7 +20331,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -20392,7 +20392,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -20502,7 +20502,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -20537,7 +20537,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -20592,7 +20592,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] }, @@ -20736,7 +20736,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -20796,7 +20796,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] }, @@ -20879,7 +20879,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] }, @@ -20993,7 +20993,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -21054,7 +21054,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -21109,7 +21109,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -21157,7 +21157,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -21303,7 +21303,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -21362,7 +21362,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] }, @@ -21470,7 +21470,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] }, @@ -21609,7 +21609,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -21765,7 +21765,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -21911,7 +21911,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -21970,7 +21970,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] }, @@ -22078,7 +22078,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -22133,7 +22133,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -22192,7 +22192,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -22239,7 +22239,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -22301,7 +22301,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -22356,7 +22356,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -22412,7 +22412,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -22447,7 +22447,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] }, @@ -22524,7 +22524,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -22586,7 +22586,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -22619,7 +22619,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -22652,7 +22652,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -22685,7 +22685,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -22713,7 +22713,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -22789,7 +22789,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -22853,7 +22853,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] }, @@ -22910,7 +22910,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -22945,7 +22945,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] }, @@ -23000,7 +23000,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -23049,7 +23049,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -23104,7 +23104,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -23164,7 +23164,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -23174,7 +23174,7 @@ "tags": [ "Relation" ], - "operationId": "GetRelationTypeById", + "operationId": "GetRelationByRelationTypeId", "parameters": [ { "name": "id", @@ -23242,7 +23242,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -23290,7 +23290,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -23436,7 +23436,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -23495,7 +23495,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] }, @@ -23603,7 +23603,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] }, @@ -23742,7 +23742,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -23898,7 +23898,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -24044,7 +24044,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -24103,7 +24103,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] }, @@ -24211,7 +24211,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -24258,7 +24258,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -24320,7 +24320,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -24375,7 +24375,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -24427,7 +24427,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -24508,7 +24508,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -24543,7 +24543,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -24648,7 +24648,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -24779,7 +24779,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -24967,7 +24967,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -24999,7 +24999,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -25031,7 +25031,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -25101,7 +25101,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -25149,7 +25149,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -25193,7 +25193,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -25252,7 +25252,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -25304,7 +25304,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -25352,7 +25352,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -25498,7 +25498,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -25557,7 +25557,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] }, @@ -25665,7 +25665,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] }, @@ -25804,7 +25804,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -25960,7 +25960,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -26106,7 +26106,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -26165,7 +26165,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] }, @@ -26273,7 +26273,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -26320,7 +26320,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -26382,7 +26382,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -26437,7 +26437,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -26510,7 +26510,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -26565,7 +26565,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -26600,7 +26600,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] }, @@ -26703,7 +26703,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -26752,7 +26752,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -26811,7 +26811,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -26957,7 +26957,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -27017,7 +27017,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] }, @@ -27126,7 +27126,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] }, @@ -27266,7 +27266,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -27301,7 +27301,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -27391,7 +27391,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -27426,7 +27426,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -27474,7 +27474,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -27537,7 +27537,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -27592,7 +27592,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -27697,7 +27697,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -27768,7 +27768,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] }, @@ -27862,7 +27862,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -27894,7 +27894,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -27994,7 +27994,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -28043,7 +28043,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -28166,7 +28166,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] }, @@ -28236,7 +28236,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] }, @@ -28342,7 +28342,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -28388,7 +28388,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -28479,7 +28479,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -28528,7 +28528,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -28633,7 +28633,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] }, @@ -28751,7 +28751,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] }, @@ -28804,7 +28804,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -28864,7 +28864,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] }, @@ -28947,7 +28947,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] }, @@ -29061,7 +29061,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -29186,7 +29186,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] }, @@ -29309,7 +29309,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -29437,7 +29437,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -29486,7 +29486,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -29632,7 +29632,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] }, @@ -29735,7 +29735,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] }, @@ -29802,7 +29802,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -29862,7 +29862,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] }, @@ -29971,7 +29971,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] }, @@ -30111,7 +30111,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -30174,7 +30174,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -30293,7 +30293,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -30353,7 +30353,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -30495,7 +30495,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -30617,7 +30617,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -30728,7 +30728,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] }, @@ -30868,7 +30868,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -30903,7 +30903,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -30935,7 +30935,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -30970,7 +30970,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -31072,7 +31072,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] }, @@ -31207,7 +31207,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] }, @@ -31275,7 +31275,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -31365,7 +31365,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -31455,7 +31455,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -31490,7 +31490,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -31525,7 +31525,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -31585,7 +31585,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -31648,7 +31648,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -31708,7 +31708,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -31839,7 +31839,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -31970,7 +31970,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -32116,7 +32116,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -32370,7 +32370,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -32583,7 +32583,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -32688,7 +32688,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -32737,7 +32737,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -32789,7 +32789,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] }, @@ -32933,7 +32933,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -32990,7 +32990,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] }, @@ -33099,7 +33099,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] }, @@ -33239,7 +33239,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -33291,7 +33291,7 @@ }, "security": [ { - "Backoffice User": [ ] + "Backoffice User": [] } ] } @@ -37540,7 +37540,7 @@ }, "actionParameters": { "type": "object", - "additionalProperties": { }, + "additionalProperties": {}, "nullable": true } }, @@ -37827,7 +37827,7 @@ }, "providerProperties": { "type": "object", - "additionalProperties": { }, + "additionalProperties": {}, "nullable": true } }, @@ -38162,7 +38162,7 @@ }, "extensions": { "type": "array", - "items": { } + "items": {} } }, "additionalProperties": false @@ -41591,7 +41591,7 @@ "nullable": true } }, - "additionalProperties": { } + "additionalProperties": {} }, "ProblemDetailsBuilderModel": { "type": "object", @@ -45151,7 +45151,7 @@ "authorizationCode": { "authorizationUrl": "/umbraco/management/api/v1/security/back-office/authorize", "tokenUrl": "/umbraco/management/api/v1/security/back-office/token", - "scopes": { } + "scopes": {} } } } From 4643b7dba37f187f1b3594eac4a41298db25ebe4 Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Thu, 4 Jul 2024 07:36:09 +0200 Subject: [PATCH 085/113] Support parsing object to DateTimeOffset and preserve timezone offset (#16732) --- .../Serialization/JsonObjectConverter.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Infrastructure/Serialization/JsonObjectConverter.cs b/src/Umbraco.Infrastructure/Serialization/JsonObjectConverter.cs index e1c0f5950d..d8c9778461 100644 --- a/src/Umbraco.Infrastructure/Serialization/JsonObjectConverter.cs +++ b/src/Umbraco.Infrastructure/Serialization/JsonObjectConverter.cs @@ -101,8 +101,9 @@ public sealed class JsonObjectConverter : JsonConverter JsonTokenType.Number when reader.TryGetInt32(out int i) => i, JsonTokenType.Number when reader.TryGetInt64(out long l) => l, JsonTokenType.Number => reader.GetDouble(), + JsonTokenType.String when reader.TryGetDateTimeOffset(out DateTimeOffset datetime) => datetime, JsonTokenType.String when reader.TryGetDateTime(out DateTime datetime) => datetime, - JsonTokenType.String => reader.GetString()!, + JsonTokenType.String => reader.GetString(), _ => JsonDocument.ParseValue(ref reader).RootElement.Clone() }; } From 33338add76270d59c52e9b73f801460c5201bda7 Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Thu, 4 Jul 2024 07:36:09 +0200 Subject: [PATCH 086/113] Support parsing object to DateTimeOffset and preserve timezone offset (#16732) (cherry picked from commit 4643b7dba37f187f1b3594eac4a41298db25ebe4) --- .../Serialization/JsonObjectConverter.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Infrastructure/Serialization/JsonObjectConverter.cs b/src/Umbraco.Infrastructure/Serialization/JsonObjectConverter.cs index e1c0f5950d..d8c9778461 100644 --- a/src/Umbraco.Infrastructure/Serialization/JsonObjectConverter.cs +++ b/src/Umbraco.Infrastructure/Serialization/JsonObjectConverter.cs @@ -101,8 +101,9 @@ public sealed class JsonObjectConverter : JsonConverter JsonTokenType.Number when reader.TryGetInt32(out int i) => i, JsonTokenType.Number when reader.TryGetInt64(out long l) => l, JsonTokenType.Number => reader.GetDouble(), + JsonTokenType.String when reader.TryGetDateTimeOffset(out DateTimeOffset datetime) => datetime, JsonTokenType.String when reader.TryGetDateTime(out DateTime datetime) => datetime, - JsonTokenType.String => reader.GetString()!, + JsonTokenType.String => reader.GetString(), _ => JsonDocument.ParseValue(ref reader).RootElement.Clone() }; } From ffa85ee3b548ea93d2ce1cc38700df8b8613dfcf Mon Sep 17 00:00:00 2001 From: Nhu Dinh <150406148+nhudinh0309@users.noreply.github.com> Date: Thu, 4 Jul 2024 16:57:55 +0700 Subject: [PATCH 087/113] V14 Added Content tests with Checkbox list (#16733) * Added Content tests with checkbox list * Bumped version of test helper * Make the Content tests run in the pipeline * Moved goToBackOffice to beforeEach * Removed the unnecessary file * Moved goToBackOffice to beforeEach * Make smoke test run in the pipeline --- .../package-lock.json | 26 +++--- .../Umbraco.Tests.AcceptanceTest/package.json | 4 +- .../Content/ContentWithCheckboxList.spec.ts | 85 +++++++++++++++++++ 3 files changed, 97 insertions(+), 18 deletions(-) create mode 100644 tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithCheckboxList.spec.ts diff --git a/tests/Umbraco.Tests.AcceptanceTest/package-lock.json b/tests/Umbraco.Tests.AcceptanceTest/package-lock.json index 7916e3cb54..f3fa4a9e36 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/package-lock.json +++ b/tests/Umbraco.Tests.AcceptanceTest/package-lock.json @@ -7,8 +7,8 @@ "name": "acceptancetest", "hasInstallScript": true, "dependencies": { - "@umbraco/json-models-builders": "^2.0.7", - "@umbraco/playwright-testhelpers": "^2.0.0-beta.63", + "@umbraco/json-models-builders": "^2.0.9", + "@umbraco/playwright-testhelpers": "^2.0.0-beta.65", "camelize": "^1.0.0", "dotenv": "^16.3.1", "faker": "^4.1.0", @@ -132,25 +132,19 @@ } }, "node_modules/@umbraco/json-models-builders": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/@umbraco/json-models-builders/-/json-models-builders-2.0.7.tgz", - "integrity": "sha512-roR5A+jzIFN9z1BhogMGOEzSzoR8jOrIYIAevT7EnyS3H3OM0m0uREgvjYCQo0+QMfVws4zq4Ydjx2TIfGYvlQ==", + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/@umbraco/json-models-builders/-/json-models-builders-2.0.9.tgz", + "integrity": "sha512-p6LjcE38WsFCvLtRRRVOCuMvris3OXeoueFu0FZBOHk2r7PXiqYCBUls/KbKxqpixzVDAb48RBd1hV7sKPcm5A==", "dependencies": { - "camelize": "^1.0.1", - "faker": "^6.6.6" + "camelize": "^1.0.1" } }, - "node_modules/@umbraco/json-models-builders/node_modules/faker": { - "version": "6.6.6", - "resolved": "https://registry.npmjs.org/faker/-/faker-6.6.6.tgz", - "integrity": "sha512-9tCqYEDHI5RYFQigXFwF1hnCwcWCOJl/hmll0lr5D2Ljjb0o4wphb69wikeJDz5qCEzXCoPvG6ss5SDP6IfOdg==" - }, "node_modules/@umbraco/playwright-testhelpers": { - "version": "2.0.0-beta.63", - "resolved": "https://registry.npmjs.org/@umbraco/playwright-testhelpers/-/playwright-testhelpers-2.0.0-beta.63.tgz", - "integrity": "sha512-fLXUcWNJupfGKkD6zOGg6WcU5cmqQ6gQkyIyG+UsKSrkgCxK23+N5LrOz2OVp2NZ8GQ8kB5pJ4izvCp+yMMOnA==", + "version": "2.0.0-beta.65", + "resolved": "https://registry.npmjs.org/@umbraco/playwright-testhelpers/-/playwright-testhelpers-2.0.0-beta.65.tgz", + "integrity": "sha512-plSD/4hhVaMl2TItAaBOUQyuy0Qo5rW3EGIF0TvL3a01s6hNoW1DrOCZhWsOOsMTkgf+oScLEsVIBMk0uDLQrg==", "dependencies": { - "@umbraco/json-models-builders": "2.0.7", + "@umbraco/json-models-builders": "2.0.9", "camelize": "^1.0.0", "faker": "^4.1.0", "form-data": "^4.0.0", diff --git a/tests/Umbraco.Tests.AcceptanceTest/package.json b/tests/Umbraco.Tests.AcceptanceTest/package.json index 8126ebf602..f0f2a60b94 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/package.json +++ b/tests/Umbraco.Tests.AcceptanceTest/package.json @@ -21,8 +21,8 @@ "wait-on": "^7.2.0" }, "dependencies": { - "@umbraco/json-models-builders": "^2.0.7", - "@umbraco/playwright-testhelpers": "^2.0.0-beta.63", + "@umbraco/json-models-builders": "^2.0.9", + "@umbraco/playwright-testhelpers": "^2.0.0-beta.65", "camelize": "^1.0.0", "dotenv": "^16.3.1", "faker": "^4.1.0", diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithCheckboxList.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithCheckboxList.spec.ts new file mode 100644 index 0000000000..71706203ea --- /dev/null +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithCheckboxList.spec.ts @@ -0,0 +1,85 @@ +import { ConstantHelper, test, AliasHelper } from '@umbraco/playwright-testhelpers'; +import {expect} from "@playwright/test"; + +const contentName = 'TestContent'; +const documentTypeName = 'TestDocumentTypeForContent'; +const dataTypeName = 'Checkbox list'; + +test.beforeEach(async ({umbracoApi, umbracoUi}) => { + await umbracoApi.documentType.ensureNameNotExists(documentTypeName); + await umbracoApi.document.ensureNameNotExists(contentName); + await umbracoUi.goToBackOffice(); +}); + +test.afterEach(async ({umbracoApi}) => { + await umbracoApi.document.ensureNameNotExists(contentName); + await umbracoApi.documentType.ensureNameNotExists(documentTypeName); +}); + +test('can create content with the checkbox list data type', async ({umbracoApi, umbracoUi}) => { + // Arrange + const dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); + await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, dataTypeName, dataTypeData.id); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + + // Act + await umbracoUi.content.clickActionsMenuAtRoot(); + await umbracoUi.content.clickCreateButton(); + await umbracoUi.content.chooseDocumentType(documentTypeName); + await umbracoUi.content.enterContentName(contentName); + await umbracoUi.content.clickSaveButton(); + + // Assert + await umbracoUi.content.isSuccessNotificationVisible(); + expect(await umbracoApi.document.doesNameExist(contentName)).toBeTruthy(); + const contentData = await umbracoApi.document.getByName(contentName); + expect(contentData.values).toEqual([]); +}); + +test('can publish content with the checkbox list data type', async ({umbracoApi, umbracoUi}) => { + // Arrange + const dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); + await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, dataTypeName, dataTypeData.id); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + + // Act + await umbracoUi.content.clickActionsMenuAtRoot(); + await umbracoUi.content.clickCreateButton(); + await umbracoUi.content.chooseDocumentType(documentTypeName); + await umbracoUi.content.enterContentName(contentName); + await umbracoUi.content.clickSaveAndPublishButton(); + + // Assert + await umbracoUi.content.doesSuccessNotificationsHaveCount(2); + expect(await umbracoApi.document.doesNameExist(contentName)).toBeTruthy(); + const contentData = await umbracoApi.document.getByName(contentName); + expect(contentData.values).toEqual([]); +}); + +test('can create content with the custom approved color data type', async ({umbracoApi, umbracoUi}) => { + // Arrange + const customDataTypeName = 'CustomCheckboxList'; + const optionValues = ['testOption1', 'testOption2']; + const customDataTypeId = await umbracoApi.dataType.createCheckboxListDataType(customDataTypeName, optionValues); + await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, customDataTypeName, customDataTypeId); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + + // Act + await umbracoUi.content.clickActionsMenuAtRoot(); + await umbracoUi.content.clickCreateButton(); + await umbracoUi.content.chooseDocumentType(documentTypeName); + await umbracoUi.content.enterContentName(contentName); + await umbracoUi.content.chooseCheckboxListOption(optionValues[0]); + await umbracoUi.content.clickSaveAndPublishButton(); + + // Assert + await umbracoUi.content.doesSuccessNotificationsHaveCount(2); + expect(await umbracoApi.document.doesNameExist(contentName)).toBeTruthy(); + const contentData = await umbracoApi.document.getByName(contentName); + expect(contentData.values[0].alias).toEqual(AliasHelper.toAlias(customDataTypeName)); + expect(contentData.values[0].value).toEqual([optionValues[0]]); + + // Clean + await umbracoApi.dataType.ensureNameNotExists(customDataTypeName); +}); + From 36d00bd9673e76996fcfbee943f03c39eea3be7e Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Fri, 5 Jul 2024 09:05:23 +0200 Subject: [PATCH 088/113] add `type/improvement` to list of bug fixes --- .github/release.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/release.yml b/.github/release.yml index 9b9f32cdde..379dc1559d 100644 --- a/.github/release.yml +++ b/.github/release.yml @@ -17,6 +17,7 @@ changelog: labels: - type/bug - category/bug + - type/improvement - title: 📄 Documentation labels: - category/documentation From d3fe69afe4ed891abaf2a2215a5051513c969f97 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Fri, 5 Jul 2024 09:05:23 +0200 Subject: [PATCH 089/113] add `type/improvement` to list of bug fixes --- .github/release.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/release.yml b/.github/release.yml index 9b9f32cdde..379dc1559d 100644 --- a/.github/release.yml +++ b/.github/release.yml @@ -17,6 +17,7 @@ changelog: labels: - type/bug - category/bug + - type/improvement - title: 📄 Documentation labels: - category/documentation From ff3b1d678b5f76ba299e2c406c44c7c5f617a027 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Fri, 5 Jul 2024 09:10:06 +0200 Subject: [PATCH 090/113] introduce an "other changes" category --- .github/release.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/release.yml b/.github/release.yml index 379dc1559d..e65616aafe 100644 --- a/.github/release.yml +++ b/.github/release.yml @@ -31,5 +31,8 @@ changelog: labels: - category/accessibility - title: 🚀 New Features + labels: + - type/feature + - title: Other Changes labels: - '*' From a79f2705dadeaf691aa2447ecfa4582f696ab79d Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Fri, 5 Jul 2024 09:10:06 +0200 Subject: [PATCH 091/113] introduce an "other changes" category --- .github/release.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/release.yml b/.github/release.yml index 379dc1559d..e65616aafe 100644 --- a/.github/release.yml +++ b/.github/release.yml @@ -31,5 +31,8 @@ changelog: labels: - category/accessibility - title: 🚀 New Features + labels: + - type/feature + - title: Other Changes labels: - '*' From 4283f34fd14cb7b17c12cb3c17c2981f7495f0e1 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Fri, 5 Jul 2024 11:05:18 +0200 Subject: [PATCH 092/113] update backoffice submodule --- src/Umbraco.Web.UI.Client | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client b/src/Umbraco.Web.UI.Client index 9ec8c79227..2d42c53a00 160000 --- a/src/Umbraco.Web.UI.Client +++ b/src/Umbraco.Web.UI.Client @@ -1 +1 @@ -Subproject commit 9ec8c79227312d97e5dcd6b61a6209752e48d76f +Subproject commit 2d42c53a0012647be9174f20bc34cdba2b81de67 From 8681a4e4e8f5ce492380c86cb8b2f2b795867f36 Mon Sep 17 00:00:00 2001 From: Mole Date: Fri, 5 Jul 2024 11:34:25 +0200 Subject: [PATCH 093/113] Move ReservedFieldNames to type configuration endpoints (#16735) * Move ReservedFieldNames to type configuration endpoints * Update obsoletion messages * Update src/Umbraco.Cms.Api.Management/Controllers/MemberType/ConfigurationMemberTypeController.cs Co-authored-by: Elitsa Marinovska <21998037+elit0451@users.noreply.github.com> --------- Co-authored-by: Elitsa Marinovska <21998037+elit0451@users.noreply.github.com> --- .../ConfigurationDocumentTypeController.cs | 35 +++-- .../ConfigurationMediaTypeController.cs | 31 +++++ .../ConfigurationMemberTypeController.cs | 30 ++++ .../ConfigurationPresentationFactory.cs | 52 ++++++- .../IConfigurationPresentationFactory.cs | 12 ++ src/Umbraco.Cms.Api.Management/OpenApi.json | 129 ++++++++++++++++-- .../DocumentConfigurationResponseModel.cs | 1 + .../DocumentTypeConfigurationResponseModel.cs | 2 + .../Media/MediaConfigurationResponseModel.cs | 1 + .../MediaType/MediaTypeConfigurationModel.cs | 6 + .../MemberConfigurationResponseModel.cs | 1 + .../MemberTypeConfigurationResponseModel.cs | 6 + 12 files changed, 284 insertions(+), 22 deletions(-) create mode 100644 src/Umbraco.Cms.Api.Management/Controllers/MediaType/ConfigurationMediaTypeController.cs create mode 100644 src/Umbraco.Cms.Api.Management/Controllers/MemberType/ConfigurationMemberTypeController.cs create mode 100644 src/Umbraco.Cms.Api.Management/ViewModels/MediaType/MediaTypeConfigurationModel.cs create mode 100644 src/Umbraco.Cms.Api.Management/ViewModels/MemberType/MemberTypeConfigurationResponseModel.cs diff --git a/src/Umbraco.Cms.Api.Management/Controllers/DocumentType/ConfigurationDocumentTypeController.cs b/src/Umbraco.Cms.Api.Management/Controllers/DocumentType/ConfigurationDocumentTypeController.cs index e7a785cf88..c1a4e41a75 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/DocumentType/ConfigurationDocumentTypeController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/DocumentType/ConfigurationDocumentTypeController.cs @@ -2,9 +2,12 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; +using Umbraco.Cms.Api.Management.Factories; using Umbraco.Cms.Api.Management.ViewModels.DocumentType; using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Features; using Umbraco.Cms.Web.Common.Authorization; @@ -14,18 +17,31 @@ namespace Umbraco.Cms.Api.Management.Controllers.DocumentType; [Authorize(Policy = AuthorizationPolicies.TreeAccessDocumentTypes)] public class ConfigurationDocumentTypeController : DocumentTypeControllerBase { - private readonly UmbracoFeatures _umbracoFeatures; - private readonly DataTypesSettings _dataTypesSettings; - private readonly SegmentSettings _segmentSettings; + private readonly IConfigurationPresentationFactory _configurationPresentationFactory; + [ActivatorUtilitiesConstructor] + public ConfigurationDocumentTypeController(IConfigurationPresentationFactory configurationPresentationFactory) + { + _configurationPresentationFactory = configurationPresentationFactory; + } + + [Obsolete("Use the constructor that only accepts IConfigurationPresentationFactory, scheduled for removal in V16")] + public ConfigurationDocumentTypeController( + UmbracoFeatures umbracoFeatures, + IOptionsSnapshot dataTypesSettings, + IOptionsSnapshot segmentSettings, + IConfigurationPresentationFactory configurationPresentationFactory) + : this(configurationPresentationFactory) + { + } + + [Obsolete("Use the constructor that only accepts IConfigurationPresentationFactory, scheduled for removal in V16")] public ConfigurationDocumentTypeController( UmbracoFeatures umbracoFeatures, IOptionsSnapshot dataTypesSettings, IOptionsSnapshot segmentSettings) + : this(StaticServiceProvider.Instance.GetRequiredService()) { - _umbracoFeatures = umbracoFeatures; - _dataTypesSettings = dataTypesSettings.Value; - _segmentSettings = segmentSettings.Value; } [HttpGet("configuration")] @@ -33,12 +49,7 @@ public class ConfigurationDocumentTypeController : DocumentTypeControllerBase [ProducesResponseType(typeof(DocumentTypeConfigurationResponseModel), StatusCodes.Status200OK)] public Task Configuration(CancellationToken cancellationToken) { - var responseModel = new DocumentTypeConfigurationResponseModel - { - DataTypesCanBeChanged = _dataTypesSettings.CanBeChanged, - DisableTemplates = _umbracoFeatures.Disabled.DisableTemplates, - UseSegments = _segmentSettings.Enabled, - }; + DocumentTypeConfigurationResponseModel responseModel = _configurationPresentationFactory.CreateDocumentTypeConfigurationResponseModel(); return Task.FromResult(Ok(responseModel)); } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/MediaType/ConfigurationMediaTypeController.cs b/src/Umbraco.Cms.Api.Management/Controllers/MediaType/ConfigurationMediaTypeController.cs new file mode 100644 index 0000000000..25447c60a1 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Controllers/MediaType/ConfigurationMediaTypeController.cs @@ -0,0 +1,31 @@ +using Asp.Versioning; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Api.Management.Factories; +using Umbraco.Cms.Api.Management.ViewModels.MediaType; +using Umbraco.Cms.Web.Common.Authorization; + +namespace Umbraco.Cms.Api.Management.Controllers.MediaType; + +[ApiVersion("1.0")] +[Authorize(Policy = AuthorizationPolicies.TreeAccessMediaTypes)] +public class ConfigurationMediaTypeController : MediaTypeControllerBase +{ + private readonly IConfigurationPresentationFactory _configurationPresentationFactory; + + public ConfigurationMediaTypeController(IConfigurationPresentationFactory configurationPresentationFactory) + { + _configurationPresentationFactory = configurationPresentationFactory; + } + + [HttpGet("configuration")] + [MapToApiVersion("1.0")] + [ProducesResponseType(typeof(MediaTypeConfigurationResponseModel), StatusCodes.Status200OK)] + public Task Configuration(CancellationToken cancellationToken) + { + MediaTypeConfigurationResponseModel responseModel = _configurationPresentationFactory.CreateMediaTypeConfigurationResponseModel(); + + return Task.FromResult(Ok(responseModel)); + } +} diff --git a/src/Umbraco.Cms.Api.Management/Controllers/MemberType/ConfigurationMemberTypeController.cs b/src/Umbraco.Cms.Api.Management/Controllers/MemberType/ConfigurationMemberTypeController.cs new file mode 100644 index 0000000000..141dbd80ee --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Controllers/MemberType/ConfigurationMemberTypeController.cs @@ -0,0 +1,30 @@ +using Asp.Versioning; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Api.Management.Factories; +using Umbraco.Cms.Api.Management.ViewModels.MemberType; +using Umbraco.Cms.Web.Common.Authorization; + +namespace Umbraco.Cms.Api.Management.Controllers.MemberType; + +[ApiVersion("1.0")] +public class ConfigurationMemberTypeController : MemberTypeControllerBase +{ + private readonly IConfigurationPresentationFactory _configurationPresentationFactory; + + public ConfigurationMemberTypeController(IConfigurationPresentationFactory configurationPresentationFactory) + { + _configurationPresentationFactory = configurationPresentationFactory; + } + + [HttpGet("configuration")] + [MapToApiVersion("1.0")] + [ProducesResponseType(typeof(MemberTypeConfigurationResponseModel), StatusCodes.Status200OK)] + public Task Configuration(CancellationToken cancellationToken) + { + MemberTypeConfigurationResponseModel responseModel = _configurationPresentationFactory.CreateMemberTypeConfigurationResponseModel(); + + return Task.FromResult(Ok(responseModel)); + } +} diff --git a/src/Umbraco.Cms.Api.Management/Factories/ConfigurationPresentationFactory.cs b/src/Umbraco.Cms.Api.Management/Factories/ConfigurationPresentationFactory.cs index b8580083c5..7efc4ff90f 100644 --- a/src/Umbraco.Cms.Api.Management/Factories/ConfigurationPresentationFactory.cs +++ b/src/Umbraco.Cms.Api.Management/Factories/ConfigurationPresentationFactory.cs @@ -1,8 +1,14 @@ -using Microsoft.Extensions.Options; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; using Umbraco.Cms.Api.Management.ViewModels.Document; +using Umbraco.Cms.Api.Management.ViewModels.DocumentType; using Umbraco.Cms.Api.Management.ViewModels.Media; +using Umbraco.Cms.Api.Management.ViewModels.MediaType; using Umbraco.Cms.Api.Management.ViewModels.Member; +using Umbraco.Cms.Api.Management.ViewModels.MemberType; using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.DependencyInjection; +using Umbraco.Cms.Core.Features; using Umbraco.Cms.Core.Services; namespace Umbraco.Cms.Api.Management.Factories; @@ -10,19 +16,40 @@ namespace Umbraco.Cms.Api.Management.Factories; public class ConfigurationPresentationFactory : IConfigurationPresentationFactory { private readonly IReservedFieldNamesService _reservedFieldNamesService; + private readonly UmbracoFeatures _umbracoFeatures; + private readonly DataTypesSettings _dataTypesSettings; private readonly ContentSettings _contentSettings; private readonly SegmentSettings _segmentSettings; public ConfigurationPresentationFactory( IReservedFieldNamesService reservedFieldNamesService, IOptions contentSettings, - IOptions segmentSettings) + IOptions segmentSettings, + IOptions dataTypesSettings, + UmbracoFeatures umbracoFeatures) { _reservedFieldNamesService = reservedFieldNamesService; + _umbracoFeatures = umbracoFeatures; + _dataTypesSettings = dataTypesSettings.Value; _contentSettings = contentSettings.Value; _segmentSettings = segmentSettings.Value; } + [Obsolete("Use the constructor with all dependencies")] + public ConfigurationPresentationFactory( + IReservedFieldNamesService reservedFieldNamesService, + IOptions contentSettings, + IOptions segmentSettings) + : this( + reservedFieldNamesService, + contentSettings, + segmentSettings, + StaticServiceProvider.Instance.GetRequiredService>(), + StaticServiceProvider.Instance.GetRequiredService() + ) + { + } + public DocumentConfigurationResponseModel CreateDocumentConfigurationResponseModel() => new() { @@ -33,12 +60,27 @@ public class ConfigurationPresentationFactory : IConfigurationPresentationFactor ReservedFieldNames = _reservedFieldNamesService.GetDocumentReservedFieldNames(), }; + public DocumentTypeConfigurationResponseModel CreateDocumentTypeConfigurationResponseModel() => + new() + { + DataTypesCanBeChanged = _dataTypesSettings.CanBeChanged, + DisableTemplates = _umbracoFeatures.Disabled.DisableTemplates, + UseSegments = _segmentSettings.Enabled, + ReservedFieldNames = _reservedFieldNamesService.GetDocumentReservedFieldNames(), + }; + public MemberConfigurationResponseModel CreateMemberConfigurationResponseModel() => new() { ReservedFieldNames = _reservedFieldNamesService.GetMemberReservedFieldNames(), }; + public MemberTypeConfigurationResponseModel CreateMemberTypeConfigurationResponseModel() => + new() + { + ReservedFieldNames = _reservedFieldNamesService.GetMemberReservedFieldNames(), + }; + public MediaConfigurationResponseModel CreateMediaConfigurationResponseModel() => new() { @@ -46,4 +88,10 @@ public class ConfigurationPresentationFactory : IConfigurationPresentationFactor DisableUnpublishWhenReferenced = _contentSettings.DisableUnpublishWhenReferenced, ReservedFieldNames = _reservedFieldNamesService.GetMediaReservedFieldNames(), }; + + public MediaTypeConfigurationResponseModel CreateMediaTypeConfigurationResponseModel() => + new() + { + ReservedFieldNames = _reservedFieldNamesService.GetMediaReservedFieldNames(), + }; } diff --git a/src/Umbraco.Cms.Api.Management/Factories/IConfigurationPresentationFactory.cs b/src/Umbraco.Cms.Api.Management/Factories/IConfigurationPresentationFactory.cs index 920a7fd7b8..3914a0e058 100644 --- a/src/Umbraco.Cms.Api.Management/Factories/IConfigurationPresentationFactory.cs +++ b/src/Umbraco.Cms.Api.Management/Factories/IConfigurationPresentationFactory.cs @@ -1,6 +1,9 @@ using Umbraco.Cms.Api.Management.ViewModels.Document; +using Umbraco.Cms.Api.Management.ViewModels.DocumentType; using Umbraco.Cms.Api.Management.ViewModels.Media; +using Umbraco.Cms.Api.Management.ViewModels.MediaType; using Umbraco.Cms.Api.Management.ViewModels.Member; +using Umbraco.Cms.Api.Management.ViewModels.MemberType; namespace Umbraco.Cms.Api.Management.Factories; @@ -8,7 +11,16 @@ public interface IConfigurationPresentationFactory { DocumentConfigurationResponseModel CreateDocumentConfigurationResponseModel(); + DocumentTypeConfigurationResponseModel CreateDocumentTypeConfigurationResponseModel() + => throw new NotImplementedException(); + MemberConfigurationResponseModel CreateMemberConfigurationResponseModel(); + MemberTypeConfigurationResponseModel CreateMemberTypeConfigurationResponseModel() + => throw new NotImplementedException(); + MediaConfigurationResponseModel CreateMediaConfigurationResponseModel(); + + MediaTypeConfigurationResponseModel CreateMediaTypeConfigurationResponseModel() + => throw new NotImplementedException(); } diff --git a/src/Umbraco.Cms.Api.Management/OpenApi.json b/src/Umbraco.Cms.Api.Management/OpenApi.json index 2799d1bd61..77e006ffaa 100644 --- a/src/Umbraco.Cms.Api.Management/OpenApi.json +++ b/src/Umbraco.Cms.Api.Management/OpenApi.json @@ -14526,6 +14526,41 @@ ] } }, + "/umbraco/management/api/v1/media-type/configuration": { + "get": { + "tags": [ + "Media Type" + ], + "operationId": "GetMediaTypeConfiguration", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/MediaTypeConfigurationResponseModel" + } + ] + } + } + } + }, + "401": { + "description": "The resource is protected and requires an authentication token" + }, + "403": { + "description": "The authenticated user do not have access to this resource" + } + }, + "security": [ + { + "Backoffice User": [] + } + ] + } + }, "/umbraco/management/api/v1/media-type/folder": { "post": { "tags": [ @@ -19094,6 +19129,41 @@ ] } }, + "/umbraco/management/api/v1/member-type/configuration": { + "get": { + "tags": [ + "Member Type" + ], + "operationId": "GetMemberTypeConfiguration", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/MemberTypeConfigurationResponseModel" + } + ] + } + } + } + }, + "401": { + "description": "The resource is protected and requires an authentication token" + }, + "403": { + "description": "The authenticated user do not have access to this resource" + } + }, + "security": [ + { + "Backoffice User": [] + } + ] + } + }, "/umbraco/management/api/v1/tree/member-type/root": { "get": { "tags": [ @@ -36124,7 +36194,8 @@ "type": "array", "items": { "type": "string" - } + }, + "deprecated": true } }, "additionalProperties": false @@ -36551,6 +36622,7 @@ "required": [ "dataTypesCanBeChanged", "disableTemplates", + "reservedFieldNames", "useSegments" ], "type": "object", @@ -36563,6 +36635,13 @@ }, "useSegments": { "type": "boolean" + }, + "reservedFieldNames": { + "uniqueItems": true, + "type": "array", + "items": { + "type": "string" + } } }, "additionalProperties": false @@ -37540,7 +37619,7 @@ }, "actionParameters": { "type": "object", - "additionalProperties": {}, + "additionalProperties": { }, "nullable": true } }, @@ -37827,7 +37906,7 @@ }, "providerProperties": { "type": "object", - "additionalProperties": {}, + "additionalProperties": { }, "nullable": true } }, @@ -38162,7 +38241,7 @@ }, "extensions": { "type": "array", - "items": {} + "items": { } } }, "additionalProperties": false @@ -38238,7 +38317,8 @@ "type": "array", "items": { "type": "string" - } + }, + "deprecated": true } }, "additionalProperties": false @@ -38548,6 +38628,22 @@ }, "additionalProperties": false }, + "MediaTypeConfigurationResponseModel": { + "required": [ + "reservedFieldNames" + ], + "type": "object", + "properties": { + "reservedFieldNames": { + "uniqueItems": true, + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + }, "MediaTypeItemResponseModel": { "required": [ "id", @@ -38999,7 +39095,8 @@ "type": "array", "items": { "type": "string" - } + }, + "deprecated": true } }, "additionalProperties": false @@ -39230,6 +39327,22 @@ }, "additionalProperties": false }, + "MemberTypeConfigurationResponseModel": { + "required": [ + "reservedFieldNames" + ], + "type": "object", + "properties": { + "reservedFieldNames": { + "uniqueItems": true, + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + }, "MemberTypeItemResponseModel": { "required": [ "id", @@ -41591,7 +41704,7 @@ "nullable": true } }, - "additionalProperties": {} + "additionalProperties": { } }, "ProblemDetailsBuilderModel": { "type": "object", @@ -45151,7 +45264,7 @@ "authorizationCode": { "authorizationUrl": "/umbraco/management/api/v1/security/back-office/authorize", "tokenUrl": "/umbraco/management/api/v1/security/back-office/token", - "scopes": {} + "scopes": { } } } } diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/Document/DocumentConfigurationResponseModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/Document/DocumentConfigurationResponseModel.cs index 100f3ec3c9..f6673bcd5b 100644 --- a/src/Umbraco.Cms.Api.Management/ViewModels/Document/DocumentConfigurationResponseModel.cs +++ b/src/Umbraco.Cms.Api.Management/ViewModels/Document/DocumentConfigurationResponseModel.cs @@ -10,5 +10,6 @@ public class DocumentConfigurationResponseModel public required bool AllowNonExistingSegmentsCreation { get; set; } + [Obsolete("Use DocumentTypeConfigurationResponseModel.ReservedFieldNames from the ConfigurationDocumentTypeController endpoint instead.")] public required ISet ReservedFieldNames { get; set; } } diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/DocumentType/DocumentTypeConfigurationResponseModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/DocumentType/DocumentTypeConfigurationResponseModel.cs index c575cc7bc1..802519851a 100644 --- a/src/Umbraco.Cms.Api.Management/ViewModels/DocumentType/DocumentTypeConfigurationResponseModel.cs +++ b/src/Umbraco.Cms.Api.Management/ViewModels/DocumentType/DocumentTypeConfigurationResponseModel.cs @@ -9,4 +9,6 @@ public class DocumentTypeConfigurationResponseModel public required bool DisableTemplates { get; set; } public required bool UseSegments { get; set; } + + public required ISet ReservedFieldNames { get; set; } } diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/Media/MediaConfigurationResponseModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/Media/MediaConfigurationResponseModel.cs index e1a15250a6..ff723b658f 100644 --- a/src/Umbraco.Cms.Api.Management/ViewModels/Media/MediaConfigurationResponseModel.cs +++ b/src/Umbraco.Cms.Api.Management/ViewModels/Media/MediaConfigurationResponseModel.cs @@ -6,5 +6,6 @@ public class MediaConfigurationResponseModel public required bool DisableUnpublishWhenReferenced { get; set; } + [Obsolete("Use MediaTypeConfigurationResponseModel.ReservedFieldNames from the ConfigurationMediaTypeController endpoint instead.")] public required ISet ReservedFieldNames { get; set; } } diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/MediaType/MediaTypeConfigurationModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/MediaType/MediaTypeConfigurationModel.cs new file mode 100644 index 0000000000..313dc7643e --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/ViewModels/MediaType/MediaTypeConfigurationModel.cs @@ -0,0 +1,6 @@ +namespace Umbraco.Cms.Api.Management.ViewModels.MediaType; + +public class MediaTypeConfigurationResponseModel +{ + public required ISet ReservedFieldNames { get; set; } +} diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/Member/MemberConfigurationResponseModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/Member/MemberConfigurationResponseModel.cs index 5faeed7fd8..a3434583bd 100644 --- a/src/Umbraco.Cms.Api.Management/ViewModels/Member/MemberConfigurationResponseModel.cs +++ b/src/Umbraco.Cms.Api.Management/ViewModels/Member/MemberConfigurationResponseModel.cs @@ -2,5 +2,6 @@ public class MemberConfigurationResponseModel { + [Obsolete("Use MemberTypeConfigurationResponseModel.ReservedFieldNames from the ConfigurationMemberTypeController endpoint instead.")] public required ISet ReservedFieldNames { get; set; } } diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/MemberType/MemberTypeConfigurationResponseModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/MemberType/MemberTypeConfigurationResponseModel.cs new file mode 100644 index 0000000000..85e44e93b7 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/ViewModels/MemberType/MemberTypeConfigurationResponseModel.cs @@ -0,0 +1,6 @@ +namespace Umbraco.Cms.Api.Management.ViewModels.MemberType; + +public class MemberTypeConfigurationResponseModel +{ + public required ISet ReservedFieldNames { get; set; } +} From 58a766293e65e226bceb2ff36f22319be92697b0 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Fri, 5 Jul 2024 13:29:59 +0200 Subject: [PATCH 094/113] update backoffice submodule --- src/Umbraco.Web.UI.Client | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client b/src/Umbraco.Web.UI.Client index 2d42c53a00..98b657dba6 160000 --- a/src/Umbraco.Web.UI.Client +++ b/src/Umbraco.Web.UI.Client @@ -1 +1 @@ -Subproject commit 2d42c53a0012647be9174f20bc34cdba2b81de67 +Subproject commit 98b657dba652da774314e70e0bfeed0aa4ee5a48 From 26084d8bb5781de5d1804d2eff3e23b031e19c86 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Fri, 5 Jul 2024 13:45:27 +0200 Subject: [PATCH 095/113] update backoffice submodule --- src/Umbraco.Web.UI.Client | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client b/src/Umbraco.Web.UI.Client index 9ec8c79227..f43c9496cf 160000 --- a/src/Umbraco.Web.UI.Client +++ b/src/Umbraco.Web.UI.Client @@ -1 +1 @@ -Subproject commit 9ec8c79227312d97e5dcd6b61a6209752e48d76f +Subproject commit f43c9496cfcb32ef7fb50b8015777b823242d008 From f6deaca45204fb598d1ddebd4259d3e939f5926c Mon Sep 17 00:00:00 2001 From: Elitsa Date: Mon, 8 Jul 2024 09:27:36 +0300 Subject: [PATCH 096/113] Removing override of OnTransformingIndexValues from DeliveryApiContentIndex --- src/Umbraco.Examine.Lucene/DeliveryApiContentIndex.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/Umbraco.Examine.Lucene/DeliveryApiContentIndex.cs b/src/Umbraco.Examine.Lucene/DeliveryApiContentIndex.cs index c572fc85f7..367817f368 100644 --- a/src/Umbraco.Examine.Lucene/DeliveryApiContentIndex.cs +++ b/src/Umbraco.Examine.Lucene/DeliveryApiContentIndex.cs @@ -134,10 +134,4 @@ public class DeliveryApiContentIndex : UmbracoExamineIndex return (compositeIdModel.Id?.ToString(CultureInfo.InvariantCulture), compositeIdModel.Culture); } - - protected override void OnTransformingIndexValues(IndexingItemEventArgs e) - { - // UmbracoExamineIndex (base class down the hierarchy) performs some magic transformations here for paths and icons; - // we don't want that for the Delivery API, so we'll have to override this method and simply do nothing. - } } From a7ff32891ed87bb724fa02d41232ffcec474e499 Mon Sep 17 00:00:00 2001 From: Elitsa Date: Mon, 8 Jul 2024 09:28:34 +0300 Subject: [PATCH 097/113] Making sure base.OnTransformingIndexValues(e); is called for DeliveryApiContentIndex without the special index value transformations --- .../UmbracoExamineIndex.cs | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Examine.Lucene/UmbracoExamineIndex.cs b/src/Umbraco.Examine.Lucene/UmbracoExamineIndex.cs index 5faccf581f..fb162b6521 100644 --- a/src/Umbraco.Examine.Lucene/UmbracoExamineIndex.cs +++ b/src/Umbraco.Examine.Lucene/UmbracoExamineIndex.cs @@ -115,16 +115,32 @@ public abstract class UmbracoExamineIndex : LuceneIndex, IUmbracoIndex, IIndexDi { base.OnTransformingIndexValues(e); + // Performs special value transformations to all deriving indexes of this base class + // but the DeliveryApiContentIndex (they are not needed in this case). + // The alternative is to move the call to ApplySpecialIndexValueTransformations into a new base class + // that all implementors but the DeliveryApiContentIndex would inherit from but that would be breaking + // for any custom indexes deriving from UmbracoExamineIndex. + if (e.Index.Name != Constants.UmbracoIndexes.DeliveryApiContentIndexName) + { + ApplySpecialIndexValueTransformations(e); + } + } + + /// + /// Updates the index ValueSet with a special __Path and __Icon fields. + /// + private void ApplySpecialIndexValueTransformations(IndexingItemEventArgs e) + { var updatedValues = e.ValueSet.Values.ToDictionary(x => x.Key, x => (IEnumerable)x.Value); - //ensure special __Path field + // Ensure special __Path field var path = e.ValueSet.GetValue("path"); if (path != null) { updatedValues[UmbracoExamineFieldNames.IndexPathFieldName] = path.Yield(); } - //icon + // Ensure special __Icon field if (e.ValueSet.Values.TryGetValue("icon", out IReadOnlyList? icon) && e.ValueSet.Values.ContainsKey(UmbracoExamineFieldNames.IconFieldName) == false) { From 8dc02fa0814114277d9930396b1fc978d0d7dfef Mon Sep 17 00:00:00 2001 From: Elitsa Date: Mon, 8 Jul 2024 09:30:12 +0300 Subject: [PATCH 098/113] Revert "Making sure base.OnTransformingIndexValues(e); is called for DeliveryApiContentIndex without the special index value transformations" This reverts commit a7ff32891ed87bb724fa02d41232ffcec474e499. --- .../UmbracoExamineIndex.cs | 20 ++----------------- 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/src/Umbraco.Examine.Lucene/UmbracoExamineIndex.cs b/src/Umbraco.Examine.Lucene/UmbracoExamineIndex.cs index fb162b6521..5faccf581f 100644 --- a/src/Umbraco.Examine.Lucene/UmbracoExamineIndex.cs +++ b/src/Umbraco.Examine.Lucene/UmbracoExamineIndex.cs @@ -115,32 +115,16 @@ public abstract class UmbracoExamineIndex : LuceneIndex, IUmbracoIndex, IIndexDi { base.OnTransformingIndexValues(e); - // Performs special value transformations to all deriving indexes of this base class - // but the DeliveryApiContentIndex (they are not needed in this case). - // The alternative is to move the call to ApplySpecialIndexValueTransformations into a new base class - // that all implementors but the DeliveryApiContentIndex would inherit from but that would be breaking - // for any custom indexes deriving from UmbracoExamineIndex. - if (e.Index.Name != Constants.UmbracoIndexes.DeliveryApiContentIndexName) - { - ApplySpecialIndexValueTransformations(e); - } - } - - /// - /// Updates the index ValueSet with a special __Path and __Icon fields. - /// - private void ApplySpecialIndexValueTransformations(IndexingItemEventArgs e) - { var updatedValues = e.ValueSet.Values.ToDictionary(x => x.Key, x => (IEnumerable)x.Value); - // Ensure special __Path field + //ensure special __Path field var path = e.ValueSet.GetValue("path"); if (path != null) { updatedValues[UmbracoExamineFieldNames.IndexPathFieldName] = path.Yield(); } - // Ensure special __Icon field + //icon if (e.ValueSet.Values.TryGetValue("icon", out IReadOnlyList? icon) && e.ValueSet.Values.ContainsKey(UmbracoExamineFieldNames.IconFieldName) == false) { From 5f6be40bdf1e3eeadc78b62c7751b46f6458f986 Mon Sep 17 00:00:00 2001 From: Elitsa Date: Mon, 8 Jul 2024 09:30:12 +0300 Subject: [PATCH 099/113] Revert "Removing override of OnTransformingIndexValues from DeliveryApiContentIndex" This reverts commit f6deaca45204fb598d1ddebd4259d3e939f5926c. --- src/Umbraco.Examine.Lucene/DeliveryApiContentIndex.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Umbraco.Examine.Lucene/DeliveryApiContentIndex.cs b/src/Umbraco.Examine.Lucene/DeliveryApiContentIndex.cs index 367817f368..c572fc85f7 100644 --- a/src/Umbraco.Examine.Lucene/DeliveryApiContentIndex.cs +++ b/src/Umbraco.Examine.Lucene/DeliveryApiContentIndex.cs @@ -134,4 +134,10 @@ public class DeliveryApiContentIndex : UmbracoExamineIndex return (compositeIdModel.Id?.ToString(CultureInfo.InvariantCulture), compositeIdModel.Culture); } + + protected override void OnTransformingIndexValues(IndexingItemEventArgs e) + { + // UmbracoExamineIndex (base class down the hierarchy) performs some magic transformations here for paths and icons; + // we don't want that for the Delivery API, so we'll have to override this method and simply do nothing. + } } From 512114bcd7bd61e58f73e90308fe9bbd6b8b5927 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Mon, 8 Jul 2024 16:15:48 +0200 Subject: [PATCH 100/113] V14: Add `usernameIsEmail` to the user configuration object (#16759) * fix: move usernameIsEmail from the current user to the user configuration since it is a static variable not only relevant to the current user * update OpenApi.json * update OpenApi.json * update OpenApi.json --- .../Factories/UserPresentationFactory.cs | 1 + src/Umbraco.Cms.Api.Management/OpenApi.json | 773 +++++++++--------- .../CurrenUserConfigurationResponseModel.cs | 1 + .../User/UserConfigurationResponseModel.cs | 2 + 4 files changed, 393 insertions(+), 384 deletions(-) diff --git a/src/Umbraco.Cms.Api.Management/Factories/UserPresentationFactory.cs b/src/Umbraco.Cms.Api.Management/Factories/UserPresentationFactory.cs index 9164be6772..59e94b5d43 100644 --- a/src/Umbraco.Cms.Api.Management/Factories/UserPresentationFactory.cs +++ b/src/Umbraco.Cms.Api.Management/Factories/UserPresentationFactory.cs @@ -150,6 +150,7 @@ public class UserPresentationFactory : IUserPresentationFactory { // You should not be able to invite users if any providers has deny local login set. CanInviteUsers = _emailSender.CanSendRequiredEmail() && _externalLoginProviders.HasDenyLocalLogin() is false, + UsernameIsEmail = _securitySettings.UsernameIsEmail, PasswordConfiguration = _passwordConfigurationPresentationFactory.CreatePasswordConfigurationResponseModel(), }); diff --git a/src/Umbraco.Cms.Api.Management/OpenApi.json b/src/Umbraco.Cms.Api.Management/OpenApi.json index 77e006ffaa..5d2d532ebc 100644 --- a/src/Umbraco.Cms.Api.Management/OpenApi.json +++ b/src/Umbraco.Cms.Api.Management/OpenApi.json @@ -53,7 +53,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -199,7 +199,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -259,7 +259,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] }, @@ -368,7 +368,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] }, @@ -508,7 +508,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -639,7 +639,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -695,7 +695,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -811,7 +811,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -874,7 +874,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -909,7 +909,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -1055,7 +1055,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -1115,7 +1115,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] }, @@ -1224,7 +1224,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] }, @@ -1364,7 +1364,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -1441,7 +1441,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -1490,7 +1490,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -1549,7 +1549,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -1597,7 +1597,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -1668,7 +1668,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -1731,7 +1731,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -1793,7 +1793,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] }, @@ -1963,7 +1963,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -2023,7 +2023,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] }, @@ -2132,7 +2132,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] }, @@ -2272,7 +2272,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -2341,7 +2341,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -2483,7 +2483,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -2629,7 +2629,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -2678,7 +2678,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -2726,7 +2726,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -2789,7 +2789,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -2844,7 +2844,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -2990,7 +2990,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -3050,7 +3050,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] }, @@ -3159,7 +3159,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] }, @@ -3299,7 +3299,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -3415,7 +3415,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -3561,7 +3561,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -3621,7 +3621,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] }, @@ -3730,7 +3730,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] }, @@ -3870,7 +3870,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -3990,7 +3990,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -4039,7 +4039,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -4087,7 +4087,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -4158,7 +4158,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -4221,7 +4221,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -4367,7 +4367,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -4427,7 +4427,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] }, @@ -4510,7 +4510,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] }, @@ -4650,7 +4650,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -4728,7 +4728,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -4806,7 +4806,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -4883,7 +4883,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -5040,7 +5040,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -5101,7 +5101,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -5243,7 +5243,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -5385,7 +5385,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -5440,7 +5440,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -5533,7 +5533,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -5568,7 +5568,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -5714,7 +5714,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -5774,7 +5774,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] }, @@ -5883,7 +5883,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] }, @@ -6023,7 +6023,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -6169,7 +6169,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -6218,7 +6218,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -6277,7 +6277,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -6325,7 +6325,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -6396,7 +6396,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -6459,7 +6459,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -6558,7 +6558,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -6632,7 +6632,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -6750,7 +6750,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -6868,7 +6868,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -6997,7 +6997,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -7143,7 +7143,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -7203,7 +7203,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] }, @@ -7312,7 +7312,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] }, @@ -7452,7 +7452,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -7531,7 +7531,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -7662,7 +7662,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -7722,7 +7722,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] }, @@ -7888,7 +7888,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -8004,7 +8004,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -8115,7 +8115,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -8178,7 +8178,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] }, @@ -8292,7 +8292,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -8423,7 +8423,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] }, @@ -8506,7 +8506,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] }, @@ -8564,7 +8564,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] }, @@ -8678,7 +8678,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -8820,7 +8820,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -8962,7 +8962,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -9026,7 +9026,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -9090,7 +9090,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -9232,7 +9232,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -9374,7 +9374,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -9441,7 +9441,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -9476,7 +9476,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -9607,7 +9607,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -9659,7 +9659,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -9790,7 +9790,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -9839,7 +9839,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -9898,7 +9898,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -9972,7 +9972,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -10083,7 +10083,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -10157,7 +10157,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -10299,7 +10299,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -10362,7 +10362,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -10417,7 +10417,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -10465,7 +10465,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -10536,7 +10536,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -10599,7 +10599,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -10689,7 +10689,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -10723,7 +10723,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -10778,7 +10778,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -10837,7 +10837,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -10932,7 +10932,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -11048,7 +11048,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -11136,7 +11136,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -11213,7 +11213,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -11283,7 +11283,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -11335,7 +11335,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -11391,7 +11391,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -11512,7 +11512,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -11762,7 +11762,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -11794,7 +11794,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -11846,7 +11846,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] }, @@ -11990,7 +11990,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -12046,7 +12046,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] }, @@ -12154,7 +12154,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] }, @@ -12293,7 +12293,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -12348,7 +12348,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -12415,7 +12415,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -12510,7 +12510,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -12595,7 +12595,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -12650,7 +12650,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] }, @@ -12768,7 +12768,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -12827,7 +12827,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] }, @@ -12909,7 +12909,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -12965,7 +12965,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -13003,7 +13003,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -13041,7 +13041,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -13117,7 +13117,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -13176,7 +13176,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -13228,7 +13228,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -13287,7 +13287,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -13433,7 +13433,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -13493,7 +13493,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] }, @@ -13576,7 +13576,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] }, @@ -13716,7 +13716,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -13794,7 +13794,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -13871,7 +13871,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -14028,7 +14028,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -14089,7 +14089,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -14231,7 +14231,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -14373,7 +14373,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -14428,7 +14428,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -14521,7 +14521,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -14556,7 +14556,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -14702,7 +14702,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -14762,7 +14762,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] }, @@ -14871,7 +14871,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] }, @@ -15011,7 +15011,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -15157,7 +15157,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -15205,7 +15205,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -15276,7 +15276,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -15339,7 +15339,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -15460,7 +15460,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -15509,7 +15509,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -15568,7 +15568,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -15714,7 +15714,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -15774,7 +15774,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] }, @@ -15883,7 +15883,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] }, @@ -16023,7 +16023,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -16102,7 +16102,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -16218,7 +16218,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -16329,7 +16329,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -16393,7 +16393,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -16457,7 +16457,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -16599,7 +16599,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -16666,7 +16666,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -16701,7 +16701,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -16832,7 +16832,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -16884,7 +16884,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -17015,7 +17015,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -17089,7 +17089,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -17200,7 +17200,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -17274,7 +17274,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -17416,7 +17416,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -17479,7 +17479,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -17534,7 +17534,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -17582,7 +17582,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -17653,7 +17653,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -17716,7 +17716,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -17765,7 +17765,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -17820,7 +17820,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] }, @@ -17938,7 +17938,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -17987,7 +17987,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] }, @@ -18096,7 +18096,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] }, @@ -18236,7 +18236,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -18291,7 +18291,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -18340,7 +18340,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -18399,7 +18399,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -18545,7 +18545,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -18605,7 +18605,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] }, @@ -18688,7 +18688,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] }, @@ -18828,7 +18828,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -18905,7 +18905,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -19031,7 +19031,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -19124,7 +19124,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -19159,7 +19159,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -19214,7 +19214,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -19334,7 +19334,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -19383,7 +19383,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -19442,7 +19442,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -19588,7 +19588,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -19648,7 +19648,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] }, @@ -19757,7 +19757,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] }, @@ -19897,7 +19897,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -20039,7 +20039,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -20074,7 +20074,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -20205,7 +20205,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -20279,7 +20279,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -20314,7 +20314,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -20349,7 +20349,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -20401,7 +20401,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -20462,7 +20462,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -20572,7 +20572,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -20607,7 +20607,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -20662,7 +20662,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] }, @@ -20806,7 +20806,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -20866,7 +20866,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] }, @@ -20949,7 +20949,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] }, @@ -21063,7 +21063,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -21124,7 +21124,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -21179,7 +21179,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -21227,7 +21227,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -21373,7 +21373,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -21432,7 +21432,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] }, @@ -21540,7 +21540,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] }, @@ -21679,7 +21679,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -21835,7 +21835,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -21981,7 +21981,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -22040,7 +22040,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] }, @@ -22148,7 +22148,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -22203,7 +22203,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -22262,7 +22262,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -22309,7 +22309,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -22371,7 +22371,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -22426,7 +22426,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -22482,7 +22482,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -22517,7 +22517,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] }, @@ -22594,7 +22594,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -22656,7 +22656,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -22689,7 +22689,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -22722,7 +22722,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -22755,7 +22755,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -22783,7 +22783,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -22859,7 +22859,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -22923,7 +22923,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] }, @@ -22980,7 +22980,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -23015,7 +23015,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] }, @@ -23070,7 +23070,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -23119,7 +23119,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -23174,7 +23174,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -23234,7 +23234,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -23312,7 +23312,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -23360,7 +23360,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -23506,7 +23506,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -23565,7 +23565,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] }, @@ -23673,7 +23673,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] }, @@ -23812,7 +23812,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -23968,7 +23968,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -24114,7 +24114,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -24173,7 +24173,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] }, @@ -24281,7 +24281,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -24328,7 +24328,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -24390,7 +24390,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -24445,7 +24445,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -24497,7 +24497,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -24578,7 +24578,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -24613,7 +24613,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -24718,7 +24718,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -24849,7 +24849,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -25037,7 +25037,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -25069,7 +25069,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -25101,7 +25101,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -25171,7 +25171,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -25219,7 +25219,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -25263,7 +25263,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -25322,7 +25322,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -25374,7 +25374,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -25422,7 +25422,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -25568,7 +25568,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -25627,7 +25627,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] }, @@ -25735,7 +25735,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] }, @@ -25874,7 +25874,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -26030,7 +26030,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -26176,7 +26176,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -26235,7 +26235,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] }, @@ -26343,7 +26343,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -26390,7 +26390,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -26452,7 +26452,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -26507,7 +26507,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -26580,7 +26580,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -26635,7 +26635,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -26670,7 +26670,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] }, @@ -26773,7 +26773,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -26822,7 +26822,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -26881,7 +26881,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -27027,7 +27027,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -27087,7 +27087,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] }, @@ -27196,7 +27196,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] }, @@ -27336,7 +27336,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -27371,7 +27371,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -27461,7 +27461,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -27496,7 +27496,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -27544,7 +27544,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -27607,7 +27607,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -27662,7 +27662,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -27767,7 +27767,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -27838,7 +27838,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] }, @@ -27932,7 +27932,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -27964,7 +27964,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -28064,7 +28064,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -28113,7 +28113,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -28236,7 +28236,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] }, @@ -28306,7 +28306,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] }, @@ -28412,7 +28412,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -28458,7 +28458,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -28549,7 +28549,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -28598,7 +28598,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -28703,7 +28703,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] }, @@ -28821,7 +28821,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] }, @@ -28874,7 +28874,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -28934,7 +28934,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] }, @@ -29017,7 +29017,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] }, @@ -29131,7 +29131,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -29256,7 +29256,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] }, @@ -29379,7 +29379,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -29507,7 +29507,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -29556,7 +29556,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -29702,7 +29702,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] }, @@ -29805,7 +29805,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] }, @@ -29872,7 +29872,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -29932,7 +29932,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] }, @@ -30041,7 +30041,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] }, @@ -30181,7 +30181,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -30244,7 +30244,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -30363,7 +30363,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -30423,7 +30423,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -30565,7 +30565,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -30687,7 +30687,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -30798,7 +30798,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] }, @@ -30938,7 +30938,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -30973,7 +30973,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -31005,7 +31005,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -31040,7 +31040,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -31142,7 +31142,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] }, @@ -31277,7 +31277,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] }, @@ -31345,7 +31345,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -31435,7 +31435,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -31525,7 +31525,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -31560,7 +31560,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -31595,7 +31595,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -31655,7 +31655,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -31718,7 +31718,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -31778,7 +31778,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -31909,7 +31909,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -32040,7 +32040,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -32186,7 +32186,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -32440,7 +32440,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -32653,7 +32653,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -32758,7 +32758,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -32807,7 +32807,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -32859,7 +32859,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] }, @@ -33003,7 +33003,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -33060,7 +33060,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] }, @@ -33169,7 +33169,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] }, @@ -33309,7 +33309,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -33361,7 +33361,7 @@ }, "security": [ { - "Backoffice User": [] + "Backoffice User": [ ] } ] } @@ -35351,7 +35351,8 @@ "type": "boolean" }, "usernameIsEmail": { - "type": "boolean" + "type": "boolean", + "deprecated": true }, "passwordConfiguration": { "oneOf": [ @@ -44549,13 +44550,17 @@ "UserConfigurationResponseModel": { "required": [ "canInviteUsers", - "passwordConfiguration" + "passwordConfiguration", + "usernameIsEmail" ], "type": "object", "properties": { "canInviteUsers": { "type": "boolean" }, + "usernameIsEmail": { + "type": "boolean" + }, "passwordConfiguration": { "oneOf": [ { diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/User/Current/CurrenUserConfigurationResponseModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/User/Current/CurrenUserConfigurationResponseModel.cs index 75a6a451d3..fc1d7d8b4f 100644 --- a/src/Umbraco.Cms.Api.Management/ViewModels/User/Current/CurrenUserConfigurationResponseModel.cs +++ b/src/Umbraco.Cms.Api.Management/ViewModels/User/Current/CurrenUserConfigurationResponseModel.cs @@ -6,6 +6,7 @@ public class CurrenUserConfigurationResponseModel { public bool KeepUserLoggedIn { get; set; } + [Obsolete("Use the UserConfigurationResponseModel instead. This will be removed in V15.")] public bool UsernameIsEmail { get; set; } public required PasswordConfigurationResponseModel PasswordConfiguration { get; set; } diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/User/UserConfigurationResponseModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/User/UserConfigurationResponseModel.cs index f06f137d64..a64cb4273c 100644 --- a/src/Umbraco.Cms.Api.Management/ViewModels/User/UserConfigurationResponseModel.cs +++ b/src/Umbraco.Cms.Api.Management/ViewModels/User/UserConfigurationResponseModel.cs @@ -6,5 +6,7 @@ public class UserConfigurationResponseModel { public bool CanInviteUsers { get; set; } + public bool UsernameIsEmail { get; set; } + public required PasswordConfigurationResponseModel PasswordConfiguration { get; set; } } From 510f805223dab3c990691e183dc4054bb6c5a3fa Mon Sep 17 00:00:00 2001 From: Elitsa Marinovska <21998037+elit0451@users.noreply.github.com> Date: Tue, 9 Jul 2024 10:57:26 +0300 Subject: [PATCH 101/113] Keeping launchBrowser settings as in v13 (#16765) --- src/Umbraco.Web.UI/Properties/launchSettings.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI/Properties/launchSettings.json b/src/Umbraco.Web.UI/Properties/launchSettings.json index cf917cdcb4..2631298a10 100644 --- a/src/Umbraco.Web.UI/Properties/launchSettings.json +++ b/src/Umbraco.Web.UI/Properties/launchSettings.json @@ -11,14 +11,14 @@ "profiles": { "IIS Express": { "commandName": "IISExpress", - "launchBrowser": false, + "launchBrowser": true, "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } }, "Umbraco.Web.UI": { "commandName": "Project", - "launchBrowser": false, + "launchBrowser": true, "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" }, From d05e32b6e9d4d0e0836696a668bfcfd5c6572bac Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Wed, 10 Jul 2024 11:49:47 +0200 Subject: [PATCH 102/113] update backoffice submodule --- src/Umbraco.Web.UI.Client | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client b/src/Umbraco.Web.UI.Client index f43c9496cf..3c95bfec76 160000 --- a/src/Umbraco.Web.UI.Client +++ b/src/Umbraco.Web.UI.Client @@ -1 +1 @@ -Subproject commit f43c9496cfcb32ef7fb50b8015777b823242d008 +Subproject commit 3c95bfec76dde99d426a44d17b9c786e9d13fc07 From fd3f77c4778389a79dc0f5e2270b8a00d2c7abda Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Wed, 10 Jul 2024 11:50:40 +0200 Subject: [PATCH 103/113] update backoffice submodule (hotfix ufm) --- src/Umbraco.Web.UI.Client | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client b/src/Umbraco.Web.UI.Client index 98b657dba6..3d252940b8 160000 --- a/src/Umbraco.Web.UI.Client +++ b/src/Umbraco.Web.UI.Client @@ -1 +1 @@ -Subproject commit 98b657dba652da774314e70e0bfeed0aa4ee5a48 +Subproject commit 3d252940b8872abc6b948e580422561711ebad0a From e4b776a56c6382f00d2702eb0838d0f42b2fe195 Mon Sep 17 00:00:00 2001 From: Zeegaan Date: Wed, 10 Jul 2024 11:53:07 +0200 Subject: [PATCH 104/113] bump version --- version.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.json b/version.json index 31fd8e32c7..a3f6170c9f 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": "14.1.0-rc2", + "version": "14.1.0", "assemblyVersion": { "precision": "build" }, From c1bf3a7e154d3e11c7dd493a6272ec033cf7d9aa Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Thu, 11 Jul 2024 12:02:39 +0200 Subject: [PATCH 105/113] bump version to 13.4.1 --- version.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.json b/version.json index 288af47e08..3b44f006d5 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.5.0-rc", + "version": "13.4.1", "assemblyVersion": { "precision": "build" }, From a41555cc0d451344ed552f36e66f77d0346c1ca5 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Thu, 11 Jul 2024 11:41:20 +0200 Subject: [PATCH 106/113] fix: revert the `inert` attribute on Tours since you are expected to be able to work with the background in certain steps --- .../components/application/umbtour/umbtourstep.directive.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbtour/umbtourstep.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbtour/umbtourstep.directive.js index 53ff989c7d..c54f907d44 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbtour/umbtourstep.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbtour/umbtourstep.directive.js @@ -14,18 +14,15 @@ (function () { 'use strict'; - function TourStepDirective(focusLockService) { + function TourStepDirective() { function link(scope, element, attrs, ctrl) { scope.close = function () { if (scope.onClose) { scope.onClose(); - focusLockService.removeInertAttribute(); } } - - focusLockService.addInertAttribute(); } var directive = { From d72fc5c4e83c71dfee73b45fc2eacf5158a040e6 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Thu, 11 Jul 2024 11:41:20 +0200 Subject: [PATCH 107/113] fix: revert the `inert` attribute on Tours since you are expected to be able to work with the background in certain steps --- .../components/application/umbtour/umbtourstep.directive.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbtour/umbtourstep.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbtour/umbtourstep.directive.js index 53ff989c7d..c54f907d44 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbtour/umbtourstep.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbtour/umbtourstep.directive.js @@ -14,18 +14,15 @@ (function () { 'use strict'; - function TourStepDirective(focusLockService) { + function TourStepDirective() { function link(scope, element, attrs, ctrl) { scope.close = function () { if (scope.onClose) { scope.onClose(); - focusLockService.removeInertAttribute(); } } - - focusLockService.addInertAttribute(); } var directive = { From be81586b46047d4c41d734b36363cf09206943b3 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Thu, 11 Jul 2024 15:07:36 +0200 Subject: [PATCH 108/113] set version to 13.5.0-rc --- version.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.json b/version.json index 3b44f006d5..288af47e08 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.4.1", + "version": "13.5.0-rc", "assemblyVersion": { "precision": "build" }, From efe2f51d13ed2eb529137d73f876fd525ded747b Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Mon, 15 Jul 2024 09:58:47 +0200 Subject: [PATCH 109/113] update backoffice submodule --- src/Umbraco.Web.UI.Client | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client b/src/Umbraco.Web.UI.Client index 631a21735d..c464cb2aea 160000 --- a/src/Umbraco.Web.UI.Client +++ b/src/Umbraco.Web.UI.Client @@ -1 +1 @@ -Subproject commit 631a21735df12b408b3a3d8191f6e11eb9296106 +Subproject commit c464cb2aea43504a80b7e3da9504066ee929bf7e From dfe41d7f7630b5d1be144a4ea1fd8e861143f839 Mon Sep 17 00:00:00 2001 From: Elitsa Marinovska <21998037+elit0451@users.noreply.github.com> Date: Mon, 15 Jul 2024 11:45:57 +0300 Subject: [PATCH 110/113] V13: Ensure `TransformingIndexValues` event is also raised from the `DeliveryApiContentIndex` (#16756) * Removing override of OnTransformingIndexValues from DeliveryApiContentIndex * Making sure that TransformingIndexValues event is raised for DeliveryApiContentIndex without performing the special index value transformations * Review suggestion --- .../DeliveryApiContentIndex.cs | 9 +++----- .../UmbracoExamineIndex.cs | 21 +++++++++++++++++-- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/src/Umbraco.Examine.Lucene/DeliveryApiContentIndex.cs b/src/Umbraco.Examine.Lucene/DeliveryApiContentIndex.cs index c572fc85f7..3195a6df7c 100644 --- a/src/Umbraco.Examine.Lucene/DeliveryApiContentIndex.cs +++ b/src/Umbraco.Examine.Lucene/DeliveryApiContentIndex.cs @@ -17,6 +17,9 @@ public class DeliveryApiContentIndex : UmbracoExamineIndex private readonly IDeliveryApiCompositeIdHandler _deliveryApiCompositeIdHandler; private readonly ILogger _logger; + // The special path and icon value transformations are not needed in this case + protected override bool ApplySpecialValueTransformations => false; + [Obsolete("Use the constructor that takes an IDeliveryApiCompositeIdHandler instead, scheduled for removal in v15")] public DeliveryApiContentIndex( ILoggerFactory loggerFactory, @@ -134,10 +137,4 @@ public class DeliveryApiContentIndex : UmbracoExamineIndex return (compositeIdModel.Id?.ToString(CultureInfo.InvariantCulture), compositeIdModel.Culture); } - - protected override void OnTransformingIndexValues(IndexingItemEventArgs e) - { - // UmbracoExamineIndex (base class down the hierarchy) performs some magic transformations here for paths and icons; - // we don't want that for the Delivery API, so we'll have to override this method and simply do nothing. - } } diff --git a/src/Umbraco.Examine.Lucene/UmbracoExamineIndex.cs b/src/Umbraco.Examine.Lucene/UmbracoExamineIndex.cs index 5faccf581f..bfec67f5d1 100644 --- a/src/Umbraco.Examine.Lucene/UmbracoExamineIndex.cs +++ b/src/Umbraco.Examine.Lucene/UmbracoExamineIndex.cs @@ -38,8 +38,14 @@ public abstract class UmbracoExamineIndex : LuceneIndex, IUmbracoIndex, IIndexDi } public Attempt IsHealthy() => _diagnostics.IsHealthy(); + public virtual IReadOnlyDictionary Metadata => _diagnostics.Metadata; + /// + /// Performs special __Path and __Icon value transformations to all deriving indexes when set to true. + /// + protected virtual bool ApplySpecialValueTransformations => true; + /// /// When set to true Umbraco will keep the index in sync with Umbraco data automatically /// @@ -115,16 +121,27 @@ public abstract class UmbracoExamineIndex : LuceneIndex, IUmbracoIndex, IIndexDi { base.OnTransformingIndexValues(e); + if (ApplySpecialValueTransformations) + { + ApplySpecialIndexValueTransformations(e); + } + } + + /// + /// Updates the index ValueSet with a special __Path and __Icon fields. + /// + private void ApplySpecialIndexValueTransformations(IndexingItemEventArgs e) + { var updatedValues = e.ValueSet.Values.ToDictionary(x => x.Key, x => (IEnumerable)x.Value); - //ensure special __Path field + // Ensure special __Path field var path = e.ValueSet.GetValue("path"); if (path != null) { updatedValues[UmbracoExamineFieldNames.IndexPathFieldName] = path.Yield(); } - //icon + // Ensure special __Icon field if (e.ValueSet.Values.TryGetValue("icon", out IReadOnlyList? icon) && e.ValueSet.Values.ContainsKey(UmbracoExamineFieldNames.IconFieldName) == false) { From 2f7afa67a3ad37699176b5523bd1f669aa68259a Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Thu, 18 Jul 2024 12:56:58 +0200 Subject: [PATCH 111/113] update backoffice submodule --- src/Umbraco.Web.UI.Client | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client b/src/Umbraco.Web.UI.Client index c464cb2aea..0c74f2a34c 160000 --- a/src/Umbraco.Web.UI.Client +++ b/src/Umbraco.Web.UI.Client @@ -1 +1 @@ -Subproject commit c464cb2aea43504a80b7e3da9504066ee929bf7e +Subproject commit 0c74f2a34c72de17bb84e0805fd70ba4a44eb4be From cad5502f79b2af6d696bc91b39c6b4196fc001e2 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Thu, 18 Jul 2024 14:06:27 +0200 Subject: [PATCH 112/113] V14: Remove user-defined.css (#16792) * chore: remove the `user-defined.css` asset from the RCL You cannot overwrite an asset in an RCL just like that, as the user-defined.css were intended. * delete actual file --- .../umbraco/UmbracoBackOffice/Index.cshtml | 1 - src/Umbraco.Cms.StaticAssets/umbraco/UmbracoLogin/Index.cshtml | 1 - src/Umbraco.Web.UI.Login/index.html | 1 - src/Umbraco.Web.UI.Login/public/user-defined.css | 1 - 4 files changed, 4 deletions(-) delete mode 100644 src/Umbraco.Web.UI.Login/public/user-defined.css diff --git a/src/Umbraco.Cms.StaticAssets/umbraco/UmbracoBackOffice/Index.cshtml b/src/Umbraco.Cms.StaticAssets/umbraco/UmbracoBackOffice/Index.cshtml index 453a5ed0fc..16fb9c48e6 100644 --- a/src/Umbraco.Cms.StaticAssets/umbraco/UmbracoBackOffice/Index.cshtml +++ b/src/Umbraco.Cms.StaticAssets/umbraco/UmbracoBackOffice/Index.cshtml @@ -33,7 +33,6 @@ Umbraco - @await Html.BackOfficeImportMapScriptAsync(JsonSerializer, BackOfficePathGenerator, PackageManifestService) diff --git a/src/Umbraco.Cms.StaticAssets/umbraco/UmbracoLogin/Index.cshtml b/src/Umbraco.Cms.StaticAssets/umbraco/UmbracoLogin/Index.cshtml index 731ae9f81d..856796cd25 100644 --- a/src/Umbraco.Cms.StaticAssets/umbraco/UmbracoLogin/Index.cshtml +++ b/src/Umbraco.Cms.StaticAssets/umbraco/UmbracoLogin/Index.cshtml @@ -45,7 +45,6 @@ Umbraco -