From a2355915214562a6b2f2c808e45e680bd1e1bf59 Mon Sep 17 00:00:00 2001 From: Erik-Jan Westendorp Date: Mon, 12 Aug 2024 10:18:20 +0200 Subject: [PATCH 01/41] Add Content-Security-Policy (CSP) health check (#16830) * Add CSP Header Check * Update src/Umbraco.Core/EmbeddedResources/Lang/en.xml Co-authored-by: Laura Neto <12862535+lauraneto@users.noreply.github.com> * Update src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml Co-authored-by: Laura Neto <12862535+lauraneto@users.noreply.github.com> --------- Co-authored-by: Laura Neto <12862535+lauraneto@users.noreply.github.com> --- src/Umbraco.Core/Constants-HealthChecks.cs | 1 + .../EmbeddedResources/Lang/en.xml | 6 ++++ .../EmbeddedResources/Lang/en_us.xml | 6 ++++ .../HealthChecks/Checks/Security/CspCheck.cs | 31 +++++++++++++++++++ 4 files changed, 44 insertions(+) create mode 100644 src/Umbraco.Core/HealthChecks/Checks/Security/CspCheck.cs diff --git a/src/Umbraco.Core/Constants-HealthChecks.cs b/src/Umbraco.Core/Constants-HealthChecks.cs index bb18145401..d4b35f4e04 100644 --- a/src/Umbraco.Core/Constants-HealthChecks.cs +++ b/src/Umbraco.Core/Constants-HealthChecks.cs @@ -52,6 +52,7 @@ public static partial class Constants [Obsolete("This link is not used anymore in the XSS protected check.")] public const string XssProtectionCheck = "https://umbra.co/healthchecks-xss-protection"; public const string ExcessiveHeadersCheck = "https://umbra.co/healthchecks-excessive-headers"; + public const string CspHeaderCheck = "https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP"; public static class HttpsCheck { diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/en.xml b/src/Umbraco.Core/EmbeddedResources/Lang/en.xml index 893ab7c242..c05ea72cfc 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/en.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/en.xml @@ -467,6 +467,12 @@ You can read about this on the Mozilla website ]]> X-XSS-Protection was not found.]]> + + Content-Security-Policy (CSP) was found. ]]> + + + Content-Security-Policy (CSP) used to prevent cross-site scripting (XSS) attacks and other code injection vulnerabilities was not found.]]> + %0%.]]> No headers revealing information about the website technology were found. diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml b/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml index 69558f72da..dec3809f8d 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml @@ -455,6 +455,12 @@ You can read about this on the Mozilla website ]]> X-XSS-Protection was not found.]]> + + Content-Security-Policy (CSP) was found. ]]> + + + Content-Security-Policy (CSP) used to prevent cross-site scripting (XSS) attacks and other code injection vulnerabilities was not found.]]> + %0%.]]> No headers revealing information about the website technology were found. diff --git a/src/Umbraco.Core/HealthChecks/Checks/Security/CspCheck.cs b/src/Umbraco.Core/HealthChecks/Checks/Security/CspCheck.cs new file mode 100644 index 0000000000..1ac8cf56f4 --- /dev/null +++ b/src/Umbraco.Core/HealthChecks/Checks/Security/CspCheck.cs @@ -0,0 +1,31 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Umbraco.Cms.Core.Hosting; +using Umbraco.Cms.Core.Services; + +namespace Umbraco.Cms.Core.HealthChecks.Checks.Security; + +/// +/// Health check for the recommended production setup regarding the content-security-policy header. +/// +[HealthCheck( + "10BEBF47-C128-4C5E-9680-5059BEAFBBDF", + "Content Security Policy (CSP)", + Description = "Checks whether the site contains a Content-Security-Policy (CSP) header.", + Group = "Security")] +public class CspCheck : BaseHttpHeaderCheck +{ + private const string LocalizationPrefix = "contentSecurityPolicy"; + + /// + /// Initializes a new instance of the class. + /// + public CspCheck(IHostingEnvironment hostingEnvironment, ILocalizedTextService textService) + : base(hostingEnvironment, textService, "Content-Security-Policy", LocalizationPrefix, false, false) + { + } + + /// + protected override string ReadMoreLink => Constants.HealthChecks.DocumentationLinks.Security.CspHeaderCheck; +} From 3a9ef3810bdc063be16bded4877957065e964dbc Mon Sep 17 00:00:00 2001 From: Peter <45105665+PeterKvayt@users.noreply.github.com> Date: Wed, 14 Aug 2024 22:07:36 +0200 Subject: [PATCH 02/41] Making method ExecuteAsync virtual. (#16496) Co-authored-by: Kvyatkovsky, Petr --- .../HostedServices/RecurringHostedServiceBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Infrastructure/HostedServices/RecurringHostedServiceBase.cs b/src/Umbraco.Infrastructure/HostedServices/RecurringHostedServiceBase.cs index a35f7aa956..c6f21738c2 100644 --- a/src/Umbraco.Infrastructure/HostedServices/RecurringHostedServiceBase.cs +++ b/src/Umbraco.Infrastructure/HostedServices/RecurringHostedServiceBase.cs @@ -129,7 +129,7 @@ public abstract class RecurringHostedServiceBase : IHostedService, IDisposable /// Executes the task. /// /// The task state. - public async void ExecuteAsync(object? state) + public virtual async void ExecuteAsync(object? state) { try { From 80b8b2a4ed8a9a247618f6e53561f797f6aec173 Mon Sep 17 00:00:00 2001 From: Jason Elkin Date: Thu, 15 Aug 2024 09:51:27 +0200 Subject: [PATCH 03/41] Issue-15712: MemberDefaultLockoutTimeInMinutes fix. --- src/Umbraco.Infrastructure/Security/IdentityMapDefinition.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Infrastructure/Security/IdentityMapDefinition.cs b/src/Umbraco.Infrastructure/Security/IdentityMapDefinition.cs index c19053240d..624c0eeeb1 100644 --- a/src/Umbraco.Infrastructure/Security/IdentityMapDefinition.cs +++ b/src/Umbraco.Infrastructure/Security/IdentityMapDefinition.cs @@ -112,7 +112,7 @@ public class IdentityMapDefinition : IMapDefinition target.PasswordConfig = source.PasswordConfiguration; target.IsApproved = source.IsApproved; target.SecurityStamp = source.SecurityStamp; - DateTime? lockedOutUntil = source.LastLockoutDate?.AddMinutes(_securitySettings.UserDefaultLockoutTimeInMinutes); + DateTime? lockedOutUntil = source.LastLockoutDate?.AddMinutes(_securitySettings.MemberDefaultLockoutTimeInMinutes); target.LockoutEnd = source.IsLockedOut ? (lockedOutUntil ?? DateTime.MaxValue).ToUniversalTime() : null; target.Comments = source.Comments; target.LastLockoutDateUtc = source.LastLockoutDate == DateTime.MinValue From 9338395a59b767852e967de2a3020eca11214583 Mon Sep 17 00:00:00 2001 From: Emanuel Gustafzon <104900900+EmanuelGustafzon@users.noreply.github.com> Date: Fri, 16 Aug 2024 21:59:10 +0200 Subject: [PATCH 04/41] Stripped username before login (#16921) * Stripped username before login --- src/Umbraco.Web.Common/Security/UmbracoSignInManager.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.Common/Security/UmbracoSignInManager.cs b/src/Umbraco.Web.Common/Security/UmbracoSignInManager.cs index f0ab7e02e2..b0b2622e16 100644 --- a/src/Umbraco.Web.Common/Security/UmbracoSignInManager.cs +++ b/src/Umbraco.Web.Common/Security/UmbracoSignInManager.cs @@ -165,7 +165,8 @@ public abstract class UmbracoSignInManager : SignInManager public override async Task PasswordSignInAsync(string userName, string password, bool isPersistent, bool lockoutOnFailure) { // override to handle logging/events - TUser? user = await UserManager.FindByNameAsync(userName); + string strippedUsername = userName.Trim(); + TUser? user = await UserManager.FindByNameAsync(strippedUsername); if (user == null) { return await HandleSignIn(null, userName, SignInResult.Failed); From 3b6fbf1a4cfff9c08617aa404ab99df4a0c48dec Mon Sep 17 00:00:00 2001 From: Matthew Care Date: Sat, 17 Aug 2024 16:00:24 +0200 Subject: [PATCH 05/41] Cache a dictionary of dictionaries (#15908) * Re-commit change Re-commit change after merge * Fix issues after merge Update usings after merge * Re-delete obsolete Remove the obsolete method --- src/Umbraco.Web.Common/UmbracoHelper.cs | 64 +++++++++++++++++-------- 1 file changed, 45 insertions(+), 19 deletions(-) diff --git a/src/Umbraco.Web.Common/UmbracoHelper.cs b/src/Umbraco.Web.Common/UmbracoHelper.cs index ec726148ec..9b1a9c6275 100644 --- a/src/Umbraco.Web.Common/UmbracoHelper.cs +++ b/src/Umbraco.Web.Common/UmbracoHelper.cs @@ -4,6 +4,7 @@ using Umbraco.Cms.Core.Dictionary; using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Cms.Core.Strings; using Umbraco.Cms.Core.Templates; +using Umbraco.Extensions; namespace Umbraco.Cms.Web.Common; @@ -18,7 +19,7 @@ public class UmbracoHelper private readonly IUmbracoComponentRenderer _componentRenderer; private readonly ICultureDictionaryFactory _cultureDictionaryFactory; private readonly IPublishedContentQuery _publishedContentQuery; - private ICultureDictionary? _cultureDictionary; + private readonly Dictionary _cultureDictionaries = []; private IPublishedContent? _currentPage; @@ -103,29 +104,29 @@ public class UmbracoHelper /// /// Returns the dictionary value for the key specified /// - /// - /// - public string? GetDictionaryValue(string key) => CultureDictionary[key]; + /// Key of dictionary item. + /// The dictionary value, should one exist. + public string? GetDictionaryValue(string key) => GetDictionaryValue(key, Thread.CurrentThread.CurrentUICulture); /// /// Returns the dictionary value for the key specified, and if empty returns the specified default fall back value /// - /// key of dictionary item + /// Key of dictionary item. /// the specific culture on which the result well be back upon - /// + /// The dictionary value, should one exist. public string? GetDictionaryValue(string key, CultureInfo specificCulture) { - _cultureDictionary = _cultureDictionaryFactory.CreateDictionary(specificCulture); - return GetDictionaryValue(key); + ICultureDictionary cultureDictionary = GetCultureDictionary(specificCulture); + return cultureDictionary[key]; } /// /// Returns the dictionary value for the key specified, and if empty returns the specified default fall back value /// - /// key of dictionary item - /// fall back text if dictionary item is empty - Name altText to match Umbraco.Field - /// + /// key of dictionary item. + /// fall back text if dictionary item is empty - Name altText to match Umbraco.Field. + /// Returns the dictionary value, or a default value if none exists. public string GetDictionaryValueOrDefault(string key, string defaultValue) { var dictionaryValue = GetDictionaryValue(key); @@ -140,26 +141,51 @@ public class UmbracoHelper /// /// Returns the dictionary value for the key specified, and if empty returns the specified default fall back value /// - /// key of dictionary item - /// the specific culture on which the result well be back upon - /// fall back text if dictionary item is empty - Name altText to match Umbraco.Field - /// + /// Key of dictionary item. + /// The specific culture on which the result well be back upon. + /// Fall back text if dictionary item is empty - Name altText to match Umbraco.Field. + /// Returns the dictionary value, or a default value if none exists. public string GetDictionaryValueOrDefault(string key, CultureInfo specificCulture, string defaultValue) { - _cultureDictionary = _cultureDictionaryFactory.CreateDictionary(specificCulture); - var dictionaryValue = GetDictionaryValue(key); + var dictionaryValue = GetDictionaryValue(key, specificCulture); if (string.IsNullOrWhiteSpace(dictionaryValue)) { dictionaryValue = defaultValue; } + return dictionaryValue; } + /// + /// Gets the ICultureDictionary for the current UI Culture for access to dictionary items + /// + public ICultureDictionary CultureDictionary => GetCultureDictionary(Thread.CurrentThread.CurrentUICulture); /// - /// Returns the ICultureDictionary for access to dictionary items + /// Gets the ICultureDictionary for access to dictionary items for a specific culture /// - public ICultureDictionary CultureDictionary => _cultureDictionary ??= _cultureDictionaryFactory.CreateDictionary(); + /// The culture of the culture dictionary you want to retrieve. + /// Returns the culture dictionary for the specified culture. + public ICultureDictionary GetCultureDictionary(CultureInfo specificCulture) + { + CreateCultureDictionary(specificCulture); + return _cultureDictionaries.GetValue(specificCulture)!; + } + + /// + /// Creates a culture dictionary for a specific culture if it doesn't already exist + /// + /// The culture to create a culture dictionary for. + internal void CreateCultureDictionary(CultureInfo specificCulture) + { + if (_cultureDictionaries.ContainsKey(specificCulture)) + { + return; + } + + ICultureDictionary dictionary = _cultureDictionaryFactory.CreateDictionary(specificCulture); + _cultureDictionaries.Add(specificCulture, dictionary); + } #endregion From e8051978e975deafe57511d8939c003ea2347a94 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 15 Aug 2024 09:11:32 +0000 Subject: [PATCH 06/41] Bump axios from 1.6.5 to 1.7.4 in /tests/Umbraco.Tests.AcceptanceTest Bumps [axios](https://github.com/axios/axios) from 1.6.5 to 1.7.4. - [Release notes](https://github.com/axios/axios/releases) - [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md) - [Commits](https://github.com/axios/axios/compare/v1.6.5...v1.7.4) --- updated-dependencies: - dependency-name: axios dependency-type: indirect ... Signed-off-by: dependabot[bot] --- tests/Umbraco.Tests.AcceptanceTest/package-lock.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/Umbraco.Tests.AcceptanceTest/package-lock.json b/tests/Umbraco.Tests.AcceptanceTest/package-lock.json index 264945f43c..80c133b996 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/package-lock.json +++ b/tests/Umbraco.Tests.AcceptanceTest/package-lock.json @@ -182,12 +182,12 @@ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, "node_modules/axios": { - "version": "1.6.5", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.5.tgz", - "integrity": "sha512-Ii012v05KEVuUoFWmMW/UQv9aRIc3ZwkWDcM+h5Il8izZCtRVpDUfwpoFf7eOtajT3QiGR4yDUx7lPqHJULgbg==", + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.4.tgz", + "integrity": "sha512-DukmaFRnY6AzAALSH4J2M3k6PkaC+MfaAGdEERRWcC9q3/TWQwLpHR8ZRLKTdQ3aBDL64EdluRDjJqKw+BPZEw==", "dev": true, "dependencies": { - "follow-redirects": "^1.15.4", + "follow-redirects": "^1.15.6", "form-data": "^4.0.0", "proxy-from-env": "^1.1.0" } From 862e45eca3710f56446cb214dee5d174fb193b9e Mon Sep 17 00:00:00 2001 From: Jason Elkin Date: Thu, 22 Aug 2024 21:01:21 +0100 Subject: [PATCH 07/41] Ignore Visual Studio's generated launchSettings file. --- tests/Umbraco.Tests.AcceptanceTest.UmbracoProject/.gitignore | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 tests/Umbraco.Tests.AcceptanceTest.UmbracoProject/.gitignore diff --git a/tests/Umbraco.Tests.AcceptanceTest.UmbracoProject/.gitignore b/tests/Umbraco.Tests.AcceptanceTest.UmbracoProject/.gitignore new file mode 100644 index 0000000000..ff99115ae5 --- /dev/null +++ b/tests/Umbraco.Tests.AcceptanceTest.UmbracoProject/.gitignore @@ -0,0 +1,2 @@ +# Ignore Visual Studio's generated launchSettings file. +Properties/launchSettings.json From 385a5345b1dcd1fbc20afb854cfeff1c36f52210 Mon Sep 17 00:00:00 2001 From: Terence Burridge Date: Wed, 20 Mar 2024 17:58:48 +0000 Subject: [PATCH 08/41] Update valid reasons not to have a template on a content node to include having a redirect field --- src/Umbraco.Web.Website/Routing/UmbracoRouteValuesFactory.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.Website/Routing/UmbracoRouteValuesFactory.cs b/src/Umbraco.Web.Website/Routing/UmbracoRouteValuesFactory.cs index d8fbca45d4..c46bb890b1 100644 --- a/src/Umbraco.Web.Website/Routing/UmbracoRouteValuesFactory.cs +++ b/src/Umbraco.Web.Website/Routing/UmbracoRouteValuesFactory.cs @@ -128,11 +128,12 @@ public class UmbracoRouteValuesFactory : IUmbracoRouteValuesFactory IPublishedRequest request = def.PublishedRequest; // Here we need to check if there is no hijacked route and no template assigned but there is a content item. - // If this is the case we want to return a blank page. + // If this is the case we want to return a blank page, the only exception being if the content item has a redirect field present. // We also check if templates have been disabled since if they are then we're allowed to render even though there's no template, // for example for json rendering in headless. if (request.HasPublishedContent() && !request.HasTemplate() + && !request.IsRedirect() && !_umbracoFeatures.Disabled.DisableTemplates && !hasHijackedRoute) { From 148d51bf8c7b1bbd48555f24320e7d26f61acfd5 Mon Sep 17 00:00:00 2001 From: Emma L Garland Date: Tue, 27 Aug 2024 11:29:02 +0100 Subject: [PATCH 09/41] Remove warnings from Umbraco.Web.UI (#16958) * Fixed warning SA1119 for Umbraco.Web.UI project (unnecessary parenthesis) * Remove parenthesis (startup no longer exists) --------- Co-authored-by: Emma Garland --- src/Umbraco.Web.UI/Program.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI/Program.cs b/src/Umbraco.Web.UI/Program.cs index e91f6ba60d..aa718b5ec3 100644 --- a/src/Umbraco.Web.UI/Program.cs +++ b/src/Umbraco.Web.UI/Program.cs @@ -11,7 +11,7 @@ WebApplication app = builder.Build(); await app.BootUmbracoAsync(); -#if (UseHttpsRedirect) +#if UseHttpsRedirect app.UseHttpsRedirection(); #endif From 98e9a300bc81e485eb2afc95af93c28ad3a1cea9 Mon Sep 17 00:00:00 2001 From: Emma L Garland Date: Tue, 27 Aug 2024 17:56:09 +0100 Subject: [PATCH 10/41] Treat warnings as errors for initial projects (#15019) * Treat warnings as errors for Umbraco.Cms, Umbraco.Cms.Api.Common and Umbraco.Cms.Api.Delivery projects. * Treat warnings as errors for Umbraco.Cms.Imaging.ImageSharp and Umbraco.Cms.Imaging.ImageSharp2 projects * Treat warnings as errors for Umbraco.Cms.Persistence.EFCore.Sqlite, Umbraco.Cms.Persistence.EFCore.SqlServer and Umbraco.Cms.StaticAssets * Treat warnings as errors for Umbraco.Cms.Targets * Treat warnings as errors for templates/Umbraco.Templates and Umbraco.JsonSchema * More projects with warnings as errors. * Reverted warnings as errors due to some file changes since the update. * Remove unwanted tag * Removed warnings as errors on TestData project * Implement warnings as errors in props file, and exclude specific warnings where appropriate as per PR review suggestions. * Reverted spaces * Revert "Reverted spaces" This reverts commit 3734c45e2270c3324768b33e459aefcc6a8c4739. * Update sdk version to fully support Umbraco.code 2.2.0 dependency on Microsoft.CodeAnalysis.CSharp.Workspaces 4.10.0 * Ignore PathToLongErrors for now in static assets --------- Co-authored-by: Emma Garland Co-authored-by: Sven Geusens --- Directory.Build.props | 1 + global.json | 2 +- src/Umbraco.Cms.Api.Common/Umbraco.Cms.Api.Common.csproj | 1 - .../Umbraco.Cms.Api.Delivery.csproj | 5 ++++- .../Umbraco.Cms.Api.Management.csproj | 5 ++++- .../Umbraco.Cms.Imaging.ImageSharp.csproj | 1 - .../Umbraco.Cms.Imaging.ImageSharp2.csproj | 1 - .../Umbraco.Cms.Persistence.EFCore.SqlServer.csproj | 1 - .../Umbraco.Cms.Persistence.EFCore.Sqlite.csproj | 1 - .../Umbraco.Cms.Persistence.EFCore.csproj | 8 ++++++-- .../Umbraco.Cms.Persistence.SqlServer.csproj | 7 ++++++- .../Umbraco.Cms.Persistence.Sqlite.csproj | 5 ++++- .../Umbraco.Cms.StaticAssets.csproj | 7 ++++++- src/Umbraco.Cms.Targets/Umbraco.Cms.Targets.csproj | 6 +++++- src/Umbraco.Cms/Umbraco.Cms.csproj | 1 - src/Umbraco.Core/Umbraco.Core.csproj | 5 ++++- src/Umbraco.Examine.Lucene/Umbraco.Examine.Lucene.csproj | 5 ++++- src/Umbraco.Infrastructure/Umbraco.Infrastructure.csproj | 5 ++++- .../Umbraco.PublishedCache.NuCache.csproj | 5 ++++- src/Umbraco.Web.Common/Umbraco.Web.Common.csproj | 5 ++++- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 4 ++++ src/Umbraco.Web.Website/Umbraco.Web.Website.csproj | 5 ++++- tests/Umbraco.TestData/Umbraco.TestData.csproj | 5 +++++ .../Umbraco.Tests.Benchmarks.csproj | 5 ++++- tests/Umbraco.Tests.Common/Umbraco.Tests.Common.csproj | 5 ++++- .../Umbraco.Tests.Integration.csproj | 5 ++++- .../Umbraco.Tests.UnitTests.csproj | 5 ++++- tools/Umbraco.JsonSchema/Umbraco.JsonSchema.csproj | 5 ++++- 28 files changed, 90 insertions(+), 26 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index 5f3055125f..81e3c40a9b 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -14,6 +14,7 @@ en-US enable nullable + true enable true false diff --git a/global.json b/global.json index 391ba3c2a3..e972eb192a 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "8.0.100", + "version": "8.0.300", "rollForward": "latestFeature" } } diff --git a/src/Umbraco.Cms.Api.Common/Umbraco.Cms.Api.Common.csproj b/src/Umbraco.Cms.Api.Common/Umbraco.Cms.Api.Common.csproj index 523d6f4982..a73fc16574 100644 --- a/src/Umbraco.Cms.Api.Common/Umbraco.Cms.Api.Common.csproj +++ b/src/Umbraco.Cms.Api.Common/Umbraco.Cms.Api.Common.csproj @@ -3,7 +3,6 @@ Umbraco CMS - API Common Contains the bits and pieces that are shared between the Umbraco CMS APIs. - diff --git a/src/Umbraco.Cms.Api.Delivery/Umbraco.Cms.Api.Delivery.csproj b/src/Umbraco.Cms.Api.Delivery/Umbraco.Cms.Api.Delivery.csproj index 8948a49a1a..5e32b81eeb 100644 --- a/src/Umbraco.Cms.Api.Delivery/Umbraco.Cms.Api.Delivery.csproj +++ b/src/Umbraco.Cms.Api.Delivery/Umbraco.Cms.Api.Delivery.csproj @@ -3,7 +3,10 @@ Umbraco CMS - Delivery API Contains the presentation layer for the Umbraco CMS Delivery API. - + + + ASP0019 + diff --git a/src/Umbraco.Cms.Api.Management/Umbraco.Cms.Api.Management.csproj b/src/Umbraco.Cms.Api.Management/Umbraco.Cms.Api.Management.csproj index 37ab3611de..88ee0cef62 100644 --- a/src/Umbraco.Cms.Api.Management/Umbraco.Cms.Api.Management.csproj +++ b/src/Umbraco.Cms.Api.Management/Umbraco.Cms.Api.Management.csproj @@ -7,7 +7,10 @@ Umbraco.Cms.Api.Management Umbraco.Cms.Api.Management - + + + false + 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 549ea5cb40..77aed066d8 100644 --- a/src/Umbraco.Cms.Imaging.ImageSharp/Umbraco.Cms.Imaging.ImageSharp.csproj +++ b/src/Umbraco.Cms.Imaging.ImageSharp/Umbraco.Cms.Imaging.ImageSharp.csproj @@ -3,7 +3,6 @@ Umbraco CMS - Imaging - ImageSharp Adds imaging support using ImageSharp/ImageSharp.Web to Umbraco CMS. - 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 c513082675..16bac191d5 100644 --- a/src/Umbraco.Cms.Imaging.ImageSharp2/Umbraco.Cms.Imaging.ImageSharp2.csproj +++ b/src/Umbraco.Cms.Imaging.ImageSharp2/Umbraco.Cms.Imaging.ImageSharp2.csproj @@ -3,7 +3,6 @@ Umbraco CMS - Imaging - ImageSharp 2 Adds imaging support using ImageSharp/ImageSharp.Web version 2 to Umbraco CMS. - diff --git a/src/Umbraco.Cms.Persistence.EFCore.SqlServer/Umbraco.Cms.Persistence.EFCore.SqlServer.csproj b/src/Umbraco.Cms.Persistence.EFCore.SqlServer/Umbraco.Cms.Persistence.EFCore.SqlServer.csproj index 7e6fc6153d..a43a5f01f2 100644 --- a/src/Umbraco.Cms.Persistence.EFCore.SqlServer/Umbraco.Cms.Persistence.EFCore.SqlServer.csproj +++ b/src/Umbraco.Cms.Persistence.EFCore.SqlServer/Umbraco.Cms.Persistence.EFCore.SqlServer.csproj @@ -3,7 +3,6 @@ Umbraco CMS - Persistence - Entity Framework Core - SQL Server migrations Adds support for Entity Framework Core SQL Server migrations to Umbraco CMS. - diff --git a/src/Umbraco.Cms.Persistence.EFCore.Sqlite/Umbraco.Cms.Persistence.EFCore.Sqlite.csproj b/src/Umbraco.Cms.Persistence.EFCore.Sqlite/Umbraco.Cms.Persistence.EFCore.Sqlite.csproj index 36751bb869..f847ef4786 100644 --- a/src/Umbraco.Cms.Persistence.EFCore.Sqlite/Umbraco.Cms.Persistence.EFCore.Sqlite.csproj +++ b/src/Umbraco.Cms.Persistence.EFCore.Sqlite/Umbraco.Cms.Persistence.EFCore.Sqlite.csproj @@ -3,7 +3,6 @@ Umbraco CMS - Persistence - Entity Framework Core - SQLite migrations Adds support for Entity Framework Core SQLite migrations to Umbraco CMS. - diff --git a/src/Umbraco.Cms.Persistence.EFCore/Umbraco.Cms.Persistence.EFCore.csproj b/src/Umbraco.Cms.Persistence.EFCore/Umbraco.Cms.Persistence.EFCore.csproj index 4500c2812b..d5fc7229b0 100644 --- a/src/Umbraco.Cms.Persistence.EFCore/Umbraco.Cms.Persistence.EFCore.csproj +++ b/src/Umbraco.Cms.Persistence.EFCore/Umbraco.Cms.Persistence.EFCore.csproj @@ -3,8 +3,12 @@ Umbraco CMS - Persistence - Entity Framework Core Adds support for Entity Framework Core to Umbraco CMS. - - + + + IDE0270,CS0108,CS1998 + + diff --git a/src/Umbraco.Cms.Persistence.SqlServer/Umbraco.Cms.Persistence.SqlServer.csproj b/src/Umbraco.Cms.Persistence.SqlServer/Umbraco.Cms.Persistence.SqlServer.csproj index 75e2a6fe60..d62aea6ceb 100644 --- a/src/Umbraco.Cms.Persistence.SqlServer/Umbraco.Cms.Persistence.SqlServer.csproj +++ b/src/Umbraco.Cms.Persistence.SqlServer/Umbraco.Cms.Persistence.SqlServer.csproj @@ -3,7 +3,12 @@ Umbraco CMS - Persistence - SQL Server Adds support for SQL Server to Umbraco CMS. - + + + SA1405,SA1121,SA1117,SA1116,IDE1006,CS0618,IDE0270,IDE0057,IDE0054,CSO618,IDE0048,CS1574 + diff --git a/src/Umbraco.Cms.Persistence.Sqlite/Umbraco.Cms.Persistence.Sqlite.csproj b/src/Umbraco.Cms.Persistence.Sqlite/Umbraco.Cms.Persistence.Sqlite.csproj index f9755aad61..0c39c66d64 100644 --- a/src/Umbraco.Cms.Persistence.Sqlite/Umbraco.Cms.Persistence.Sqlite.csproj +++ b/src/Umbraco.Cms.Persistence.Sqlite/Umbraco.Cms.Persistence.Sqlite.csproj @@ -3,7 +3,10 @@ Umbraco CMS - Persistence - SQLite Adds support for SQLite to Umbraco CMS. - + + + CS0114 + diff --git a/src/Umbraco.Cms.StaticAssets/Umbraco.Cms.StaticAssets.csproj b/src/Umbraco.Cms.StaticAssets/Umbraco.Cms.StaticAssets.csproj index ff487ca66a..80b7bc0211 100644 --- a/src/Umbraco.Cms.StaticAssets/Umbraco.Cms.StaticAssets.csproj +++ b/src/Umbraco.Cms.StaticAssets/Umbraco.Cms.StaticAssets.csproj @@ -6,6 +6,11 @@ / + + + NU5123 + + @@ -66,4 +71,4 @@ - + \ No newline at end of file diff --git a/src/Umbraco.Cms.Targets/Umbraco.Cms.Targets.csproj b/src/Umbraco.Cms.Targets/Umbraco.Cms.Targets.csproj index b83a582ade..e9e0df050d 100644 --- a/src/Umbraco.Cms.Targets/Umbraco.Cms.Targets.csproj +++ b/src/Umbraco.Cms.Targets/Umbraco.Cms.Targets.csproj @@ -5,7 +5,11 @@ false false - + + + IDE0270,CS0108,CS1998 + diff --git a/src/Umbraco.Cms/Umbraco.Cms.csproj b/src/Umbraco.Cms/Umbraco.Cms.csproj index 39a3b03c56..3bc60ebd12 100644 --- a/src/Umbraco.Cms/Umbraco.Cms.csproj +++ b/src/Umbraco.Cms/Umbraco.Cms.csproj @@ -5,7 +5,6 @@ false false - diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index e0d0bd79fa..e4b3a7cd04 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -5,7 +5,10 @@ Contains the core assembly needed to run Umbraco CMS. Umbraco.Cms.Core - + + + false + diff --git a/src/Umbraco.Examine.Lucene/Umbraco.Examine.Lucene.csproj b/src/Umbraco.Examine.Lucene/Umbraco.Examine.Lucene.csproj index 7e28eb6971..e036605449 100644 --- a/src/Umbraco.Examine.Lucene/Umbraco.Examine.Lucene.csproj +++ b/src/Umbraco.Examine.Lucene/Umbraco.Examine.Lucene.csproj @@ -5,7 +5,10 @@ Adds Examine searching support using Lucene to Umbraco CMS. Umbraco.Cms.Infrastructure.Examine - + + + CS0618 + diff --git a/src/Umbraco.Infrastructure/Umbraco.Infrastructure.csproj b/src/Umbraco.Infrastructure/Umbraco.Infrastructure.csproj index a3b64b1cc5..cca5a43954 100644 --- a/src/Umbraco.Infrastructure/Umbraco.Infrastructure.csproj +++ b/src/Umbraco.Infrastructure/Umbraco.Infrastructure.csproj @@ -5,7 +5,10 @@ Contains the infrastructure assembly needed to run Umbraco CMS. Umbraco.Cms.Infrastructure - + + + false + $(DefineConstants);TRACE_SCOPES diff --git a/src/Umbraco.PublishedCache.NuCache/Umbraco.PublishedCache.NuCache.csproj b/src/Umbraco.PublishedCache.NuCache/Umbraco.PublishedCache.NuCache.csproj index 4894759e5f..04986712c8 100644 --- a/src/Umbraco.PublishedCache.NuCache/Umbraco.PublishedCache.NuCache.csproj +++ b/src/Umbraco.PublishedCache.NuCache/Umbraco.PublishedCache.NuCache.csproj @@ -5,7 +5,10 @@ Contains the published cache assembly needed to run Umbraco CMS. Umbraco.Cms.Infrastructure.PublishedCache - + + + false + diff --git a/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj b/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj index 4fa0721a68..6de2cd1278 100644 --- a/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj +++ b/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj @@ -5,7 +5,10 @@ Contains the web assembly needed to run Umbraco CMS. Umbraco.Cms.Web.Common - + + + false + diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index acd631e1ad..c22b5ae739 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -5,6 +5,10 @@ false + + + SA1119 + diff --git a/src/Umbraco.Web.Website/Umbraco.Web.Website.csproj b/src/Umbraco.Web.Website/Umbraco.Web.Website.csproj index 31c057e7b9..8ee1e80bb2 100644 --- a/src/Umbraco.Web.Website/Umbraco.Web.Website.csproj +++ b/src/Umbraco.Web.Website/Umbraco.Web.Website.csproj @@ -5,7 +5,10 @@ Contains the website assembly needed to run the frontend of Umbraco CMS. Umbraco.Cms.Web.Website - + + + false + diff --git a/tests/Umbraco.TestData/Umbraco.TestData.csproj b/tests/Umbraco.TestData/Umbraco.TestData.csproj index 7e19740a31..2b8fc3fca8 100644 --- a/tests/Umbraco.TestData/Umbraco.TestData.csproj +++ b/tests/Umbraco.TestData/Umbraco.TestData.csproj @@ -1,4 +1,9 @@ + + + CS0618,IDE1006,IDE0057 + diff --git a/tests/Umbraco.Tests.Benchmarks/Umbraco.Tests.Benchmarks.csproj b/tests/Umbraco.Tests.Benchmarks/Umbraco.Tests.Benchmarks.csproj index 189d69ac31..5b8f2b3b56 100644 --- a/tests/Umbraco.Tests.Benchmarks/Umbraco.Tests.Benchmarks.csproj +++ b/tests/Umbraco.Tests.Benchmarks/Umbraco.Tests.Benchmarks.csproj @@ -5,7 +5,10 @@ false false - + + + false + diff --git a/tests/Umbraco.Tests.Common/Umbraco.Tests.Common.csproj b/tests/Umbraco.Tests.Common/Umbraco.Tests.Common.csproj index bf0dc9ddc1..24e0dce808 100644 --- a/tests/Umbraco.Tests.Common/Umbraco.Tests.Common.csproj +++ b/tests/Umbraco.Tests.Common/Umbraco.Tests.Common.csproj @@ -7,7 +7,10 @@ true $(BaseEnablePackageValidation) - + + + false + diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj b/tests/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj index b7ab21b062..a04ee45648 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj +++ b/tests/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj @@ -9,7 +9,10 @@ $(BaseEnablePackageValidation) NU5100 - + + + false + diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Tests.UnitTests.csproj b/tests/Umbraco.Tests.UnitTests/Umbraco.Tests.UnitTests.csproj index a411bad38c..6986e03494 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Tests.UnitTests.csproj +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Tests.UnitTests.csproj @@ -5,7 +5,10 @@ false false - + + + false + diff --git a/tools/Umbraco.JsonSchema/Umbraco.JsonSchema.csproj b/tools/Umbraco.JsonSchema/Umbraco.JsonSchema.csproj index ce5001608f..471ebff802 100644 --- a/tools/Umbraco.JsonSchema/Umbraco.JsonSchema.csproj +++ b/tools/Umbraco.JsonSchema/Umbraco.JsonSchema.csproj @@ -4,7 +4,10 @@ false false - + + + false + From 7acce83c83b9809a4746d056fb45bd3938327696 Mon Sep 17 00:00:00 2001 From: marwaldv Date: Mon, 20 Nov 2023 17:36:07 +0100 Subject: [PATCH 11/41] Update LogDto.cs fix: issue resolved, where content saving breaks due to saving to umbracoLog with error message like that: String or binary data would be truncated in table 'dto.umbracoLog', column 'parameters'. Truncated value: 'English (United States), German (Switzerland), French (France), Italian (Italy), Arabic (Iraq), Pers'... --- src/Umbraco.Infrastructure/Persistence/Dtos/LogDto.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Infrastructure/Persistence/Dtos/LogDto.cs b/src/Umbraco.Infrastructure/Persistence/Dtos/LogDto.cs index 89bfeb8612..62f8232da5 100644 --- a/src/Umbraco.Infrastructure/Persistence/Dtos/LogDto.cs +++ b/src/Umbraco.Infrastructure/Persistence/Dtos/LogDto.cs @@ -55,6 +55,6 @@ internal class LogDto /// [Column("parameters")] [NullSetting(NullSetting = NullSettings.Null)] - [Length(500)] + [Length(4000)] public string? Parameters { get; set; } } From 3d5e70654b142d1ee3f786f7533667c069865d41 Mon Sep 17 00:00:00 2001 From: kasparboelkjeldsen <137318845+kasparboelkjeldsen@users.noreply.github.com> Date: Mon, 9 Sep 2024 08:06:52 +0200 Subject: [PATCH 12/41] ModelsGenerator to IModelsGenerator in BuildModelsBuilderController (#16919) * ModelsGenerator to IModelsGenerator in BuildModelsBuilderController * Added obsolete constructors for backwards compatibility --- .../BuildModelsBuilderController.cs | 28 +++++++++++++++++-- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Cms.Api.Management/Controllers/ModelsBuilder/BuildModelsBuilderController.cs b/src/Umbraco.Cms.Api.Management/Controllers/ModelsBuilder/BuildModelsBuilderController.cs index 9d5f67cb1f..14adb70c3b 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/ModelsBuilder/BuildModelsBuilderController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/ModelsBuilder/BuildModelsBuilderController.cs @@ -1,8 +1,9 @@ -using System; +using System; using System.Threading.Tasks; using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Infrastructure.ModelsBuilder; @@ -16,12 +17,13 @@ public class BuildModelsBuilderController : ModelsBuilderControllerBase { private ModelsBuilderSettings _modelsBuilderSettings; private readonly ModelsGenerationError _mbErrors; - private readonly ModelsGenerator _modelGenerator; + private readonly IModelsGenerator _modelGenerator; + [ActivatorUtilitiesConstructor] public BuildModelsBuilderController( IOptionsMonitor modelsBuilderSettings, ModelsGenerationError mbErrors, - ModelsGenerator modelGenerator) + IModelsGenerator modelGenerator) { _mbErrors = mbErrors; _modelGenerator = modelGenerator; @@ -30,6 +32,26 @@ public class BuildModelsBuilderController : ModelsBuilderControllerBase modelsBuilderSettings.OnChange(x => _modelsBuilderSettings = x); } + [Obsolete("Please use the constructor that accepts IModelsGenerator only. Will be removed in V16.")] + public BuildModelsBuilderController( + IOptionsMonitor modelsBuilderSettings, + ModelsGenerationError mbErrors, + ModelsGenerator modelGenerator) + : this(modelsBuilderSettings, mbErrors, (IModelsGenerator)modelGenerator) + { + } + + // this constructor is required for the DI, otherwise it'll throw an "Ambiguous Constructor" errors at boot time. + [Obsolete("Please use the constructor that accepts IModelsGenerator only. Will be removed in V16.")] + public BuildModelsBuilderController( + IOptionsMonitor modelsBuilderSettings, + ModelsGenerationError mbErrors, + IModelsGenerator modelGenerator, + ModelsGenerator notUsed) + : this(modelsBuilderSettings, mbErrors, modelGenerator) + { + } + [HttpPost("build")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status428PreconditionRequired)] From 862820c6a6bfe2aac4127623b9238cfea739d59c Mon Sep 17 00:00:00 2001 From: Emma L Garland Date: Tue, 10 Sep 2024 15:17:29 +0100 Subject: [PATCH 13/41] Resolved more warnings, and marked more warning types as errors (#16990) * Fix warnings SA1111, SA1028, SA1500, IDE1270 in Umbraco.Web.Website, and updated rules. * Remove warnings: IDE0270: Null check can be simplified * More SqlServer project warnings resolved * CS0105 namespace appeared already * Suppress warning until implementation: #pragma warning disable CS0162 // Unreachable code detected #pragma warning disable CS0618 // Type or member is obsolete CS0162 remove unreachable code SA1028 remove trailing whitespace SA1106 no empty statements CS1570 malformed XML CS1572 corrected xml parameter CS1573 param tag added IDE0007 var not explicit IDE0008 explicit not var IDE0057 simplify substring IDE0074 compound assignment CA1825 array.empty Down to 3479 warnings --- .../Indexer/RebuildIndexerController.cs | 4 +- .../Security/BackOfficeController.cs | 4 +- .../MediaTypeEditingPresentationFactory.cs | 3 +- ...SqliteEFCoreDistributedLockingMechanism.cs | 14 +- .../LocalDb.cs | 122 +++++++++++++----- src/Umbraco.Core/Logging/DisposableTimer.cs | 4 +- .../Models/Membership/UserGroup.cs | 2 - .../ITrackedReferencesRepository.cs | 4 +- .../JsonPropertyIndexValueFactoryBase.cs | 1 - .../Routing/ContentFinderByUrlAndTemplate.cs | 4 +- .../Routing/DefaultUrlProvider.cs | 2 +- src/Umbraco.Core/Services/ContentService.cs | 44 ++----- src/Umbraco.Core/Services/DataTypeService.cs | 4 +- src/Umbraco.Core/Services/IAuditService.cs | 11 +- .../Services/IContentPublishingService.cs | 4 +- src/Umbraco.Core/Services/IRelationService.cs | 7 +- .../Services/ITrackedReferencesService.cs | 2 +- .../ImportExport/ContentTypeImportService.cs | 2 +- .../Services/LocalizationService.cs | 4 +- .../RecycleBin/RecycleBinQueryResultType.cs | 2 +- .../Services/RedirectUrlService.cs | 8 +- src/Umbraco.Core/Services/UserService.cs | 12 +- .../Services/WebProfilerService.cs | 4 + .../BackgroundJobs/IRecurringBackgroundJob.cs | 14 +- .../Jobs/HealthCheckNotifierJob.cs | 1 + .../BackgroundJobs/Jobs/LogScrubberJob.cs | 8 +- .../InstructionProcessJob.cs | 2 +- .../Install/FilePermissionHelper.cs | 15 +-- .../Logging/Viewer/LogViewerConfig.cs | 2 - .../Upgrade/UmbracoPremigrationPlan.cs | 1 - .../Repositories/Implement/UserRepository.cs | 8 +- .../ImageCropperPropertyEditor.cs | 1 - .../BackOfficeClaimsPrincipalFactory.cs | 1 + .../Security/BackOfficeIdentityUser.cs | 11 +- ...acoBuilderDependencyInjectionExtensions.cs | 6 +- .../UmbracoBuilderExtensions.cs | 3 +- .../Routing/PublicAccessRequestHandler.cs | 5 +- .../Routing/UmbracoRouteValuesFactory.cs | 13 +- .../Umbraco.Web.Website.csproj | 7 +- .../ViewEngines/ProfilingViewEngine.cs | 2 + .../EnumeratorBenchmarks.cs | 4 +- .../HashFromStreams.cs | 2 +- .../Implementations/TestHelper.cs | 15 +-- .../UmbracoTestServerTestBase.cs | 2 +- .../Testing/TestWebProfilerRepository.cs | 2 +- .../Repositories/ContentTypeRepositoryTest.cs | 2 +- .../Services/LanguageServiceTests.cs | 6 +- .../Umbraco.Core/Components/ComponentTests.cs | 4 +- .../InstructionProcessJobTests.cs | 2 +- .../Jobs/TempFileCleanupJobTests.cs | 2 +- 50 files changed, 205 insertions(+), 204 deletions(-) diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Indexer/RebuildIndexerController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Indexer/RebuildIndexerController.cs index b64c252a1a..97131adcb9 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Indexer/RebuildIndexerController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Indexer/RebuildIndexerController.cs @@ -1,4 +1,4 @@ -using Asp.Versioning; +using Asp.Versioning; using Examine; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; @@ -38,7 +38,7 @@ public class RebuildIndexerController : IndexerControllerBase [ProducesResponseType(StatusCodes.Status200OK)] public async Task Rebuild(CancellationToken cancellationToken, string indexName) { - if (!_examineManager.TryGetIndex(indexName, out var index)) + if (!_examineManager.TryGetIndex(indexName, out IIndex? index)) { var invalidModelProblem = new ProblemDetails { diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Security/BackOfficeController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Security/BackOfficeController.cs index fd6d9116c5..5a247a647c 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Security/BackOfficeController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Security/BackOfficeController.cs @@ -1,4 +1,4 @@ -using System.Security.Claims; +using System.Security.Claims; using Asp.Versioning; using Microsoft.AspNetCore; using Microsoft.AspNetCore.Authentication; @@ -217,7 +217,7 @@ public class BackOfficeController : SecurityControllerBase /// /// Called when a user links an external login provider in the back office /// - /// + /// /// // This method is marked as AllowAnonymous and protected with a secret (linkKey) inside the model for the following reasons // - when a js client uses the fetch api (or old ajax requests) they can send a bearer token diff --git a/src/Umbraco.Cms.Api.Management/Factories/MediaTypeEditingPresentationFactory.cs b/src/Umbraco.Cms.Api.Management/Factories/MediaTypeEditingPresentationFactory.cs index 11b2d18134..c5cd70ed49 100644 --- a/src/Umbraco.Cms.Api.Management/Factories/MediaTypeEditingPresentationFactory.cs +++ b/src/Umbraco.Cms.Api.Management/Factories/MediaTypeEditingPresentationFactory.cs @@ -1,5 +1,4 @@ -using Umbraco.Cms.Api.Management.ViewModels.MediaType; -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Api.Management.ViewModels.MediaType; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.ContentTypeEditing; using Umbraco.Cms.Core.Services; diff --git a/src/Umbraco.Cms.Persistence.EFCore/Locking/SqliteEFCoreDistributedLockingMechanism.cs b/src/Umbraco.Cms.Persistence.EFCore/Locking/SqliteEFCoreDistributedLockingMechanism.cs index 23b3d8d410..8308ae4d3a 100644 --- a/src/Umbraco.Cms.Persistence.EFCore/Locking/SqliteEFCoreDistributedLockingMechanism.cs +++ b/src/Umbraco.Cms.Persistence.EFCore/Locking/SqliteEFCoreDistributedLockingMechanism.cs @@ -115,12 +115,7 @@ internal class SqliteEFCoreDistributedLockingMechanism : IDistributedLockingM // Mostly no-op just check that we didn't end up ReadUncommitted for real. private void ObtainReadLock() { - IEfCoreScope? efCoreScope = _parent._efCoreScopeAccessor.Value.AmbientScope; - - if (efCoreScope is null) - { - throw new PanicException("No current ambient scope"); - } + IEfCoreScope? efCoreScope = _parent._efCoreScopeAccessor.Value.AmbientScope ?? throw new PanicException("No current ambient scope"); efCoreScope.ExecuteWithContextAsync(async database => { @@ -136,12 +131,7 @@ internal class SqliteEFCoreDistributedLockingMechanism : IDistributedLockingM // lock occurs for entire database as opposed to row/table. private void ObtainWriteLock() { - IEfCoreScope? efCoreScope = _parent._efCoreScopeAccessor.Value.AmbientScope; - - if (efCoreScope is null) - { - throw new PanicException("No ambient scope"); - } + IEfCoreScope? efCoreScope = _parent._efCoreScopeAccessor.Value.AmbientScope ?? throw new PanicException("No ambient scope"); efCoreScope.ExecuteWithContextAsync(async database => { diff --git a/src/Umbraco.Cms.Persistence.SqlServer/LocalDb.cs b/src/Umbraco.Cms.Persistence.SqlServer/LocalDb.cs index 6fcdfca76a..a8a6480c89 100644 --- a/src/Umbraco.Cms.Persistence.SqlServer/LocalDb.cs +++ b/src/Umbraco.Cms.Persistence.SqlServer/LocalDb.cs @@ -420,7 +420,13 @@ public class LocalDb /// public bool CreateDatabase(string databaseName, string filesPath) { - GetDatabaseFiles(databaseName, filesPath, out var logName, out _, out _, out var mdfFilename, + GetDatabaseFiles( + databaseName, + filesPath, + out var logName, + out _, + out _, + out var mdfFilename, out var ldfFilename); using (var conn = new SqlConnection(_masterCstr)) @@ -463,7 +469,9 @@ public class LocalDb { conn.Open(); - SetCommand(cmd, @" + SetCommand( + cmd, + @" SELECT name, filename FROM master.dbo.sysdatabases WHERE ('[' + name + ']' = @0 OR name = @0)", databaseName); @@ -554,11 +562,7 @@ public class LocalDb { conn.Open(); - var mdf = GetDatabase(cmd, databaseName); - if (mdf == null) - { - throw new InvalidOperationException("Database does not exist."); - } + var mdf = GetDatabase(cmd, databaseName) ?? throw new InvalidOperationException("Database does not exist."); DetachDatabase(cmd, databaseName); @@ -597,9 +601,12 @@ public class LocalDb /// The LDF logical name. /// The MDF filename. /// The LDF filename. - public void GetFilenames(string databaseName, - out string? mdfName, out string? ldfName, - out string? mdfFilename, out string? ldfFilename) + public void GetFilenames( + string databaseName, + out string? mdfName, + out string? ldfName, + out string? mdfFilename, + out string? ldfFilename) { using (var conn = new SqlConnection(_masterCstr)) using (SqlCommand? cmd = conn.CreateCommand()) @@ -621,7 +628,9 @@ public class LocalDb { conn.Open(); - SetCommand(cmd, @" + SetCommand( + cmd, + @" DECLARE @sql VARCHAR(MAX); SELECT @sql = COALESCE(@sql,'') + 'kill ' + CONVERT(VARCHAR, SPId) + ';' FROM master.sys.sysprocesses @@ -640,7 +649,9 @@ public class LocalDb /// The full filename of the MDF file, if the database exists, otherwise null. private static string? GetDatabase(SqlCommand cmd, string databaseName) { - SetCommand(cmd, @" + SetCommand( + cmd, + @" SELECT name, filename FROM master.dbo.sysdatabases WHERE ('[' + name + ']' = @0 OR name = @0)", databaseName); @@ -707,7 +718,7 @@ public class LocalDb File.Delete(mdf); } - ldf = ldf ?? GetLogFilename(mdf); + ldf ??= GetLogFilename(mdf); if (File.Exists(ldf)) { File.Delete(ldf); @@ -726,7 +737,7 @@ public class LocalDb throw new ArgumentException("Not a valid MDF filename (no .mdf extension).", nameof(mdfFilename)); } - return mdfFilename.Substring(0, mdfFilename.Length - ".mdf".Length) + "_log.ldf"; + return mdfFilename[..^".mdf".Length] + "_log.ldf"; } /// @@ -743,7 +754,9 @@ public class LocalDb var unused1 = cmd.ExecuteNonQuery(); - SetCommand(cmd, @" + SetCommand( + cmd, + @" EXEC sp_detach_db @dbname=@0", databaseName); @@ -758,8 +771,14 @@ public class LocalDb /// The directory containing database files. private static void AttachDatabase(SqlCommand cmd, string databaseName, string filesPath) { - GetDatabaseFiles(databaseName, filesPath, - out var logName, out _, out _, out var mdfFilename, out var ldfFilename); + GetDatabaseFiles( + databaseName, + filesPath, + out var logName, + out _, + out _, + out var mdfFilename, + out var ldfFilename); // cannot use parameters on CREATE DATABASE // ie "CREATE DATABASE @0 ..." does not work @@ -802,13 +821,19 @@ public class LocalDb /// The LDF logical name. /// The MDF filename. /// The LDF filename. - private void GetFilenames(SqlCommand cmd, string databaseName, - out string? mdfName, out string? ldfName, - out string? mdfFilename, out string? ldfFilename) + private void GetFilenames( + SqlCommand cmd, + string databaseName, + out string? mdfName, + out string? ldfName, + out string? mdfFilename, + out string? ldfFilename) { mdfName = ldfName = mdfFilename = ldfFilename = null; - SetCommand(cmd, @" + SetCommand( + cmd, + @" SELECT DB_NAME(database_id), type_desc, name, physical_name FROM master.sys.master_files WHERE database_id=DB_ID(@0)", @@ -854,21 +879,32 @@ public class LocalDb /// . /// Extensions are used eg to copy MyDatabase.mdf to MyDatabase.mdf.temp. /// - public void CopyDatabaseFiles(string databaseName, string filesPath, - string? targetDatabaseName = null, string? targetFilesPath = null, - string? sourceExtension = null, string? targetExtension = null, - bool overwrite = false, bool delete = false) + public void CopyDatabaseFiles( + string databaseName, + string filesPath, + string? targetDatabaseName = null, + string? targetFilesPath = null, + string? sourceExtension = null, + string? targetExtension = null, + bool overwrite = false, + bool delete = false) { var nop = (targetFilesPath == null || targetFilesPath == filesPath) && (targetDatabaseName == null || targetDatabaseName == databaseName) - && (sourceExtension == null && targetExtension == null || sourceExtension == targetExtension); + && ((sourceExtension == null && targetExtension == null) || sourceExtension == targetExtension); if (nop && delete == false) { return; } - GetDatabaseFiles(databaseName, filesPath, - out _, out _, out _, out var mdfFilename, out var ldfFilename); + GetDatabaseFiles( + databaseName, + filesPath, + out _, + out _, + out _, + out var mdfFilename, + out var ldfFilename); if (sourceExtension != null) { @@ -892,8 +928,14 @@ public class LocalDb else { // copy or copy+delete ie move - GetDatabaseFiles(targetDatabaseName ?? databaseName, targetFilesPath ?? filesPath, - out _, out _, out _, out var targetMdfFilename, out var targetLdfFilename); + GetDatabaseFiles( + targetDatabaseName ?? databaseName, + targetFilesPath ?? filesPath, + out _, + out _, + out _, + out var targetMdfFilename, + out var targetLdfFilename); if (targetExtension != null) { @@ -936,8 +978,14 @@ public class LocalDb /// public bool DatabaseFilesExist(string databaseName, string filesPath, string? extension = null) { - GetDatabaseFiles(databaseName, filesPath, - out _, out _, out _, out var mdfFilename, out var ldfFilename); + GetDatabaseFiles( + databaseName, + filesPath, + out _, + out _, + out _, + out var mdfFilename, + out var ldfFilename); if (extension != null) { @@ -958,10 +1006,14 @@ public class LocalDb /// The base log filename (the LDF filename without the .ldf extension). /// The MDF filename. /// The LDF filename. - private static void GetDatabaseFiles(string databaseName, string filesPath, + private static void GetDatabaseFiles( + string databaseName, + string filesPath, out string logName, - out string baseFilename, out string baseLogFilename, - out string mdfFilename, out string ldfFilename) + out string baseFilename, + out string baseLogFilename, + out string mdfFilename, + out string ldfFilename) { logName = databaseName + "_log"; baseFilename = Path.Combine(filesPath, databaseName); diff --git a/src/Umbraco.Core/Logging/DisposableTimer.cs b/src/Umbraco.Core/Logging/DisposableTimer.cs index 087ae7adb6..7db20a0068 100644 --- a/src/Umbraco.Core/Logging/DisposableTimer.cs +++ b/src/Umbraco.Core/Logging/DisposableTimer.cs @@ -44,7 +44,7 @@ public class DisposableTimer : DisposableObjectSlim _endMessageArgs = endMessageArgs; _failMessageArgs = failMessageArgs; _thresholdMilliseconds = thresholdMilliseconds < 0 ? 0 : thresholdMilliseconds; - _timingId = Guid.NewGuid().ToString("N").Substring(0, 7); // keep it short-ish + _timingId = Guid.NewGuid().ToString("N")[..7]; // keep it short-ish if (thresholdMilliseconds == 0) { @@ -63,7 +63,7 @@ public class DisposableTimer : DisposableObjectSlim var args = new object[startMessageArgs.Length + 1]; startMessageArgs.CopyTo(args, 0); args[^1] = _timingId; - + if (_logger.IsEnabled(Microsoft.Extensions.Logging.LogLevel.Debug)) { logger.LogDebug(startMessage + " [Timing {TimingId}]", args); diff --git a/src/Umbraco.Core/Models/Membership/UserGroup.cs b/src/Umbraco.Core/Models/Membership/UserGroup.cs index c512702d54..95f8dee7b8 100644 --- a/src/Umbraco.Core/Models/Membership/UserGroup.cs +++ b/src/Umbraco.Core/Models/Membership/UserGroup.cs @@ -58,8 +58,6 @@ public class UserGroup : EntityBase, IUserGroup, IReadOnlyUserGroup /// /// /// - /// - /// /// /// public UserGroup( diff --git a/src/Umbraco.Core/Persistence/Repositories/ITrackedReferencesRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ITrackedReferencesRepository.cs index fda745c2f6..4442e5ecf2 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ITrackedReferencesRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ITrackedReferencesRepository.cs @@ -54,7 +54,7 @@ public interface ITrackedReferencesRepository /// Gets a page of items which are in relation with the current item. /// Basically, shows the items which depend on the current item. /// - /// The identifier of the entity to retrieve relations for. + /// The identifier of the entity to retrieve relations for. /// The amount of items to skip. /// The amount of items to take. /// @@ -81,7 +81,7 @@ public interface ITrackedReferencesRepository /// /// Gets a page of items used in any kind of relation from selected integer ids. /// - /// The identifiers of the entities to check for relations. + /// The identifiers of the entities to check for relations. /// The amount of items to skip. /// The amount of items to take. /// diff --git a/src/Umbraco.Core/PropertyEditors/JsonPropertyIndexValueFactoryBase.cs b/src/Umbraco.Core/PropertyEditors/JsonPropertyIndexValueFactoryBase.cs index a6f3b5739b..e4a7492c5d 100644 --- a/src/Umbraco.Core/PropertyEditors/JsonPropertyIndexValueFactoryBase.cs +++ b/src/Umbraco.Core/PropertyEditors/JsonPropertyIndexValueFactoryBase.cs @@ -2,7 +2,6 @@ using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Serialization; -using Umbraco.Cms.Core.Serialization; using Umbraco.Extensions; namespace Umbraco.Cms.Core.PropertyEditors; diff --git a/src/Umbraco.Core/Routing/ContentFinderByUrlAndTemplate.cs b/src/Umbraco.Core/Routing/ContentFinderByUrlAndTemplate.cs index 39fc468cee..200a7d7143 100644 --- a/src/Umbraco.Core/Routing/ContentFinderByUrlAndTemplate.cs +++ b/src/Umbraco.Core/Routing/ContentFinderByUrlAndTemplate.cs @@ -77,8 +77,8 @@ public class ContentFinderByUrlAndTemplate : ContentFinderByUrl // look for template in last position var pos = path.LastIndexOf('/'); - var templateAlias = path.Substring(pos + 1); - path = pos == 0 ? "/" : path.Substring(0, pos);; + var templateAlias = path[(pos + 1)..]; + path = pos == 0 ? "/" : path[..pos]; ITemplate? template = _fileService.GetTemplate(templateAlias); diff --git a/src/Umbraco.Core/Routing/DefaultUrlProvider.cs b/src/Umbraco.Core/Routing/DefaultUrlProvider.cs index c30d5453aa..4a5d5d2826 100644 --- a/src/Umbraco.Core/Routing/DefaultUrlProvider.cs +++ b/src/Umbraco.Core/Routing/DefaultUrlProvider.cs @@ -98,7 +98,7 @@ public class DefaultUrlProvider : IUrlProvider // need to strip off the leading ID for the route if it exists (occurs if the route is for a node with a domain assigned) var pos = route.IndexOf('/'); - var path = pos == 0 ? route : route.Substring(pos); + var path = pos == 0 ? route : route[pos..]; var uri = new Uri(CombinePaths(d.Uri.GetLeftPart(UriPartial.Path), path)); uri = _uriUtility.UriFromUmbraco(uri, _requestSettings); diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs index dd61585203..cd005b576c 100644 --- a/src/Umbraco.Core/Services/ContentService.cs +++ b/src/Umbraco.Core/Services/ContentService.cs @@ -582,10 +582,7 @@ public class ContentService : RepositoryService, IContentService throw new ArgumentOutOfRangeException(nameof(pageSize)); } - if (ordering == null) - { - ordering = Ordering.By("sortOrder"); - } + ordering ??= Ordering.By("sortOrder"); using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { @@ -613,10 +610,7 @@ public class ContentService : RepositoryService, IContentService throw new ArgumentOutOfRangeException(nameof(pageSize)); } - if (ordering == null) - { - ordering = Ordering.By("sortOrder"); - } + ordering ??= Ordering.By("sortOrder"); using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { @@ -773,10 +767,7 @@ public class ContentService : RepositoryService, IContentService throw new ArgumentOutOfRangeException(nameof(pageSize)); } - if (ordering == null) - { - ordering = Ordering.By("sortOrder"); - } + ordering ??= Ordering.By("sortOrder"); using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { @@ -790,10 +781,7 @@ public class ContentService : RepositoryService, IContentService /// public IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalChildren, IQuery? filter = null, Ordering? ordering = null) { - if (ordering == null) - { - ordering = Ordering.By("Path"); - } + ordering ??= Ordering.By("Path"); using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { @@ -931,10 +919,7 @@ public class ContentService : RepositoryService, IContentService { using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { - if (ordering == null) - { - ordering = Ordering.By("Path"); - } + ordering ??= Ordering.By("Path"); scope.ReadLock(Constants.Locks.ContentTree); IQuery? query = Query()? @@ -1361,6 +1346,7 @@ public class ContentService : RepositoryService, IContentService /// /// /// + /// /// /// /// @@ -1831,7 +1817,7 @@ public class ContentService : RepositoryService, IContentService // publish the culture values and validate the property values, if validation fails, log the invalid properties so the develeper has an idea of what has failed IProperty[]? invalidProperties = null; - var impact = _cultureImpactFactory.ImpactExplicit(culture, IsDefaultCulture(allLangs.Value, culture)); + CultureImpact impact = _cultureImpactFactory.ImpactExplicit(culture, IsDefaultCulture(allLangs.Value, culture)); var tryPublish = d.PublishCulture(impact) && _propertyValidationService.Value.IsPropertyDataValid(d, out invalidProperties, impact); if (invalidProperties != null && invalidProperties.Length > 0) @@ -1913,7 +1899,7 @@ public class ContentService : RepositoryService, IContentService { return culturesToPublish.All(culture => { - var impact = _cultureImpactFactory.Create(culture, IsDefaultCulture(allLangs, culture), content); + CultureImpact? impact = _cultureImpactFactory.Create(culture, IsDefaultCulture(allLangs, culture), content); return content.PublishCulture(impact) && _propertyValidationService.Value.IsPropertyDataValid(content, out _, impact); }); @@ -1929,10 +1915,7 @@ public class ContentService : RepositoryService, IContentService // if published, republish if (published) { - if (cultures == null) - { - cultures = new HashSet(); // empty means 'already published' - } + cultures ??= new HashSet(); // empty means 'already published' if (edited) { @@ -1948,10 +1931,7 @@ public class ContentService : RepositoryService, IContentService return cultures; // null means 'nothing to do' } - if (cultures == null) - { - cultures = new HashSet(); - } + cultures ??= new HashSet(); cultures.Add(c); // means 'publish this culture' return cultures; @@ -2654,7 +2634,6 @@ public class ContentService : RepositoryService, IContentService /// /// The to copy /// Id of the Content's new Parent - /// Key of the Content's new Parent /// Boolean indicating whether the copy should be related to the original /// A value indicating whether to recursively copy children. /// Optional Id of the User copying the Content @@ -3300,6 +3279,7 @@ public class ContentService : RepositoryService, IContentService /// /// /// + /// /// private PublishResult StrategyCanUnpublish( ICoreScope scope, @@ -3308,7 +3288,7 @@ public class ContentService : RepositoryService, IContentService IDictionary? notificationState) { // raise Unpublishing notification - var notification = new ContentUnpublishingNotification(content, evtMsgs).WithState(notificationState); + ContentUnpublishingNotification notification = new ContentUnpublishingNotification(content, evtMsgs).WithState(notificationState); var notificationResult = scope.Notifications.PublishCancelable(notification); if (notificationResult) diff --git a/src/Umbraco.Core/Services/DataTypeService.cs b/src/Umbraco.Core/Services/DataTypeService.cs index 382238d583..7797a30015 100644 --- a/src/Umbraco.Core/Services/DataTypeService.cs +++ b/src/Umbraco.Core/Services/DataTypeService.cs @@ -330,7 +330,7 @@ namespace Umbraco.Cms.Core.Services.Implement return Task.FromResult(dataTypes); } - + /// public async Task> GetByEditorAliasAsync(string[] propertyEditorAlias) { @@ -555,7 +555,7 @@ namespace Umbraco.Cms.Core.Services.Implement return Attempt.FailWithStatus(DataTypeOperationStatus.DuplicateKey, dataType); } - var result = await SaveAsync(dataType, () => DataTypeOperationStatus.Success, userKey, AuditType.New); + Attempt result = await SaveAsync(dataType, () => DataTypeOperationStatus.Success, userKey, AuditType.New); scope.Complete(); diff --git a/src/Umbraco.Core/Services/IAuditService.cs b/src/Umbraco.Core/Services/IAuditService.cs index e4c973ced0..7481a5e613 100644 --- a/src/Umbraco.Core/Services/IAuditService.cs +++ b/src/Umbraco.Core/Services/IAuditService.cs @@ -105,10 +105,9 @@ public interface IAuditService : IService /// /// Returns paged items in the audit trail for a given user /// - /// - /// - /// - /// + /// + /// + /// /// /// By default this will always be ordered descending (newest first) /// @@ -117,9 +116,7 @@ public interface IAuditService : IService /// or the custom filter /// so we need to do that here /// - /// - /// Optional filter to be applied - /// + /// /// Task> GetPagedItemsByUserAsync( Guid userKey, diff --git a/src/Umbraco.Core/Services/IContentPublishingService.cs b/src/Umbraco.Core/Services/IContentPublishingService.cs index 0d60da7313..701aba3166 100644 --- a/src/Umbraco.Core/Services/IContentPublishingService.cs +++ b/src/Umbraco.Core/Services/IContentPublishingService.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models.ContentPublishing; +using Umbraco.Cms.Core.Models.ContentPublishing; using Umbraco.Cms.Core.Services.OperationStatus; namespace Umbraco.Cms.Core.Services; @@ -9,7 +9,7 @@ public interface IContentPublishingService /// Publishes a single content item. /// /// The key of the root content. - /// The cultures to publish. + /// The cultures to publish and their publishing schedules. /// The identifier of the user performing the operation. /// Result of the publish operation. Task> PublishAsync(Guid key, CultureAndScheduleModel cultureAndSchedule, Guid userKey); diff --git a/src/Umbraco.Core/Services/IRelationService.cs b/src/Umbraco.Core/Services/IRelationService.cs index 04e99528b5..676e7ea17c 100644 --- a/src/Umbraco.Core/Services/IRelationService.cs +++ b/src/Umbraco.Core/Services/IRelationService.cs @@ -177,9 +177,8 @@ public interface IRelationService : IService /// Gets a paged result of /// /// - /// - /// - /// + /// + /// /// /// Task, RelationOperationStatus>> GetPagedByRelationTypeKeyAsync(Guid key, int skip, int take, Ordering? ordering = null); @@ -403,6 +402,8 @@ public interface IRelationService : IService /// Gets the Relation types in a paged manner. /// Currently implements the paging in memory on the name attribute because the underlying repository does not support paging yet /// + /// + /// /// /// Task> GetPagedRelationTypesAsync(int skip, int take, params int[] ids); diff --git a/src/Umbraco.Core/Services/ITrackedReferencesService.cs b/src/Umbraco.Core/Services/ITrackedReferencesService.cs index 2b5dcfb833..7cfcd1c117 100644 --- a/src/Umbraco.Core/Services/ITrackedReferencesService.cs +++ b/src/Umbraco.Core/Services/ITrackedReferencesService.cs @@ -54,7 +54,7 @@ public interface ITrackedReferencesService /// Gets a paged result of items which are in relation with the current item. /// Basically, shows the items which depend on the current item. /// - /// The identifier of the entity to retrieve relations for. + /// The identifier of the entity to retrieve relations for. /// The amount of items to skip /// The amount of items to take. /// diff --git a/src/Umbraco.Core/Services/ImportExport/ContentTypeImportService.cs b/src/Umbraco.Core/Services/ImportExport/ContentTypeImportService.cs index ec9cc5a5ce..c1f97d8d40 100644 --- a/src/Umbraco.Core/Services/ImportExport/ContentTypeImportService.cs +++ b/src/Umbraco.Core/Services/ImportExport/ContentTypeImportService.cs @@ -32,7 +32,7 @@ public class ContentTypeImportService : IContentTypeImportService /// Imports the contentType /// /// - /// + /// /// the id of the contentType to overwrite, null if a new contentType should be created /// public async Task> Import( diff --git a/src/Umbraco.Core/Services/LocalizationService.cs b/src/Umbraco.Core/Services/LocalizationService.cs index e1fcfab220..989b8f8206 100644 --- a/src/Umbraco.Core/Services/LocalizationService.cs +++ b/src/Umbraco.Core/Services/LocalizationService.cs @@ -1,4 +1,4 @@ -using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Events; @@ -220,7 +220,7 @@ internal class LocalizationService : RepositoryService, ILocalizationService /// Optional id of the user saving the dictionary item [Obsolete("Please use IDictionaryItemService for dictionary item operations. Will be removed in V15.")] public void Save(IDictionaryItem dictionaryItem, int userId = Constants.Security.SuperUserId) - { ; + { Guid currentUserKey = _userIdKeyResolver.GetAsync(userId).GetAwaiter().GetResult(); if (dictionaryItem.Id > 0) { diff --git a/src/Umbraco.Core/Services/Querying/RecycleBin/RecycleBinQueryResultType.cs b/src/Umbraco.Core/Services/Querying/RecycleBin/RecycleBinQueryResultType.cs index e864020a15..f9f0a34a75 100644 --- a/src/Umbraco.Core/Services/Querying/RecycleBin/RecycleBinQueryResultType.cs +++ b/src/Umbraco.Core/Services/Querying/RecycleBin/RecycleBinQueryResultType.cs @@ -1,7 +1,7 @@ namespace Umbraco.Cms.Core.Services.Querying.RecycleBin; /// -/// < 10 = Success +/// <10 = Success. /// public enum RecycleBinQueryResultType { diff --git a/src/Umbraco.Core/Services/RedirectUrlService.cs b/src/Umbraco.Core/Services/RedirectUrlService.cs index 07646de3de..0944b2a6a1 100644 --- a/src/Umbraco.Core/Services/RedirectUrlService.cs +++ b/src/Umbraco.Core/Services/RedirectUrlService.cs @@ -1,4 +1,4 @@ -using System.Threading.Tasks; +using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; @@ -84,7 +84,7 @@ internal class RedirectUrlService : RepositoryService, IRedirectUrlService public async Task GetMostRecentRedirectUrlAsync(string url) { - using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true)) + using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { return await _redirectUrlRepository.GetMostRecentUrlAsync(url); } @@ -142,9 +142,9 @@ internal class RedirectUrlService : RepositoryService, IRedirectUrlService return await GetMostRecentRedirectUrlAsync(url); } - using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true)) + using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { return await _redirectUrlRepository.GetMostRecentUrlAsync(url, culture); } - } + } } diff --git a/src/Umbraco.Core/Services/UserService.cs b/src/Umbraco.Core/Services/UserService.cs index dd25791d13..0167dc6d92 100644 --- a/src/Umbraco.Core/Services/UserService.cs +++ b/src/Umbraco.Core/Services/UserService.cs @@ -4,14 +4,12 @@ using System.Linq.Expressions; using Microsoft.Extensions.DependencyInjection; using System.Security.Claims; using System.Security.Cryptography; -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.Editors; -using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Exceptions; using Umbraco.Cms.Core.IO; @@ -156,7 +154,7 @@ internal class UserService : RepositoryService, IUserService if (permissions.Any(x => x.EntityId == nodeId)) { EntityPermission found = permissions.First(x => x.EntityId == nodeId); - var assignedPermissionsArray = found.AssignedPermissions; + ISet assignedPermissionsArray = found.AssignedPermissions; // Working with permissions assigned directly to a user AND to their groups, so maybe several per node // and we need to get the most permissive set @@ -668,7 +666,7 @@ internal class UserService : RepositoryService, IUserService return Attempt.FailWithStatus(UserOperationStatus.MissingUser, new UserCreationResult()); } - var userGroups = _userGroupRepository.GetMany().Where(x=>model.UserGroupKeys.Contains(x.Key)).ToArray(); + IUserGroup[] userGroups = _userGroupRepository.GetMany().Where(x=>model.UserGroupKeys.Contains(x.Key)).ToArray(); if (userGroups.Length != model.UserGroupKeys.Count) { @@ -789,7 +787,7 @@ internal class UserService : RepositoryService, IUserService return Attempt.FailWithStatus(UserOperationStatus.MissingUser, new UserInvitationResult()); } - var userGroups = _userGroupRepository.GetMany().Where(x=>model.UserGroupKeys.Contains(x.Key)).ToArray(); + IUserGroup[] userGroups = _userGroupRepository.GetMany().Where(x => model.UserGroupKeys.Contains(x.Key)).ToArray(); if (userGroups.Length != model.UserGroupKeys.Count) { @@ -2298,7 +2296,7 @@ internal class UserService : RepositoryService, IUserService var results = new List(); foreach (KeyValuePair node in nodes) { - var permissions = permissionsCollection.GetAllPermissions(node.Value); + ISet permissions = permissionsCollection.GetAllPermissions(node.Value); results.Add(new NodePermissions { NodeKey = node.Key, Permissions = permissions }); } @@ -2361,7 +2359,7 @@ internal class UserService : RepositoryService, IUserService var results = new List(); foreach (int nodeId in idKeyMap.Keys) { - var permissions = permissionCollection.GetAllPermissions(nodeId); + ISet permissions = permissionCollection.GetAllPermissions(nodeId); results.Add(new NodePermissions { NodeKey = idKeyMap[nodeId], Permissions = permissions }); } diff --git a/src/Umbraco.Core/Services/WebProfilerService.cs b/src/Umbraco.Core/Services/WebProfilerService.cs index 026c2afd38..e895bba6e7 100644 --- a/src/Umbraco.Core/Services/WebProfilerService.cs +++ b/src/Umbraco.Core/Services/WebProfilerService.cs @@ -46,7 +46,11 @@ internal sealed class WebProfilerService : IWebProfilerService //FIXME when we can get current user return Attempt.Succeed(-1); +#pragma warning disable CS0162 // Unreachable code detected +#pragma warning disable CS0618 // Type or member is obsolete Attempt? userIdAttempt = _backOfficeSecurityAccessor?.BackOfficeSecurity?.GetUserId(); +#pragma warning restore CS0618 // Type or member is obsolete +#pragma warning restore CS0162 // Unreachable code detected return (userIdAttempt.HasValue && userIdAttempt.Value.Success) ? Attempt.Succeed(userIdAttempt.Value.Result) diff --git a/src/Umbraco.Infrastructure/BackgroundJobs/IRecurringBackgroundJob.cs b/src/Umbraco.Infrastructure/BackgroundJobs/IRecurringBackgroundJob.cs index c6be3dcec5..db57d02665 100644 --- a/src/Umbraco.Infrastructure/BackgroundJobs/IRecurringBackgroundJob.cs +++ b/src/Umbraco.Infrastructure/BackgroundJobs/IRecurringBackgroundJob.cs @@ -8,15 +8,17 @@ namespace Umbraco.Cms.Infrastructure.BackgroundJobs; public interface IRecurringBackgroundJob { static readonly TimeSpan DefaultDelay = System.TimeSpan.FromMinutes(3); - static readonly ServerRole[] DefaultServerRoles = new[] { ServerRole.Single, ServerRole.SchedulingPublisher }; + static readonly ServerRole[] DefaultServerRoles = new[] { ServerRole.Single, ServerRole.SchedulingPublisher }; - /// Timespan representing how often the task should recur. + /// + /// Timespan representing how often the task should recur. + /// TimeSpan Period { get; } - /// - /// Timespan representing the initial delay after application start-up before the first run of the task - /// occurs. - /// + /// + /// Timespan representing the initial delay after application start-up before the first run of the task + /// occurs. + /// TimeSpan Delay { get => DefaultDelay; } ServerRole[] ServerRoles { get => DefaultServerRoles; } diff --git a/src/Umbraco.Infrastructure/BackgroundJobs/Jobs/HealthCheckNotifierJob.cs b/src/Umbraco.Infrastructure/BackgroundJobs/Jobs/HealthCheckNotifierJob.cs index cf99583fc4..c79a1a0866 100644 --- a/src/Umbraco.Infrastructure/BackgroundJobs/Jobs/HealthCheckNotifierJob.cs +++ b/src/Umbraco.Infrastructure/BackgroundJobs/Jobs/HealthCheckNotifierJob.cs @@ -47,6 +47,7 @@ public class HealthCheckNotifierJob : IRecurringBackgroundJob /// The typed logger. /// The profiling logger. /// Parser of crontab expressions. + /// public HealthCheckNotifierJob( IOptionsMonitor healthChecksSettings, HealthCheckCollection healthChecks, diff --git a/src/Umbraco.Infrastructure/BackgroundJobs/Jobs/LogScrubberJob.cs b/src/Umbraco.Infrastructure/BackgroundJobs/Jobs/LogScrubberJob.cs index 1c745661cb..3fcbe8d3d6 100644 --- a/src/Umbraco.Infrastructure/BackgroundJobs/Jobs/LogScrubberJob.cs +++ b/src/Umbraco.Infrastructure/BackgroundJobs/Jobs/LogScrubberJob.cs @@ -30,7 +30,7 @@ public class LogScrubberJob : IRecurringBackgroundJob private readonly IAuditService _auditService; private readonly ILogger _logger; private readonly IProfilingLogger _profilingLogger; - private readonly ICoreScopeProvider _scopeProvider; + private readonly ICoreScopeProvider _scopeProvider; private LoggingSettings _settings; /// @@ -41,14 +41,14 @@ public class LogScrubberJob : IRecurringBackgroundJob /// Provides scopes for database operations. /// The typed logger. /// The profiling logger. - public LogScrubberJob( + public LogScrubberJob( IAuditService auditService, IOptionsMonitor settings, ICoreScopeProvider scopeProvider, ILogger logger, IProfilingLogger profilingLogger) { - + _auditService = auditService; _settings = settings.CurrentValue; _scopeProvider = scopeProvider; @@ -59,7 +59,7 @@ public class LogScrubberJob : IRecurringBackgroundJob public Task RunJobAsync() { - + // Ensure we use an explicit scope since we are running on a background thread. using (ICoreScope scope = _scopeProvider.CreateCoreScope()) using (_profilingLogger.DebugDuration("Log scrubbing executing", "Log scrubbing complete")) diff --git a/src/Umbraco.Infrastructure/BackgroundJobs/Jobs/ServerRegistration/InstructionProcessJob.cs b/src/Umbraco.Infrastructure/BackgroundJobs/Jobs/ServerRegistration/InstructionProcessJob.cs index cc6e35bf83..87b459d69f 100644 --- a/src/Umbraco.Infrastructure/BackgroundJobs/Jobs/ServerRegistration/InstructionProcessJob.cs +++ b/src/Umbraco.Infrastructure/BackgroundJobs/Jobs/ServerRegistration/InstructionProcessJob.cs @@ -55,5 +55,5 @@ public class InstructionProcessJob : IRecurringBackgroundJob } return Task.CompletedTask; - } + } } diff --git a/src/Umbraco.Infrastructure/Install/FilePermissionHelper.cs b/src/Umbraco.Infrastructure/Install/FilePermissionHelper.cs index ccb3e4a0da..aab9c71acd 100644 --- a/src/Umbraco.Infrastructure/Install/FilePermissionHelper.cs +++ b/src/Umbraco.Infrastructure/Install/FilePermissionHelper.cs @@ -92,10 +92,7 @@ public class FilePermissionHelper : IFilePermissionHelper continue; } - if (temp == null) - { - temp = new List(); - } + temp ??= new List(); temp.Add(dir.TrimStart(_basePath)); success = false; @@ -117,10 +114,7 @@ public class FilePermissionHelper : IFilePermissionHelper continue; } - if (temp == null) - { - temp = new List(); - } + temp ??= new List(); temp.Add(file.TrimStart(_basePath)); success = false; @@ -145,10 +139,7 @@ public class FilePermissionHelper : IFilePermissionHelper continue; } - if (temp == null) - { - temp = new List(); - } + temp ??= new List(); temp.Add(dir); success = false; diff --git a/src/Umbraco.Infrastructure/Logging/Viewer/LogViewerConfig.cs b/src/Umbraco.Infrastructure/Logging/Viewer/LogViewerConfig.cs index 3e4b0b7931..3bd18bc1ac 100644 --- a/src/Umbraco.Infrastructure/Logging/Viewer/LogViewerConfig.cs +++ b/src/Umbraco.Infrastructure/Logging/Viewer/LogViewerConfig.cs @@ -49,7 +49,5 @@ public class LogViewerConfig : ILogViewerConfig IReadOnlyList result = GetSavedSearches()!; scope.Complete(); return result; - scope.Complete(); - return result; } } diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPremigrationPlan.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPremigrationPlan.cs index 23f4aa108c..c9d23acb90 100644 --- a/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPremigrationPlan.cs +++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPremigrationPlan.cs @@ -12,7 +12,6 @@ public class UmbracoPremigrationPlan : MigrationPlan /// /// Initializes a new instance of the class. /// - /// The Umbraco version. public UmbracoPremigrationPlan() : base(Constants.Conventions.Migrations.UmbracoUpgradePlanPremigrationsName) => DefinePlan(); diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserRepository.cs index 332339e49c..892fa78ae4 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserRepository.cs @@ -411,8 +411,8 @@ SELECT 4 AS [Key], COUNT(id) AS [Value] FROM umbracoUser WHERE userDisabled = 0 List userIds = dtos.Count == 1 ? new List {dtos[0].Id} : dtos.Select(x => x.Id).ToList(); Dictionary? xUsers = dtos.Count == 1 ? null : dtos.ToDictionary(x => x.Id, x => x); - List groupIds = new List(); - List groupKeys = new List(); + var groupIds = new List(); + var groupKeys = new List(); Sql sql; try { @@ -595,9 +595,9 @@ SELECT 4 AS [Key], COUNT(id) AS [Value] FROM umbracoUser WHERE userDisabled = 0 // map languages - foreach (var group in groups.Values) + foreach (UserGroupDto group in groups.Values) { - if (groups2languages.TryGetValue(group.Id, out var list)) + if (groups2languages.TryGetValue(group.Id, out IGrouping? list)) { group.UserGroup2LanguageDtos = list.ToList(); // groups2apps is distinct } diff --git a/src/Umbraco.Infrastructure/PropertyEditors/ImageCropperPropertyEditor.cs b/src/Umbraco.Infrastructure/PropertyEditors/ImageCropperPropertyEditor.cs index 449d59ba42..46f02863a9 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/ImageCropperPropertyEditor.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/ImageCropperPropertyEditor.cs @@ -197,7 +197,6 @@ public class ImageCropperPropertyEditor : DataEditor, IMediaUrlGenerator, /// Returns the "src" property from the json structure if the value is formatted correctly /// /// - /// The deserialized value /// Should the path returned be the application relative path /// private string? GetFileSrcFromPropertyValue(object? propVal, bool relative = true) diff --git a/src/Umbraco.Infrastructure/Security/BackOfficeClaimsPrincipalFactory.cs b/src/Umbraco.Infrastructure/Security/BackOfficeClaimsPrincipalFactory.cs index eeac48801d..32a554aff0 100644 --- a/src/Umbraco.Infrastructure/Security/BackOfficeClaimsPrincipalFactory.cs +++ b/src/Umbraco.Infrastructure/Security/BackOfficeClaimsPrincipalFactory.cs @@ -17,6 +17,7 @@ public class BackOfficeClaimsPrincipalFactory : UserClaimsPrincipalFactory /// The user manager /// The + /// public BackOfficeClaimsPrincipalFactory( UserManager userManager, IOptions optionsAccessor, diff --git a/src/Umbraco.Infrastructure/Security/BackOfficeIdentityUser.cs b/src/Umbraco.Infrastructure/Security/BackOfficeIdentityUser.cs index 4c7943c69c..4341b01b79 100644 --- a/src/Umbraco.Infrastructure/Security/BackOfficeIdentityUser.cs +++ b/src/Umbraco.Infrastructure/Security/BackOfficeIdentityUser.cs @@ -62,10 +62,7 @@ public class BackOfficeIdentityUser : UmbracoIdentityUser get => _startContentIds; set { - if (value == null) - { - value = new int[0]; - } + value ??= new int[0]; BeingDirty.SetPropertyValueAndDetectChanges(value, ref _startContentIds!, nameof(StartContentIds), _startIdsComparer); } @@ -79,10 +76,7 @@ public class BackOfficeIdentityUser : UmbracoIdentityUser get => _startMediaIds; set { - if (value == null) - { - value = new int[0]; - } + value ??= Array.Empty(); BeingDirty.SetPropertyValueAndDetectChanges(value, ref _startMediaIds!, nameof(StartMediaIds), _startIdsComparer); } @@ -123,6 +117,7 @@ public class BackOfficeIdentityUser : UmbracoIdentityUser /// This is allowed to be null (but would need to be filled in if trying to persist this instance) /// /// + /// public static BackOfficeIdentityUser CreateNew(GlobalSettings globalSettings, string? username, string email, string culture, string? name = null, Guid? id = null) { if (string.IsNullOrWhiteSpace(username)) diff --git a/src/Umbraco.Web.Common/ModelsBuilder/DependencyInjection/UmbracoBuilderDependencyInjectionExtensions.cs b/src/Umbraco.Web.Common/ModelsBuilder/DependencyInjection/UmbracoBuilderDependencyInjectionExtensions.cs index 20cd35d0d1..3784781413 100644 --- a/src/Umbraco.Web.Common/ModelsBuilder/DependencyInjection/UmbracoBuilderDependencyInjectionExtensions.cs +++ b/src/Umbraco.Web.Common/ModelsBuilder/DependencyInjection/UmbracoBuilderDependencyInjectionExtensions.cs @@ -126,7 +126,7 @@ public static class UmbracoBuilderDependencyInjectionExtensions builder.AddNotificationHandler(); builder.AddNotificationHandler(); builder.AddNotificationHandler(); - + builder.AddNotificationHandler(); builder.AddNotificationHandler(); } @@ -135,7 +135,7 @@ public static class UmbracoBuilderDependencyInjectionExtensions // Register required services for ModelsBuilderDashboardController builder.Services.AddSingleton(); - + // TODO: Remove in v13 - this is only here in case someone is already using this generator directly builder.Services.AddSingleton(); builder.Services.AddSingleton(); @@ -165,7 +165,7 @@ public static class UmbracoBuilderDependencyInjectionExtensions // This is what the community MB would replace, all of the above services are fine to be registered builder.Services.AddSingleton(factory => factory.CreateDefaultPublishedModelFactory()); - + return builder; } } diff --git a/src/Umbraco.Web.Website/DependencyInjection/UmbracoBuilderExtensions.cs b/src/Umbraco.Web.Website/DependencyInjection/UmbracoBuilderExtensions.cs index f182bda7b6..400b288786 100644 --- a/src/Umbraco.Web.Website/DependencyInjection/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.Web.Website/DependencyInjection/UmbracoBuilderExtensions.cs @@ -57,8 +57,7 @@ public static partial class UmbracoBuilderExtensions x.GetRequiredService(), x.GetRequiredService(), x.GetRequiredService(), - x.GetRequiredService>() - )); + x.GetRequiredService>())); builder.Services.AddSingleton(); builder.Services.TryAddEnumerable(Singleton()); builder.Services.AddSingleton(); diff --git a/src/Umbraco.Web.Website/Routing/PublicAccessRequestHandler.cs b/src/Umbraco.Web.Website/Routing/PublicAccessRequestHandler.cs index b7c751d28c..801e4af08f 100644 --- a/src/Umbraco.Web.Website/Routing/PublicAccessRequestHandler.cs +++ b/src/Umbraco.Web.Website/Routing/PublicAccessRequestHandler.cs @@ -148,9 +148,10 @@ public class PublicAccessRequestHandler : IPublicAccessRequestHandler _logger.LogDebug("EnsurePublishedContentAccess: Page is not protected"); } } + } - // loop until we have access or reached max loops - } while (publicAccessStatus != PublicAccessStatus.AccessAccepted && i++ < maxLoop); + // loop until we have access or reached max loops + while (publicAccessStatus != PublicAccessStatus.AccessAccepted && i++ < maxLoop); if (i == maxLoop) { diff --git a/src/Umbraco.Web.Website/Routing/UmbracoRouteValuesFactory.cs b/src/Umbraco.Web.Website/Routing/UmbracoRouteValuesFactory.cs index c46bb890b1..714024a1bc 100644 --- a/src/Umbraco.Web.Website/Routing/UmbracoRouteValuesFactory.cs +++ b/src/Umbraco.Web.Website/Routing/UmbracoRouteValuesFactory.cs @@ -45,13 +45,8 @@ public class UmbracoRouteValuesFactory : IUmbracoRouteValuesFactory ControllerActionDescriptor? descriptor = _controllerActionSearcher.Find( new DefaultHttpContext(), // this actually makes no difference for this method DefaultControllerName, - UmbracoRouteValues.DefaultActionName); - - if (descriptor == null) - { - throw new InvalidOperationException( + UmbracoRouteValues.DefaultActionName) ?? throw new InvalidOperationException( $"No controller/action found by name {DefaultControllerName}.{UmbracoRouteValues.DefaultActionName}"); - } return descriptor; }); @@ -151,8 +146,8 @@ public class UmbracoRouteValuesFactory : IUmbracoRouteValuesFactory $"The call to {nameof(IPublishedRouter.UpdateRequestAsync)} cannot return null"); } - string? customActionName = GetTemplateName(request); - + string? customActionName = GetTemplateName(request); + def = new UmbracoRouteValues( request, def.ControllerActionDescriptor, @@ -167,7 +162,7 @@ public class UmbracoRouteValuesFactory : IUmbracoRouteValuesFactory return def; } - + private string? GetTemplateName(IPublishedRequest request) { // check that a template is defined), if it doesn't and there is a hijacked route it will just route diff --git a/src/Umbraco.Web.Website/Umbraco.Web.Website.csproj b/src/Umbraco.Web.Website/Umbraco.Web.Website.csproj index 8ee1e80bb2..0e8d475725 100644 --- a/src/Umbraco.Web.Website/Umbraco.Web.Website.csproj +++ b/src/Umbraco.Web.Website/Umbraco.Web.Website.csproj @@ -6,8 +6,11 @@ Umbraco.Cms.Web.Website - - false + + ASP0019,CS0618,SA1401,SA1649,IDE0270,IDE1006 diff --git a/src/Umbraco.Web.Website/ViewEngines/ProfilingViewEngine.cs b/src/Umbraco.Web.Website/ViewEngines/ProfilingViewEngine.cs index 5862f90d64..33ec4acb33 100644 --- a/src/Umbraco.Web.Website/ViewEngines/ProfilingViewEngine.cs +++ b/src/Umbraco.Web.Website/ViewEngines/ProfilingViewEngine.cs @@ -8,6 +8,8 @@ public class ProfilingViewEngine : IViewEngine { private readonly string _name; private readonly IProfiler _profiler; + + //TODO: can this be made private and with underscore? internal readonly IViewEngine Inner; public ProfilingViewEngine(IViewEngine inner, IProfiler profiler) diff --git a/tests/Umbraco.Tests.Benchmarks/EnumeratorBenchmarks.cs b/tests/Umbraco.Tests.Benchmarks/EnumeratorBenchmarks.cs index 05db4034dd..460e819f18 100644 --- a/tests/Umbraco.Tests.Benchmarks/EnumeratorBenchmarks.cs +++ b/tests/Umbraco.Tests.Benchmarks/EnumeratorBenchmarks.cs @@ -11,7 +11,7 @@ public class EnumeratorBenchmarks { foreach (var t in EnumerateOneWithArray(1)) { - ; + } } @@ -20,7 +20,7 @@ public class EnumeratorBenchmarks { foreach (var t in EnumerateOneWithYield(1)) { - ; + } } diff --git a/tests/Umbraco.Tests.Benchmarks/HashFromStreams.cs b/tests/Umbraco.Tests.Benchmarks/HashFromStreams.cs index cc8ad90877..7b291b88b8 100644 --- a/tests/Umbraco.Tests.Benchmarks/HashFromStreams.cs +++ b/tests/Umbraco.Tests.Benchmarks/HashFromStreams.cs @@ -18,7 +18,7 @@ public class HashFromStreams // the SHA1CryptoServiceProvider is faster, but that is not reflected in these benchmarks. /* - + BenchmarkDotNet=v0.13.0, OS=Windows 10.0.19042.1052 (20H2/October2020Update) Intel Core i7-7700HQ CPU 2.80GHz (Kaby Lake), 1 CPU, 8 logical and 4 physical cores .NET SDK=5.0.300-preview.21258.4 diff --git a/tests/Umbraco.Tests.Integration/Implementations/TestHelper.cs b/tests/Umbraco.Tests.Integration/Implementations/TestHelper.cs index 1a4ccacdd1..47d095c02a 100644 --- a/tests/Umbraco.Tests.Integration/Implementations/TestHelper.cs +++ b/tests/Umbraco.Tests.Integration/Implementations/TestHelper.cs @@ -174,16 +174,13 @@ public class TestHelper : TestHelperBase private static void AssertListsAreEqual(PropertyInfo property, IEnumerable expected, IEnumerable actual, Func sorter = null, int dateDeltaMilliseconds = 0) { - if (sorter == null) + // this is pretty hackerific but saves us some code to write + sorter ??= enumerable => { - // this is pretty hackerific but saves us some code to write - sorter = enumerable => - { - // semi-generic way of ensuring any collection of IEntity are sorted by Ids for comparison - var entities = enumerable.OfType().ToList(); - return entities.Count > 0 ? entities.OrderBy(x => x.Id) : entities; - }; - } + // semi-generic way of ensuring any collection of IEntity are sorted by Ids for comparison + var entities = enumerable.OfType().ToList(); + return entities.Count > 0 ? entities.OrderBy(x => x.Id) : entities; + }; var expectedListEx = sorter(expected).Cast().ToList(); var actualListEx = sorter(actual).Cast().ToList(); diff --git a/tests/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs b/tests/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs index cf321d9498..34445e2446 100644 --- a/tests/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs +++ b/tests/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs @@ -298,7 +298,7 @@ namespace Umbraco.Cms.Tests.Integration.TestServerTest protected virtual void CustomMvcSetup(IMvcBuilder mvcBuilder) { - + } /// diff --git a/tests/Umbraco.Tests.Integration/Testing/TestWebProfilerRepository.cs b/tests/Umbraco.Tests.Integration/Testing/TestWebProfilerRepository.cs index 3d5dbb2e2b..a3c572ad04 100644 --- a/tests/Umbraco.Tests.Integration/Testing/TestWebProfilerRepository.cs +++ b/tests/Umbraco.Tests.Integration/Testing/TestWebProfilerRepository.cs @@ -5,7 +5,7 @@ namespace Umbraco.Cms.Tests.Integration.Testing; public class TestWebProfilerRepository : IWebProfilerRepository { private bool _status = false; - + public void SetStatus(int userId, bool status) => _status = status; public bool GetStatus(int userId) => _status; diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/ContentTypeRepositoryTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/ContentTypeRepositoryTest.cs index 1644a3b1e4..51ade838c6 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/ContentTypeRepositoryTest.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/ContentTypeRepositoryTest.cs @@ -404,7 +404,7 @@ public class ContentTypeRepositoryTest : UmbracoIntegrationTest var repository = ContentTypeRepository; // Act - var contentTypes = repository.Get(provider.CreateQuery().Where(x => x.ParentId == contentType.Id)).ToArray();; + var contentTypes = repository.Get(provider.CreateQuery().Where(x => x.ParentId == contentType.Id)).ToArray(); // Assert Assert.That(contentTypes.Count(), Is.EqualTo(3)); diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/LanguageServiceTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/LanguageServiceTests.cs index 043ce3d2f4..b04dcdb379 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/LanguageServiceTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/LanguageServiceTests.cs @@ -1,4 +1,4 @@ -using NUnit.Framework; +using NUnit.Framework; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Services; @@ -262,7 +262,7 @@ public class LanguageServiceTests : UmbracoIntegrationTest Assert.IsFalse(result.Success); Assert.AreEqual(LanguageOperationStatus.InvalidFallbackIsoCode, result.Status); } - + [Test] public async Task Cannot_Create_Language_With_NonExisting_Fallback_Language() { @@ -332,7 +332,7 @@ public class LanguageServiceTests : UmbracoIntegrationTest Assert.IsFalse(result.Success); Assert.AreEqual(LanguageOperationStatus.InvalidFallbackIsoCode, result.Status); } - + [Test] public async Task Cannot_Create_Direct_Cyclic_Fallback_Language() { diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Components/ComponentTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Components/ComponentTests.cs index ca47bfbd97..dd4c46f0a4 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Components/ComponentTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Components/ComponentTests.cs @@ -157,7 +157,7 @@ public class ComponentTests { return Mock.Of>(); } - + if (type == typeof(ILogger)) { return Mock.Of>(); @@ -324,7 +324,7 @@ public class ComponentTests { return Mock.Of>(); } - + if (type == typeof(IServiceProviderIsService)) { return Mock.Of(); diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/BackgroundJobs/Jobs/ServerRegistration/InstructionProcessJobTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/BackgroundJobs/Jobs/ServerRegistration/InstructionProcessJobTests.cs index f2e6e574ef..14311afbe2 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/BackgroundJobs/Jobs/ServerRegistration/InstructionProcessJobTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/BackgroundJobs/Jobs/ServerRegistration/InstructionProcessJobTests.cs @@ -30,7 +30,7 @@ public class InstructionProcessJobTests private InstructionProcessJob CreateInstructionProcessJob() { - + var mockLogger = new Mock>(); _mockDatabaseServerMessenger = new Mock(); diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/BackgroundJobs/Jobs/TempFileCleanupJobTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/BackgroundJobs/Jobs/TempFileCleanupJobTests.cs index c37094e6ac..9c76c7b799 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/BackgroundJobs/Jobs/TempFileCleanupJobTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/BackgroundJobs/Jobs/TempFileCleanupJobTests.cs @@ -29,7 +29,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.BackgroundJobs.Jobs private TempFileCleanupJob CreateTempFileCleanupJob() { - + _mockIOHelper = new Mock(); _mockIOHelper.Setup(x => x.GetTempFolders()) .Returns(new DirectoryInfo[] { new(_testPath) }); From f32d40a35e709986ff02ce6ca61c46fac0bbfa52 Mon Sep 17 00:00:00 2001 From: Andreas Zerbst <73799582+andr317c@users.noreply.github.com> Date: Tue, 17 Sep 2024 12:13:10 +0200 Subject: [PATCH 14/41] V14 QA Content with vector graphics (#17065) * Added tests for Upload vector graphics * Run all content tests * Fixed indentation * Reverted smokeTest string --- .../ContentWithUploadVectorGraphics.spec.ts | 106 ++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithUploadVectorGraphics.spec.ts diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithUploadVectorGraphics.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithUploadVectorGraphics.spec.ts new file mode 100644 index 0000000000..16491d2502 --- /dev/null +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithUploadVectorGraphics.spec.ts @@ -0,0 +1,106 @@ +import {ConstantHelper, test, AliasHelper} from '@umbraco/playwright-testhelpers'; +import {expect} from "@playwright/test"; + +const contentName = 'TestContent'; +const documentTypeName = 'TestDocumentTypeForContent'; +const dataTypeName = 'Upload Vector Graphics'; +const uploadVectorGraphicsPath = './fixtures/mediaLibrary/'; + +test.beforeEach(async ({umbracoApi}) => { + await umbracoApi.documentType.ensureNameNotExists(documentTypeName); + await umbracoApi.document.ensureNameNotExists(contentName); +}); + +test.afterEach(async ({umbracoApi}) => { + await umbracoApi.document.ensureNameNotExists(contentName); + await umbracoApi.documentType.ensureNameNotExists(documentTypeName); +}); + +test('can create content with the upload vector graphics data type', async ({umbracoApi, umbracoUi}) => { + // Arrange + const expectedState = 'Draft'; + const dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); + await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, dataTypeName, dataTypeData.id); + await umbracoUi.goToBackOffice(); + 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.variants[0].state).toBe(expectedState); + expect(contentData.values).toEqual([]); +}); + +test('can publish content with the upload vector graphics data type', async ({umbracoApi, umbracoUi}) => { + // Arrange + const expectedState = 'Published'; + const dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); + const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, dataTypeName, dataTypeData.id); + await umbracoApi.document.createDefaultDocument(contentName, documentTypeId); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + + // Act + await umbracoUi.content.goToContentWithName(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.variants[0].state).toBe(expectedState); + expect(contentData.values).toEqual([]); +}); + +test(`can upload a file with the svg extension in the content`, async ({umbracoApi, umbracoUi}) => { + // Arrange + const vectorGraphicsName = 'VectorGraphics.svg'; + const dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); + const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, dataTypeName, dataTypeData.id); + await umbracoApi.document.createDefaultDocument(contentName, documentTypeId); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + + // Act + await umbracoUi.content.goToContentWithName(contentName); + await umbracoUi.content.uploadFile(uploadVectorGraphicsPath + vectorGraphicsName); + 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[0].alias).toEqual(AliasHelper.toAlias(dataTypeName)); + expect(contentData.values[0].value.src).toContain(AliasHelper.toAlias(vectorGraphicsName)); +}); + +// TODO: Remove skip when the front-end is ready. Currently the uploaded vector graphics file still displays after removing. +test.skip('can remove an svg file in the content', async ({umbracoApi, umbracoUi}) => { + // Arrange + const uploadVectorGraphicsName = 'VectorGraphics.svg'; + const mineType = 'image/svg+xml'; + const dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); + const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, dataTypeName, dataTypeData.id); + await umbracoApi.document.createDocumentWithUploadFile(contentName, documentTypeId, dataTypeName, uploadVectorGraphicsName, mineType); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + + // Act + await umbracoUi.content.goToContentWithName(contentName); + await umbracoUi.content.clickRemoveFilesButton(); + 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([]); +}); From 66aeafe7979ceccb1675edf517cdf85e3e6722e4 Mon Sep 17 00:00:00 2001 From: Andreas Zerbst <73799582+andr317c@users.noreply.github.com> Date: Tue, 17 Sep 2024 12:13:35 +0200 Subject: [PATCH 15/41] V14 QA Content with upload video (#17066) * Added tests for upload video in content * Added ogv file * Added ogv to test * Reverted smokeTest command --- .../fixtures/mediaLibrary/Ogv.ogv | Bin 0 -> 9479 bytes .../fixtures/mediaLibrary/Webm.webm | Bin 0 -> 6017 bytes .../Content/ContentWithUploadVideo.spec.ts | 112 ++++++++++++++++++ 3 files changed, 112 insertions(+) create mode 100644 tests/Umbraco.Tests.AcceptanceTest/fixtures/mediaLibrary/Ogv.ogv create mode 100644 tests/Umbraco.Tests.AcceptanceTest/fixtures/mediaLibrary/Webm.webm create mode 100644 tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithUploadVideo.spec.ts diff --git a/tests/Umbraco.Tests.AcceptanceTest/fixtures/mediaLibrary/Ogv.ogv b/tests/Umbraco.Tests.AcceptanceTest/fixtures/mediaLibrary/Ogv.ogv new file mode 100644 index 0000000000000000000000000000000000000000..e979ba49e91078c3a9aaa3a2d5d56594bfa74f06 GIT binary patch literal 9479 zcmeI2Pe>GT6vyA}Dk>C73VD#676T8h=F*`;Les*d2a667O*^yg;Lgr6GfsaFxrfNT zd6iixuS#@|Ej)SXARRgdN$5=o><%SW=pw?zajT`T?Lh;}`}&S77Oyhv%`DR;&~EuQv3o&kZPTKWeL> zLeJpf;21z0a4;T!_pNmO7J4;g-wzZ4PW?1WPhqN7TU(!l#HX)|TSs&J{_?6K52=7u zKq?>=kP1izqyka_sen{KDj*e*3P=T{0#X5~fK)&#AQg}bNCl2kfw$qG1?RDD8440G zU2NLC8$}}=WjHEvw7}7_QiZOyVl?~)L*-WGv*1-uaWt+hie5fPv&wVPtHRNW@C#uGOeSX9*34VXXuf(CgKWvuI$SfljMphW9iX;F= z5{!&6l3}F8$O0qFjQm}n#%q>s`?!-i%g^fAMl8M#aQr1GK*yf#J3%a*5AJy$)QjO? z{2x6>o4~=_-bA=&JmR<=4c8S-Sm&U1n~In=a50?NZRnnhy-p+NQsw~}M%a!U#)^+w zx~U;oEnU3#0mL8R#u80~WLD2PVGk?c)G?!B&u-$mJ*wsPoIyUZEz8Vju+#2aVbJJh zckA`BPTeuchm6LLO*%QX-jBU;y?Z(Azi>TwXG6$mhSDmqC5y%kK~oWLdlb7=$e2eR zN9vAR4bLuN)L{kFF=}gvNy|`nvNE8<^+J6qA}wZ81_)X^UN-4YMx06~vNQp-9ftnY zS6<`T<8Y*2A@4|Hx9teZNsKAWztI5qIc?XsEZkH2ss3wH_cUz3TSnN@@R{hlgYTQ8 z6$y=$_w}W7W+9!`tU_;^a*beSK5fxkJsou( zb_~rkRC+;eSAAf5S@ppAeMY`7bV?e!>Di8&R4GkKwevYMzdc{A51p$=Q*qUTW4NyE N?2){0IG*A3{s9L`yqN$1 literal 0 HcmV?d00001 diff --git a/tests/Umbraco.Tests.AcceptanceTest/fixtures/mediaLibrary/Webm.webm b/tests/Umbraco.Tests.AcceptanceTest/fixtures/mediaLibrary/Webm.webm new file mode 100644 index 0000000000000000000000000000000000000000..ac90535d4dc5ed90ab9f28442c8c3957a860bb9a GIT binary patch literal 6017 zcmeI0O=#3W6o6l{U2Kcmwf0b@Xu4ML;CAgIC{nbwMT+O@Nu+U;bwiWQlBDtHpnE8E zLBwlKQ0lQgc+$%vJxGs5i&v}ITR~7f2>!sX?`7L=c4k57MGtvoX5M`B=1uZu2EqdX zDd*fNYPM+rh(IltjO=EIjN<7b0BFxystVA3*id8#`(j4|Yw702wxgR1C&Ul65|rSX zAP6G>F~F+p*z5j7Q)ttce=}SH=zOe}XW@RWwz0VY$@gEEfA9AB_4#=vdZGwK5r`rX zMIeem6oDuLQ3Rq0>^1_g@F!L5IE8h`sF#Gf(*6VZzuG|KJj(H?%%dd+wU2tMLKV(l z+VPI$3rqZj@Uhv(&v0Z?SY~Woj?4>B8Jh}6)`feFO_e1c2-ll!yd+1)h4YL}jw5B^ z2xGIvk=57_*ywGF4@>++>~^z_pW(=)$oTPHj?9a!p9)9T_k4=YPKDmbud>90IAh}_ zIWpeWbgFRQa~vsiWQilI9Qnc$Kf#d2K zqY<5-P&EAi^?t=K7^dmso9c#}SFjC9Y!hJL0}z0ohBS2$GyRcU!^+hZ%&0ZwD)!9@xuEDO*@)i_t&l^d$29_{;nyBe>SKM1rIHN^ zjqjbYbg8b3FKy?Bj`|bZv9~vbY#vft4_lJ3&j7^fvnNocLc&?paj5RC)v$L4y^aZ( z4ycJMJ8(CETeGG7qvu1s}g8igl@Sih^BV+r`P=l6+`@^>eZ<-|+i*&GeWJRlA&D5e} zlEdSeqb4VGNFeVxMf+Sjoz=BsHZL2+!7OFD;n8B&p!+-*HXgH7*-<6Bnx-w?(ww~1 ya(-GZqywXrs%VaBNhygE { + await umbracoApi.documentType.ensureNameNotExists(documentTypeName); + await umbracoApi.document.ensureNameNotExists(contentName); +}); + +test.afterEach(async ({umbracoApi}) => { + await umbracoApi.document.ensureNameNotExists(contentName); + await umbracoApi.documentType.ensureNameNotExists(documentTypeName); +}); + +test('can create content with the upload video data type', async ({umbracoApi, umbracoUi}) => { + // Arrange + const expectedState = 'Draft'; + const dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); + await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, dataTypeName, dataTypeData.id); + await umbracoUi.goToBackOffice(); + 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.variants[0].state).toBe(expectedState); + expect(contentData.values).toEqual([]); +}); + +test('can publish content with the upload video data type', async ({umbracoApi, umbracoUi}) => { + // Arrange + const expectedState = 'Published'; + const dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); + const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, dataTypeName, dataTypeData.id); + await umbracoApi.document.createDefaultDocument(contentName, documentTypeId); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + + // Act + await umbracoUi.content.goToContentWithName(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.variants[0].state).toBe(expectedState); + expect(contentData.values).toEqual([]); +}); + +const uploadVideos = [ + {fileExtension: 'mp4', fileName: 'Video.mp4'}, + {fileExtension: 'webm', fileName: 'Webm.webm'}, + {fileExtension: 'ogv', fileName: 'Ogv.ogv'} +]; +for (const uploadVideo of uploadVideos) { + test(`can upload a video with the ${uploadVideo.fileExtension} extension in the content`, async ({umbracoApi, umbracoUi}) => { + // Arrange + const dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); + const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, dataTypeName, dataTypeData.id); + await umbracoApi.document.createDefaultDocument(contentName, documentTypeId); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + + // Act + await umbracoUi.content.goToContentWithName(contentName); + await umbracoUi.content.uploadFile(uploadVideoPath + uploadVideo.fileName); + 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[0].alias).toEqual(AliasHelper.toAlias(dataTypeName)); + expect(contentData.values[0].value.src).toContain(AliasHelper.toAlias(uploadVideo.fileName)); + }); +} + +// TODO: Remove skip when the front-end is ready. Currently the uploaded video still displays after removing. +test.skip('can remove a mp4 file in the content', async ({umbracoApi, umbracoUi}) => { + // Arrange + const uploadFileName = 'Video.mp4'; + const mineType = 'video/mp4'; + const dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); + const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, dataTypeName, dataTypeData.id); + await umbracoApi.document.createDocumentWithUploadFile(contentName, documentTypeId, dataTypeName, uploadFileName, mineType); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + + // Act + await umbracoUi.content.goToContentWithName(contentName); + await umbracoUi.content.clickRemoveFilesButton(); + 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([]); +}); From 920591bcd17f7858ef52f1aa9e0dd69bcb9c975e Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Tue, 17 Sep 2024 14:41:29 +0200 Subject: [PATCH 16/41] Updated the assembly names to avoid a debug-warning in the log (#16997) --- src/Umbraco.Core/Constants-Composing.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Constants-Composing.cs b/src/Umbraco.Core/Constants-Composing.cs index e55c32d01a..defdf2fa93 100644 --- a/src/Umbraco.Core/Constants-Composing.cs +++ b/src/Umbraco.Core/Constants-Composing.cs @@ -13,7 +13,7 @@ public static partial class Constants public static readonly string[] UmbracoCoreAssemblyNames = { "Umbraco.Core", "Umbraco.Infrastructure", "Umbraco.PublishedCache.NuCache", "Umbraco.Examine.Lucene", - "Umbraco.Web.Common", "Umbraco.Web.BackOffice", "Umbraco.Web.Website", + "Umbraco.Web.Common", "Umbraco.Cms.Api.Common","Umbraco.Cms.Api.Delivery","Umbraco.Cms.Api.Management", "Umbraco.Web.Website", }; } } From 991b9a791ee26ff37805e0d842fb1ddd8e23b6b0 Mon Sep 17 00:00:00 2001 From: Mole Date: Tue, 17 Sep 2024 14:42:25 +0200 Subject: [PATCH 17/41] Updaet template to reference 13.5 (#17063) --- templates/UmbracoProject/.template.config/template.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/UmbracoProject/.template.config/template.json b/templates/UmbracoProject/.template.config/template.json index e342cdaeb8..355ed6f7a8 100644 --- a/templates/UmbracoProject/.template.config/template.json +++ b/templates/UmbracoProject/.template.config/template.json @@ -98,7 +98,7 @@ }, { "condition": "(UmbracoRelease == 'LTS')", - "value": "13.4.1" + "value": "13.5.0" } ] } From f72c39ef591d2fb578945bf984e016a20f05e55f Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 17 Sep 2024 21:43:05 +0900 Subject: [PATCH 18/41] V14: Fix templates not having set master template on package install (#16978) * Reorder templates to save master templates first, and use new ITemplateService * Add obsoletion * Fix if statement * Refactor async calls into async method to avoid multiple get awaiters * Update interface * Avoid breaking changes --------- Co-authored-by: Sven Geusens Co-authored-by: Bjarke Berg --- Directory.Build.props | 2 +- .../Media/EmbedProviders/OEmbedResponse.cs | 13 ++- .../EmbedProviders/OEmbedResponseBase.cs | 2 +- src/Umbraco.Core/Services/FileService.cs | 2 - src/Umbraco.Core/Services/IDataTypeService.cs | 4 +- .../Services/IPackageDataInstallation.cs | 12 +++ .../ContentTypeOperationStatus.cs | 6 +- .../UmbracoBuilder.Services.cs | 4 +- .../Packaging/PackageDataInstallation.cs | 91 ++++++++++++++++--- 9 files changed, 110 insertions(+), 26 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index 5f3055125f..52635c8fc2 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -28,7 +28,7 @@ - true + false true 14.0.0 true diff --git a/src/Umbraco.Core/Media/EmbedProviders/OEmbedResponse.cs b/src/Umbraco.Core/Media/EmbedProviders/OEmbedResponse.cs index 61c6b2d13f..9f0a224e76 100644 --- a/src/Umbraco.Core/Media/EmbedProviders/OEmbedResponse.cs +++ b/src/Umbraco.Core/Media/EmbedProviders/OEmbedResponse.cs @@ -6,5 +6,16 @@ namespace Umbraco.Cms.Core.Media.EmbedProviders; /// Wrapper class for OEmbed response. /// [DataContract] -public class OEmbedResponse : OEmbedResponseBase; +public class OEmbedResponse : OEmbedResponseBase +{ + + // these is only here to avoid breaking changes. In theory it should still be source code compatible to remove them. + public new double? ThumbnailHeight => base.ThumbnailHeight; + + public new double? ThumbnailWidth => base.ThumbnailWidth; + + public new double? Height => base.Height; + + public new double? Width => base.Width; +} diff --git a/src/Umbraco.Core/Media/EmbedProviders/OEmbedResponseBase.cs b/src/Umbraco.Core/Media/EmbedProviders/OEmbedResponseBase.cs index 57e4c878c7..c71ecb8391 100644 --- a/src/Umbraco.Core/Media/EmbedProviders/OEmbedResponseBase.cs +++ b/src/Umbraco.Core/Media/EmbedProviders/OEmbedResponseBase.cs @@ -34,7 +34,7 @@ public abstract class OEmbedResponseBase public string? ThumbnailUrl { get; set; } [DataMember(Name = "thumbnail_height")] - public T? ThumbnailHeight { get; set; } + public virtual T? ThumbnailHeight { get; set; } [DataMember(Name = "thumbnail_width")] public T? ThumbnailWidth { get; set; } diff --git a/src/Umbraco.Core/Services/FileService.cs b/src/Umbraco.Core/Services/FileService.cs index 8ef37b76bf..b9c435ebab 100644 --- a/src/Umbraco.Core/Services/FileService.cs +++ b/src/Umbraco.Core/Services/FileService.cs @@ -523,8 +523,6 @@ public class FileService : RepositoryService, IFileService /// /// List of to save /// Optional id of the user - // FIXME: we need to re-implement PackageDataInstallation.ImportTemplates so it imports templates in the correct order - // instead of relying on being able to save invalid templates (child templates whose master has yet to be created) [Obsolete("Please use ITemplateService for template operations - will be removed in Umbraco 15")] public void SaveTemplate(IEnumerable templates, int userId = Constants.Security.SuperUserId) { diff --git a/src/Umbraco.Core/Services/IDataTypeService.cs b/src/Umbraco.Core/Services/IDataTypeService.cs index 8536c8a528..e2f808cc7e 100644 --- a/src/Umbraco.Core/Services/IDataTypeService.cs +++ b/src/Umbraco.Core/Services/IDataTypeService.cs @@ -196,7 +196,7 @@ public interface IDataTypeService : IService /// /// Alias of the property editor /// Collection of configured for the property editor - Task> GetByEditorAliasAsync(string propertyEditorAlias); + Task> GetByEditorAliasAsync(string propertyEditorAlias) => Task.FromResult(GetByEditorAlias(propertyEditorAlias)); /// /// Gets all for a given editor UI alias @@ -246,5 +246,5 @@ public interface IDataTypeService : IService /// /// Aliases of the property editors /// Collection of configured for the property editors - Task> GetByEditorAliasAsync(string[] propertyEditorAlias); + Task> GetByEditorAliasAsync(string[] propertyEditorAlias) => Task.FromResult(propertyEditorAlias.SelectMany(x=>GetByEditorAlias(x))); } diff --git a/src/Umbraco.Core/Services/IPackageDataInstallation.cs b/src/Umbraco.Core/Services/IPackageDataInstallation.cs index 27eee95f32..f2dd94a78e 100644 --- a/src/Umbraco.Core/Services/IPackageDataInstallation.cs +++ b/src/Umbraco.Core/Services/IPackageDataInstallation.cs @@ -64,16 +64,28 @@ public interface IPackageDataInstallation /// An enumerable list of generated languages IReadOnlyList ImportLanguages(IEnumerable languageElements, int userId); + [Obsolete("Use Async version instead, Scheduled to be removed in v17")] IEnumerable ImportTemplate(XElement templateElement, int userId); + Task> ImportTemplateAsync(XElement templateElement, int userId) => Task.FromResult(ImportTemplate(templateElement, userId)); + /// /// Imports and saves package xml as /// /// Xml to import /// Optional user id /// An enumerable list of generated Templates + [Obsolete("Use Async version instead, Scheduled to be removed in v17")] IReadOnlyList ImportTemplates(IReadOnlyCollection templateElements, int userId); + /// + /// Imports and saves package xml as + /// + /// Xml to import + /// Optional user id + /// An enumerable list of generated Templates + Task> ImportTemplatesAsync(IReadOnlyCollection templateElements, int userId) => Task.FromResult(ImportTemplates(templateElements, userId)); + Guid GetContentTypeKey(XElement contentType); string? GetEntityTypeAlias(XElement entityType); diff --git a/src/Umbraco.Core/Services/OperationStatus/ContentTypeOperationStatus.cs b/src/Umbraco.Core/Services/OperationStatus/ContentTypeOperationStatus.cs index 32f28eaf16..2379f51c02 100644 --- a/src/Umbraco.Core/Services/OperationStatus/ContentTypeOperationStatus.cs +++ b/src/Umbraco.Core/Services/OperationStatus/ContentTypeOperationStatus.cs @@ -5,10 +5,7 @@ public enum ContentTypeOperationStatus Success, DuplicateAlias, InvalidAlias, - NameCannotBeEmpty, - NameTooLong, InvalidPropertyTypeAlias, - PropertyTypeAliasCannotEqualContentTypeAlias, DuplicatePropertyTypeAlias, DataTypeNotFound, InvalidInheritance, @@ -21,6 +18,9 @@ public enum ContentTypeOperationStatus NotFound, NotAllowed, CancelledByNotification, + PropertyTypeAliasCannotEqualContentTypeAlias, + NameCannotBeEmpty, + NameTooLong, InvalidElementFlagDocumentHasContent, InvalidElementFlagElementIsUsedInPropertyEditorConfiguration, InvalidElementFlagComparedToParent, diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Services.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Services.cs index b7bc79997f..5bd401be67 100644 --- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Services.cs +++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Services.cs @@ -113,7 +113,9 @@ public static partial class UmbracoBuilderExtensions factory.GetRequiredService(), factory.GetRequiredService(), factory.GetRequiredService(), - factory.GetRequiredService()); + factory.GetRequiredService(), + factory.GetRequiredService(), + factory.GetRequiredService()); private static LocalizedTextServiceFileSources CreateLocalizedTextServiceFileSourcesFactory( IServiceProvider container) diff --git a/src/Umbraco.Infrastructure/Packaging/PackageDataInstallation.cs b/src/Umbraco.Infrastructure/Packaging/PackageDataInstallation.cs index a6c3ea453e..4b0ab56f67 100644 --- a/src/Umbraco.Infrastructure/Packaging/PackageDataInstallation.cs +++ b/src/Umbraco.Infrastructure/Packaging/PackageDataInstallation.cs @@ -2,11 +2,13 @@ using System.Globalization; using System.Net; using System.Xml.Linq; using System.Xml.XPath; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Collections; using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Hosting; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Entities; @@ -34,6 +36,8 @@ namespace Umbraco.Cms.Infrastructure.Packaging private readonly IConfigurationEditorJsonSerializer _serializer; private readonly IMediaService _mediaService; private readonly IMediaTypeService _mediaTypeService; + private readonly ITemplateContentParserService _templateContentParserService; + private readonly ITemplateService _templateService; private readonly IEntityService _entityService; private readonly IContentTypeService _contentTypeService; private readonly IContentService _contentService; @@ -52,7 +56,9 @@ namespace Umbraco.Cms.Infrastructure.Packaging IShortStringHelper shortStringHelper, IConfigurationEditorJsonSerializer serializer, IMediaService mediaService, - IMediaTypeService mediaTypeService) + IMediaTypeService mediaTypeService, + ITemplateContentParserService templateContentParserService, + ITemplateService templateService) { _dataValueEditorFactory = dataValueEditorFactory; _logger = logger; @@ -68,6 +74,44 @@ namespace Umbraco.Cms.Infrastructure.Packaging _serializer = serializer; _mediaService = mediaService; _mediaTypeService = mediaTypeService; + _templateContentParserService = templateContentParserService; + _templateService = templateService; + } + + [Obsolete("Please use new constructor, scheduled for removal in v15")] + public PackageDataInstallation( + IDataValueEditorFactory dataValueEditorFactory, + ILogger logger, + IFileService fileService, + ILocalizationService localizationService, + IDataTypeService dataTypeService, + IEntityService entityService, + IContentTypeService contentTypeService, + IContentService contentService, + PropertyEditorCollection propertyEditors, + IScopeProvider scopeProvider, + IShortStringHelper shortStringHelper, + IConfigurationEditorJsonSerializer serializer, + IMediaService mediaService, + IMediaTypeService mediaTypeService) + : this( + dataValueEditorFactory, + logger, + fileService, + localizationService, + dataTypeService, + entityService, + contentTypeService, + contentService, + propertyEditors, + scopeProvider, + shortStringHelper, + serializer, + mediaService, + mediaTypeService, + StaticServiceProvider.Instance.GetRequiredService(), + StaticServiceProvider.Instance.GetRequiredService()) + { } // Also remove factory service registration when this constructor is removed @@ -103,7 +147,9 @@ namespace Umbraco.Cms.Infrastructure.Packaging shortStringHelper, serializer, mediaService, - mediaTypeService) + mediaTypeService, + StaticServiceProvider.Instance.GetRequiredService(), + StaticServiceProvider.Instance.GetRequiredService()) { } #region Install/Uninstall @@ -1651,16 +1697,25 @@ namespace Umbraco.Cms.Infrastructure.Packaging #region Templates + [Obsolete("Use Async version instead, Scheduled to be removed in v17")] public IEnumerable ImportTemplate(XElement templateElement, int userId) => ImportTemplates(new[] {templateElement}, userId); + public async Task> ImportTemplateAsync(XElement templateElement, int userId) + => ImportTemplatesAsync(new[] {templateElement}, userId).GetAwaiter().GetResult(); + + + [Obsolete("Use Async version instead, Scheduled to be removed in v17")] + public IReadOnlyList ImportTemplates(IReadOnlyCollection templateElements, int userId) + => ImportTemplatesAsync(templateElements, userId).GetAwaiter().GetResult(); + /// /// Imports and saves package xml as /// /// Xml to import /// Optional user id /// An enumerable list of generated Templates - public IReadOnlyList ImportTemplates(IReadOnlyCollection templateElements, int userId) + public async Task> ImportTemplatesAsync(IReadOnlyCollection templateElements, int userId) { var templates = new List(); @@ -1670,20 +1725,19 @@ namespace Umbraco.Cms.Infrastructure.Packaging { var dependencies = new List(); XElement elementCopy = tempElement; - //Ensure that the Master of the current template is part of the import, otherwise we ignore this dependency as part of the dependency sorting. - if (string.IsNullOrEmpty((string?)elementCopy.Element("Master")) == false && - templateElements.Any(x => (string?)x.Element("Alias") == (string?)elementCopy.Element("Master"))) + + //Ensure that the Master of the current template is part of the import, otherwise we ignore this dependency as part of the dependency sorting.' + var masterTemplate = _templateContentParserService.MasterTemplateAlias(tempElement.Value); + if (masterTemplate is not null && templateElements.Any(x => (string?)x.Element("Alias") == masterTemplate)) { - dependencies.Add((string)elementCopy.Element("Master")!); + dependencies.Add(masterTemplate); } - else if (string.IsNullOrEmpty((string?)elementCopy.Element("Master")) == false && - templateElements.Any(x => - (string?)x.Element("Alias") == (string?)elementCopy.Element("Master")) == false) + else { _logger.LogInformation( "Template '{TemplateAlias}' has an invalid Master '{TemplateMaster}', so the reference has been ignored.", (string?)elementCopy.Element("Alias"), - (string?)elementCopy.Element("Master")); + masterTemplate); } graph.AddItem(TopoGraph.CreateNode((string)elementCopy.Element("Alias")!, elementCopy, dependencies)); @@ -1700,9 +1754,9 @@ namespace Umbraco.Cms.Infrastructure.Packaging var design = templateElement.Element("Design")?.Value; XElement? masterElement = templateElement.Element("Master"); - var existingTemplate = _fileService.GetTemplate(alias) as Template; + var existingTemplate = await _templateService.GetAsync(alias) as Template; - Template? template = existingTemplate ?? new Template(_shortStringHelper, templateName, alias); + Template template = existingTemplate ?? new Template(_shortStringHelper, templateName, alias); // For new templates, use the serialized key if avaialble. if (existingTemplate == null && Guid.TryParse(templateElement.Element("Key")?.Value, out Guid key)) @@ -1725,9 +1779,16 @@ namespace Umbraco.Cms.Infrastructure.Packaging templates.Add(template); } - if (templates.Any()) + foreach (ITemplate template in templates) { - _fileService.SaveTemplate(templates, userId); + if (template.Id > 0) + { + await _templateService.UpdateAsync(template, Constants.Security.SuperUserKey); + } + else + { + await _templateService.CreateAsync(template, Constants.Security.SuperUserKey); + } } return templates; From 939b2aaa3d17a4741256421ca763a701d0cfa10e Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Wed, 18 Sep 2024 09:45:21 +0200 Subject: [PATCH 19/41] update backoffice submodule to 14.3 release branch --- 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 c1c99408ad..662e2268cb 160000 --- a/src/Umbraco.Web.UI.Client +++ b/src/Umbraco.Web.UI.Client @@ -1 +1 @@ -Subproject commit c1c99408ad5910e559f4f12318cfd34cb8a8ea76 +Subproject commit 662e2268cbdbf7562fe9bcd4fc9222bb24e26563 From c8899afed69a97062c8c550994df1cebedfc0b96 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 17 Sep 2024 19:02:06 +0000 Subject: [PATCH 20/41] Bump vite from 5.3.1 to 5.4.6 in /src/Umbraco.Web.UI.Login Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 5.3.1 to 5.4.6. - [Release notes](https://github.com/vitejs/vite/releases) - [Changelog](https://github.com/vitejs/vite/blob/v5.4.6/packages/vite/CHANGELOG.md) - [Commits](https://github.com/vitejs/vite/commits/v5.4.6/packages/vite) --- updated-dependencies: - dependency-name: vite dependency-type: direct:development ... Signed-off-by: dependabot[bot] --- src/Umbraco.Web.UI.Login/package-lock.json | 172 +++++++++++---------- src/Umbraco.Web.UI.Login/package.json | 2 +- 2 files changed, 89 insertions(+), 85 deletions(-) diff --git a/src/Umbraco.Web.UI.Login/package-lock.json b/src/Umbraco.Web.UI.Login/package-lock.json index 43eaac8a2b..66bfe7a528 100644 --- a/src/Umbraco.Web.UI.Login/package-lock.json +++ b/src/Umbraco.Web.UI.Login/package-lock.json @@ -10,7 +10,7 @@ "@umbraco-ui/uui-css": "^1.8.0", "msw": "^2.3.0", "typescript": "^5.4.5", - "vite": "^5.2.11", + "vite": "^5.4.6", "vite-tsconfig-paths": "^4.3.2" }, "engines": { @@ -525,9 +525,9 @@ "dev": true }, "node_modules/@rollup/rollup-android-arm-eabi": { - "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==", + "version": "4.21.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.21.3.tgz", + "integrity": "sha512-MmKSfaB9GX+zXl6E8z4koOr/xU63AMVleLEa64v7R0QF/ZloMs5vcD1sHgM64GXXS1csaJutG+ddtzcueI/BLg==", "cpu": [ "arm" ], @@ -538,9 +538,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "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==", + "version": "4.21.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.21.3.tgz", + "integrity": "sha512-zrt8ecH07PE3sB4jPOggweBjJMzI1JG5xI2DIsUbkA+7K+Gkjys6eV7i9pOenNSDJH3eOr/jLb/PzqtmdwDq5g==", "cpu": [ "arm64" ], @@ -551,9 +551,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "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==", + "version": "4.21.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.21.3.tgz", + "integrity": "sha512-P0UxIOrKNBFTQaXTxOH4RxuEBVCgEA5UTNV6Yz7z9QHnUJ7eLX9reOd/NYMO3+XZO2cco19mXTxDMXxit4R/eQ==", "cpu": [ "arm64" ], @@ -564,9 +564,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.18.0.tgz", - "integrity": "sha512-n2LMsUz7Ynu7DoQrSQkBf8iNrjOGyPLrdSg802vk6XT3FtsgX6JbE8IHRvposskFm9SNxzkLYGSq9QdpLYpRNA==", + "version": "4.21.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.21.3.tgz", + "integrity": "sha512-L1M0vKGO5ASKntqtsFEjTq/fD91vAqnzeaF6sfNAy55aD+Hi2pBI5DKwCO+UNDQHWsDViJLqshxOahXyLSh3EA==", "cpu": [ "x64" ], @@ -577,9 +577,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "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==", + "version": "4.21.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.21.3.tgz", + "integrity": "sha512-btVgIsCjuYFKUjopPoWiDqmoUXQDiW2A4C3Mtmp5vACm7/GnyuprqIDPNczeyR5W8rTXEbkmrJux7cJmD99D2g==", "cpu": [ "arm" ], @@ -590,9 +590,9 @@ ] }, "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==", + "version": "4.21.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.21.3.tgz", + "integrity": "sha512-zmjbSphplZlau6ZTkxd3+NMtE4UKVy7U4aVFMmHcgO5CUbw17ZP6QCgyxhzGaU/wFFdTfiojjbLG3/0p9HhAqA==", "cpu": [ "arm" ], @@ -603,9 +603,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "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==", + "version": "4.21.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.21.3.tgz", + "integrity": "sha512-nSZfcZtAnQPRZmUkUQwZq2OjQciR6tEoJaZVFvLHsj0MF6QhNMg0fQ6mUOsiCUpTqxTx0/O6gX0V/nYc7LrgPw==", "cpu": [ "arm64" ], @@ -616,9 +616,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "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==", + "version": "4.21.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.21.3.tgz", + "integrity": "sha512-MnvSPGO8KJXIMGlQDYfvYS3IosFN2rKsvxRpPO2l2cum+Z3exiExLwVU+GExL96pn8IP+GdH8Tz70EpBhO0sIQ==", "cpu": [ "arm64" ], @@ -629,9 +629,9 @@ ] }, "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==", + "version": "4.21.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.21.3.tgz", + "integrity": "sha512-+W+p/9QNDr2vE2AXU0qIy0qQE75E8RTwTwgqS2G5CRQ11vzq0tbnfBd6brWhS9bCRjAjepJe2fvvkvS3dno+iw==", "cpu": [ "ppc64" ], @@ -642,9 +642,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "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==", + "version": "4.21.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.21.3.tgz", + "integrity": "sha512-yXH6K6KfqGXaxHrtr+Uoy+JpNlUlI46BKVyonGiaD74ravdnF9BUNC+vV+SIuB96hUMGShhKV693rF9QDfO6nQ==", "cpu": [ "riscv64" ], @@ -655,9 +655,9 @@ ] }, "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==", + "version": "4.21.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.21.3.tgz", + "integrity": "sha512-R8cwY9wcnApN/KDYWTH4gV/ypvy9yZUHlbJvfaiXSB48JO3KpwSpjOGqO4jnGkLDSk1hgjYkTbTt6Q7uvPf8eg==", "cpu": [ "s390x" ], @@ -668,9 +668,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "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==", + "version": "4.21.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.21.3.tgz", + "integrity": "sha512-kZPbX/NOPh0vhS5sI+dR8L1bU2cSO9FgxwM8r7wHzGydzfSjLRCFAT87GR5U9scj2rhzN3JPYVC7NoBbl4FZ0g==", "cpu": [ "x64" ], @@ -681,9 +681,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "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==", + "version": "4.21.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.21.3.tgz", + "integrity": "sha512-S0Yq+xA1VEH66uiMNhijsWAafffydd2X5b77eLHfRmfLsRSpbiAWiRHV6DEpz6aOToPsgid7TI9rGd6zB1rhbg==", "cpu": [ "x64" ], @@ -694,9 +694,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "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==", + "version": "4.21.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.21.3.tgz", + "integrity": "sha512-9isNzeL34yquCPyerog+IMCNxKR8XYmGd0tHSV+OVx0TmE0aJOo9uw4fZfUuk2qxobP5sug6vNdZR6u7Mw7Q+Q==", "cpu": [ "arm64" ], @@ -707,9 +707,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "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==", + "version": "4.21.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.21.3.tgz", + "integrity": "sha512-nMIdKnfZfzn1Vsk+RuOvl43ONTZXoAPUUxgcU0tXooqg4YrAqzfKzVenqqk2g5efWh46/D28cKFrOzDSW28gTA==", "cpu": [ "ia32" ], @@ -720,9 +720,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "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==", + "version": "4.21.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.21.3.tgz", + "integrity": "sha512-fOvu7PCQjAj4eWDEuD8Xz5gpzFqXzGlxHZozHP4b9Jxv9APtdxL6STqztDzMLuRXEc4UpXGGhx029Xgm91QBeA==", "cpu": [ "x64" ], @@ -2288,15 +2288,15 @@ "dev": true }, "node_modules/picocolors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", - "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", + "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==", "dev": true }, "node_modules/postcss": { - "version": "8.4.38", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", - "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", + "version": "8.4.47", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz", + "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==", "dev": true, "funding": [ { @@ -2314,8 +2314,8 @@ ], "dependencies": { "nanoid": "^3.3.7", - "picocolors": "^1.0.0", - "source-map-js": "^1.2.0" + "picocolors": "^1.1.0", + "source-map-js": "^1.2.1" }, "engines": { "node": "^10 || ^12 || >=14" @@ -2331,9 +2331,9 @@ } }, "node_modules/rollup": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.18.0.tgz", - "integrity": "sha512-QmJz14PX3rzbJCN1SG4Xe/bAAX2a6NpCP8ab2vfu2GiUr8AQcr2nCV/oEO3yneFarB67zk8ShlIyWb2LGTb3Sg==", + "version": "4.21.3", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.21.3.tgz", + "integrity": "sha512-7sqRtBNnEbcBtMeRVc6VRsJMmpI+JU1z9VTvW8D4gXIYQFz0aLcsE6rRkyghZkLfEgUZgVvOG7A5CVz/VW5GIA==", "dev": true, "dependencies": { "@types/estree": "1.0.5" @@ -2346,22 +2346,22 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@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", + "@rollup/rollup-android-arm-eabi": "4.21.3", + "@rollup/rollup-android-arm64": "4.21.3", + "@rollup/rollup-darwin-arm64": "4.21.3", + "@rollup/rollup-darwin-x64": "4.21.3", + "@rollup/rollup-linux-arm-gnueabihf": "4.21.3", + "@rollup/rollup-linux-arm-musleabihf": "4.21.3", + "@rollup/rollup-linux-arm64-gnu": "4.21.3", + "@rollup/rollup-linux-arm64-musl": "4.21.3", + "@rollup/rollup-linux-powerpc64le-gnu": "4.21.3", + "@rollup/rollup-linux-riscv64-gnu": "4.21.3", + "@rollup/rollup-linux-s390x-gnu": "4.21.3", + "@rollup/rollup-linux-x64-gnu": "4.21.3", + "@rollup/rollup-linux-x64-musl": "4.21.3", + "@rollup/rollup-win32-arm64-msvc": "4.21.3", + "@rollup/rollup-win32-ia32-msvc": "4.21.3", + "@rollup/rollup-win32-x64-msvc": "4.21.3", "fsevents": "~2.3.2" } }, @@ -2388,9 +2388,9 @@ } }, "node_modules/source-map-js": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", - "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "dev": true, "engines": { "node": ">=0.10.0" @@ -2536,14 +2536,14 @@ } }, "node_modules/vite": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.3.1.tgz", - "integrity": "sha512-XBmSKRLXLxiaPYamLv3/hnP/KXDai1NDexN0FpkTaZXTfycHvkRHoenpgl/fvuK/kPbB6xAgoyiryAhQNxYmAQ==", + "version": "5.4.6", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.6.tgz", + "integrity": "sha512-IeL5f8OO5nylsgzd9tq4qD2QqI0k2CQLGrWD0rCN0EQJZpBK5vJAx0I+GDkMOXxQX/OfFHMuLIx6ddAxGX/k+Q==", "dev": true, "dependencies": { "esbuild": "^0.21.3", - "postcss": "^8.4.38", - "rollup": "^4.13.0" + "postcss": "^8.4.43", + "rollup": "^4.20.0" }, "bin": { "vite": "bin/vite.js" @@ -2562,6 +2562,7 @@ "less": "*", "lightningcss": "^1.21.0", "sass": "*", + "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.4.0" @@ -2579,6 +2580,9 @@ "sass": { "optional": true }, + "sass-embedded": { + "optional": true + }, "stylus": { "optional": true }, diff --git a/src/Umbraco.Web.UI.Login/package.json b/src/Umbraco.Web.UI.Login/package.json index 7b0e3455c4..38cc5bca58 100644 --- a/src/Umbraco.Web.UI.Login/package.json +++ b/src/Umbraco.Web.UI.Login/package.json @@ -19,7 +19,7 @@ "@umbraco-ui/uui-css": "^1.8.0", "msw": "^2.3.0", "typescript": "^5.4.5", - "vite": "^5.2.11", + "vite": "^5.4.6", "vite-tsconfig-paths": "^4.3.2" }, "msw": { From f8e26f5ba12b32723c19cfedf91b3faa19c4acf9 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Wed, 18 Sep 2024 12:56:19 +0200 Subject: [PATCH 21/41] Bump version --- version.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.json b/version.json index 2fb06038d0..3b2f02bc66 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.3.0-rc", + "version": "14.3.0", "assemblyVersion": { "precision": "build" }, From 5c857aa97d1efb70c960615d475ec3813b6ac3aa Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Wed, 18 Sep 2024 12:57:05 +0200 Subject: [PATCH 22/41] Bump version --- version.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.json b/version.json index 3b2f02bc66..a9105a4057 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.3.0", + "version": "14.4.0-rc", "assemblyVersion": { "precision": "build" }, From c5243e562e6cc869459b12a993cdec57f1fc762b Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Wed, 18 Sep 2024 13:10:15 +0200 Subject: [PATCH 23/41] Allow the client to send all content, with all languages, even when the user do not have permissions to save a specific language. (#17052) --- .../Document/CreateDocumentControllerBase.cs | 25 ++--- .../Document/UpdateDocumentControllerBase.cs | 25 ++--- .../Services/ContentEditingService.cs | 97 ++++++++++++++++++- 3 files changed, 123 insertions(+), 24 deletions(-) diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Document/CreateDocumentControllerBase.cs b/src/Umbraco.Cms.Api.Management/Controllers/Document/CreateDocumentControllerBase.cs index d6983964c5..669c2cdc93 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Document/CreateDocumentControllerBase.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Document/CreateDocumentControllerBase.cs @@ -18,18 +18,21 @@ public abstract class CreateDocumentControllerBase : DocumentControllerBase protected async Task HandleRequest(CreateDocumentRequestModel requestModel, Func> authorizedHandler) { - IEnumerable cultures = requestModel.Variants - .Where(v => v.Culture is not null) - .Select(v => v.Culture!); - AuthorizationResult authorizationResult = await _authorizationService.AuthorizeResourceAsync( - User, - ContentPermissionResource.WithKeys(ActionNew.ActionLetter, requestModel.Parent?.Id, cultures), - AuthorizationPolicies.ContentPermissionByResource); + // TODO This have temporarily been uncommented, to support the client sends values from all cultures, even when the user do not have access to the languages. + // The values are ignored in the ContentEditingService - if (!authorizationResult.Succeeded) - { - return Forbidden(); - } + // IEnumerable cultures = requestModel.Variants + // .Where(v => v.Culture is not null) + // .Select(v => v.Culture!); + // AuthorizationResult authorizationResult = await _authorizationService.AuthorizeResourceAsync( + // User, + // ContentPermissionResource.WithKeys(ActionNew.ActionLetter, requestModel.Parent?.Id, cultures), + // AuthorizationPolicies.ContentPermissionByResource); + // + // if (!authorizationResult.Succeeded) + // { + // return Forbidden(); + // } return await authorizedHandler(); } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Document/UpdateDocumentControllerBase.cs b/src/Umbraco.Cms.Api.Management/Controllers/Document/UpdateDocumentControllerBase.cs index 2d41fe94fe..4b585e78b9 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Document/UpdateDocumentControllerBase.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Document/UpdateDocumentControllerBase.cs @@ -17,18 +17,21 @@ public abstract class UpdateDocumentControllerBase : DocumentControllerBase protected async Task HandleRequest(Guid id, UpdateDocumentRequestModel requestModel, Func> authorizedHandler) { - IEnumerable cultures = requestModel.Variants - .Where(v => v.Culture is not null) - .Select(v => v.Culture!); - AuthorizationResult authorizationResult = await _authorizationService.AuthorizeResourceAsync( - User, - ContentPermissionResource.WithKeys(ActionUpdate.ActionLetter, id, cultures), - AuthorizationPolicies.ContentPermissionByResource); + // TODO This have temporarily been uncommented, to support the client sends values from all cultures, even when the user do not have access to the languages. + // The values are ignored in the ContentEditingService - if (!authorizationResult.Succeeded) - { - return Forbidden(); - } + // IEnumerable cultures = requestModel.Variants + // .Where(v => v.Culture is not null) + // .Select(v => v.Culture!); + // AuthorizationResult authorizationResult = await _authorizationService.AuthorizeResourceAsync( + // User, + // ContentPermissionResource.WithKeys(ActionUpdate.ActionLetter, id, cultures), + // AuthorizationPolicies.ContentPermissionByResource); + // + // if (!authorizationResult.Succeeded) + // { + // return Forbidden(); + // } return await authorizedHandler(); } diff --git a/src/Umbraco.Core/Services/ContentEditingService.cs b/src/Umbraco.Core/Services/ContentEditingService.cs index 2ad8365bcf..bc15e7ea44 100644 --- a/src/Umbraco.Core/Services/ContentEditingService.cs +++ b/src/Umbraco.Core/Services/ContentEditingService.cs @@ -1,9 +1,13 @@ -using Microsoft.Extensions.Logging; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.ContentEditing; +using Umbraco.Cms.Core.Models.Membership; using Umbraco.Cms.Core.PropertyEditors; using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Core.Services.OperationStatus; +using Umbraco.Extensions; namespace Umbraco.Cms.Core.Services; @@ -12,7 +16,11 @@ internal sealed class ContentEditingService { private readonly ITemplateService _templateService; private readonly ILogger _logger; + private readonly IUserService _userService; + private readonly ILocalizationService _localizationService; + private readonly ILanguageService _languageService; + [Obsolete("Use non-obsolete constructor. This will be removed in Umbraco 16.")] public ContentEditingService( IContentService contentService, IContentTypeService contentTypeService, @@ -24,10 +32,46 @@ internal sealed class ContentEditingService IUserIdKeyResolver userIdKeyResolver, ITreeEntitySortingService treeEntitySortingService, IContentValidationService contentValidationService) + : this( + contentService, + contentTypeService, + propertyEditorCollection, + dataTypeService, + templateService, + logger, + scopeProvider, + userIdKeyResolver, + treeEntitySortingService, + contentValidationService, + StaticServiceProvider.Instance.GetRequiredService(), + StaticServiceProvider.Instance.GetRequiredService(), + StaticServiceProvider.Instance.GetRequiredService() + ) + { + + } + + public ContentEditingService( + IContentService contentService, + IContentTypeService contentTypeService, + PropertyEditorCollection propertyEditorCollection, + IDataTypeService dataTypeService, + ITemplateService templateService, + ILogger logger, + ICoreScopeProvider scopeProvider, + IUserIdKeyResolver userIdKeyResolver, + ITreeEntitySortingService treeEntitySortingService, + IContentValidationService contentValidationService, + IUserService userService, + ILocalizationService localizationService, + ILanguageService languageService) : base(contentService, contentTypeService, propertyEditorCollection, dataTypeService, logger, scopeProvider, userIdKeyResolver, contentValidationService, treeEntitySortingService) { _templateService = templateService; _logger = logger; + _userService = userService; + _localizationService = localizationService; + _languageService = languageService; } public async Task GetAsync(Guid key) @@ -65,7 +109,7 @@ internal sealed class ContentEditingService ContentEditingOperationStatus validationStatus = result.Status; ContentValidationResult validationResult = result.Result.ValidationResult; - IContent content = result.Result.Content!; + IContent content = await EnsureOnlyAllowedFieldsAreUpdated(result.Result.Content!, userKey); ContentEditingOperationStatus updateTemplateStatus = await UpdateTemplateAsync(content, createModel.TemplateKey); if (updateTemplateStatus != ContentEditingOperationStatus.Success) { @@ -78,6 +122,53 @@ internal sealed class ContentEditingService : Attempt.FailWithStatus(saveStatus, new ContentCreateResult { Content = content }); } + /// + /// A temporary method that ensures the data is sent in is overridden by the original data, in cases where the user do not have permissions to change the data. + /// + private async Task EnsureOnlyAllowedFieldsAreUpdated(IContent contentWithPotentialUnallowedChanges, Guid userKey) + { + if (contentWithPotentialUnallowedChanges.ContentType.VariesByCulture() is false) + { + return contentWithPotentialUnallowedChanges; + } + + IContent? existingContent = await GetAsync(contentWithPotentialUnallowedChanges.Key); + + IUser? user = await _userService.GetAsync(userKey); + + if (user is null) + { + return contentWithPotentialUnallowedChanges; + } + + var allowedLanguageIds = user.CalculateAllowedLanguageIds(_localizationService)!; + + var allowedCultures = (await _languageService.GetIsoCodesByIdsAsync(allowedLanguageIds)).ToHashSet(); + + foreach (var culture in contentWithPotentialUnallowedChanges.EditedCultures ?? contentWithPotentialUnallowedChanges.PublishedCultures) + { + if (allowedCultures.Contains(culture)) + { + continue; + } + + + // else override the updates values with the original values. + foreach (IProperty property in contentWithPotentialUnallowedChanges.Properties) + { + if (property.PropertyType.VariesByCulture() is false) + { + continue; + } + + var value = existingContent?.Properties.First(x=>x.Alias == property.Alias).GetValue(culture, null, false); + property.SetValue(value, culture, null); + } + } + + return contentWithPotentialUnallowedChanges; + } + public async Task> UpdateAsync(Guid key, ContentUpdateModel updateModel, Guid userKey) { IContent? content = ContentService.GetById(key); @@ -102,6 +193,8 @@ internal sealed class ContentEditingService ContentEditingOperationStatus validationStatus = result.Status; ContentValidationResult validationResult = result.Result.ValidationResult; + content = await EnsureOnlyAllowedFieldsAreUpdated(content, userKey); + ContentEditingOperationStatus updateTemplateStatus = await UpdateTemplateAsync(content, updateModel.TemplateKey); if (updateTemplateStatus != ContentEditingOperationStatus.Success) { From d2d6d3493fac34b761ea9482dbb264f5beebba98 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Wed, 18 Sep 2024 13:15:59 +0200 Subject: [PATCH 24/41] Revert "Bump version" This reverts commit f8e26f5ba12b32723c19cfedf91b3faa19c4acf9. --- version.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.json b/version.json index 3b2f02bc66..2fb06038d0 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.3.0", + "version": "14.3.0-rc", "assemblyVersion": { "precision": "build" }, From 234c2673f6520a0ff5b47445169b42e8451b6a96 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Wed, 18 Sep 2024 13:10:15 +0200 Subject: [PATCH 25/41] Allow the client to send all content, with all languages, even when the user do not have permissions to save a specific language. (#17052) (cherry picked from commit c5243e562e6cc869459b12a993cdec57f1fc762b) --- .../Document/CreateDocumentControllerBase.cs | 25 ++--- .../Document/UpdateDocumentControllerBase.cs | 25 ++--- .../Services/ContentEditingService.cs | 97 ++++++++++++++++++- 3 files changed, 123 insertions(+), 24 deletions(-) diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Document/CreateDocumentControllerBase.cs b/src/Umbraco.Cms.Api.Management/Controllers/Document/CreateDocumentControllerBase.cs index d6983964c5..669c2cdc93 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Document/CreateDocumentControllerBase.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Document/CreateDocumentControllerBase.cs @@ -18,18 +18,21 @@ public abstract class CreateDocumentControllerBase : DocumentControllerBase protected async Task HandleRequest(CreateDocumentRequestModel requestModel, Func> authorizedHandler) { - IEnumerable cultures = requestModel.Variants - .Where(v => v.Culture is not null) - .Select(v => v.Culture!); - AuthorizationResult authorizationResult = await _authorizationService.AuthorizeResourceAsync( - User, - ContentPermissionResource.WithKeys(ActionNew.ActionLetter, requestModel.Parent?.Id, cultures), - AuthorizationPolicies.ContentPermissionByResource); + // TODO This have temporarily been uncommented, to support the client sends values from all cultures, even when the user do not have access to the languages. + // The values are ignored in the ContentEditingService - if (!authorizationResult.Succeeded) - { - return Forbidden(); - } + // IEnumerable cultures = requestModel.Variants + // .Where(v => v.Culture is not null) + // .Select(v => v.Culture!); + // AuthorizationResult authorizationResult = await _authorizationService.AuthorizeResourceAsync( + // User, + // ContentPermissionResource.WithKeys(ActionNew.ActionLetter, requestModel.Parent?.Id, cultures), + // AuthorizationPolicies.ContentPermissionByResource); + // + // if (!authorizationResult.Succeeded) + // { + // return Forbidden(); + // } return await authorizedHandler(); } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Document/UpdateDocumentControllerBase.cs b/src/Umbraco.Cms.Api.Management/Controllers/Document/UpdateDocumentControllerBase.cs index 2d41fe94fe..4b585e78b9 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Document/UpdateDocumentControllerBase.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Document/UpdateDocumentControllerBase.cs @@ -17,18 +17,21 @@ public abstract class UpdateDocumentControllerBase : DocumentControllerBase protected async Task HandleRequest(Guid id, UpdateDocumentRequestModel requestModel, Func> authorizedHandler) { - IEnumerable cultures = requestModel.Variants - .Where(v => v.Culture is not null) - .Select(v => v.Culture!); - AuthorizationResult authorizationResult = await _authorizationService.AuthorizeResourceAsync( - User, - ContentPermissionResource.WithKeys(ActionUpdate.ActionLetter, id, cultures), - AuthorizationPolicies.ContentPermissionByResource); + // TODO This have temporarily been uncommented, to support the client sends values from all cultures, even when the user do not have access to the languages. + // The values are ignored in the ContentEditingService - if (!authorizationResult.Succeeded) - { - return Forbidden(); - } + // IEnumerable cultures = requestModel.Variants + // .Where(v => v.Culture is not null) + // .Select(v => v.Culture!); + // AuthorizationResult authorizationResult = await _authorizationService.AuthorizeResourceAsync( + // User, + // ContentPermissionResource.WithKeys(ActionUpdate.ActionLetter, id, cultures), + // AuthorizationPolicies.ContentPermissionByResource); + // + // if (!authorizationResult.Succeeded) + // { + // return Forbidden(); + // } return await authorizedHandler(); } diff --git a/src/Umbraco.Core/Services/ContentEditingService.cs b/src/Umbraco.Core/Services/ContentEditingService.cs index 2ad8365bcf..bc15e7ea44 100644 --- a/src/Umbraco.Core/Services/ContentEditingService.cs +++ b/src/Umbraco.Core/Services/ContentEditingService.cs @@ -1,9 +1,13 @@ -using Microsoft.Extensions.Logging; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.ContentEditing; +using Umbraco.Cms.Core.Models.Membership; using Umbraco.Cms.Core.PropertyEditors; using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Core.Services.OperationStatus; +using Umbraco.Extensions; namespace Umbraco.Cms.Core.Services; @@ -12,7 +16,11 @@ internal sealed class ContentEditingService { private readonly ITemplateService _templateService; private readonly ILogger _logger; + private readonly IUserService _userService; + private readonly ILocalizationService _localizationService; + private readonly ILanguageService _languageService; + [Obsolete("Use non-obsolete constructor. This will be removed in Umbraco 16.")] public ContentEditingService( IContentService contentService, IContentTypeService contentTypeService, @@ -24,10 +32,46 @@ internal sealed class ContentEditingService IUserIdKeyResolver userIdKeyResolver, ITreeEntitySortingService treeEntitySortingService, IContentValidationService contentValidationService) + : this( + contentService, + contentTypeService, + propertyEditorCollection, + dataTypeService, + templateService, + logger, + scopeProvider, + userIdKeyResolver, + treeEntitySortingService, + contentValidationService, + StaticServiceProvider.Instance.GetRequiredService(), + StaticServiceProvider.Instance.GetRequiredService(), + StaticServiceProvider.Instance.GetRequiredService() + ) + { + + } + + public ContentEditingService( + IContentService contentService, + IContentTypeService contentTypeService, + PropertyEditorCollection propertyEditorCollection, + IDataTypeService dataTypeService, + ITemplateService templateService, + ILogger logger, + ICoreScopeProvider scopeProvider, + IUserIdKeyResolver userIdKeyResolver, + ITreeEntitySortingService treeEntitySortingService, + IContentValidationService contentValidationService, + IUserService userService, + ILocalizationService localizationService, + ILanguageService languageService) : base(contentService, contentTypeService, propertyEditorCollection, dataTypeService, logger, scopeProvider, userIdKeyResolver, contentValidationService, treeEntitySortingService) { _templateService = templateService; _logger = logger; + _userService = userService; + _localizationService = localizationService; + _languageService = languageService; } public async Task GetAsync(Guid key) @@ -65,7 +109,7 @@ internal sealed class ContentEditingService ContentEditingOperationStatus validationStatus = result.Status; ContentValidationResult validationResult = result.Result.ValidationResult; - IContent content = result.Result.Content!; + IContent content = await EnsureOnlyAllowedFieldsAreUpdated(result.Result.Content!, userKey); ContentEditingOperationStatus updateTemplateStatus = await UpdateTemplateAsync(content, createModel.TemplateKey); if (updateTemplateStatus != ContentEditingOperationStatus.Success) { @@ -78,6 +122,53 @@ internal sealed class ContentEditingService : Attempt.FailWithStatus(saveStatus, new ContentCreateResult { Content = content }); } + /// + /// A temporary method that ensures the data is sent in is overridden by the original data, in cases where the user do not have permissions to change the data. + /// + private async Task EnsureOnlyAllowedFieldsAreUpdated(IContent contentWithPotentialUnallowedChanges, Guid userKey) + { + if (contentWithPotentialUnallowedChanges.ContentType.VariesByCulture() is false) + { + return contentWithPotentialUnallowedChanges; + } + + IContent? existingContent = await GetAsync(contentWithPotentialUnallowedChanges.Key); + + IUser? user = await _userService.GetAsync(userKey); + + if (user is null) + { + return contentWithPotentialUnallowedChanges; + } + + var allowedLanguageIds = user.CalculateAllowedLanguageIds(_localizationService)!; + + var allowedCultures = (await _languageService.GetIsoCodesByIdsAsync(allowedLanguageIds)).ToHashSet(); + + foreach (var culture in contentWithPotentialUnallowedChanges.EditedCultures ?? contentWithPotentialUnallowedChanges.PublishedCultures) + { + if (allowedCultures.Contains(culture)) + { + continue; + } + + + // else override the updates values with the original values. + foreach (IProperty property in contentWithPotentialUnallowedChanges.Properties) + { + if (property.PropertyType.VariesByCulture() is false) + { + continue; + } + + var value = existingContent?.Properties.First(x=>x.Alias == property.Alias).GetValue(culture, null, false); + property.SetValue(value, culture, null); + } + } + + return contentWithPotentialUnallowedChanges; + } + public async Task> UpdateAsync(Guid key, ContentUpdateModel updateModel, Guid userKey) { IContent? content = ContentService.GetById(key); @@ -102,6 +193,8 @@ internal sealed class ContentEditingService ContentEditingOperationStatus validationStatus = result.Status; ContentValidationResult validationResult = result.Result.ValidationResult; + content = await EnsureOnlyAllowedFieldsAreUpdated(content, userKey); + ContentEditingOperationStatus updateTemplateStatus = await UpdateTemplateAsync(content, updateModel.TemplateKey); if (updateTemplateStatus != ContentEditingOperationStatus.Success) { From 59aac9053fc23394ac636e43199309924957aa86 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Wed, 18 Sep 2024 13:17:16 +0200 Subject: [PATCH 26/41] Bump version --- version.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.json b/version.json index 2fb06038d0..3b2f02bc66 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.3.0-rc", + "version": "14.3.0", "assemblyVersion": { "precision": "build" }, From 753aa6f9db69875d879353c6c37b8e8a3b652a43 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Wed, 18 Sep 2024 13:46:05 +0200 Subject: [PATCH 27/41] Fixed version --- version.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.json b/version.json index 3b2f02bc66..2fb06038d0 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.3.0", + "version": "14.3.0-rc", "assemblyVersion": { "precision": "build" }, From fa7b81474cce45fae6bee6fe0832416099c06f24 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Wed, 18 Sep 2024 13:48:23 +0200 Subject: [PATCH 28/41] Bump version to 14.3.0 --- version.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.json b/version.json index 2fb06038d0..3b2f02bc66 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.3.0-rc", + "version": "14.3.0", "assemblyVersion": { "precision": "build" }, From c70c8d86f9ffe00660f835906cc06c1c627d3603 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Mon, 23 Sep 2024 09:58:52 +0200 Subject: [PATCH 29/41] 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 662e2268cb..0880e2551d 160000 --- a/src/Umbraco.Web.UI.Client +++ b/src/Umbraco.Web.UI.Client @@ -1 +1 @@ -Subproject commit 662e2268cbdbf7562fe9bcd4fc9222bb24e26563 +Subproject commit 0880e2551d8f0e3dc095742795f5182b9467d6a1 From e92b4f317226af3417a6c0eec268cfa9fdca2955 Mon Sep 17 00:00:00 2001 From: Andreas Zerbst <73799582+andr317c@users.noreply.github.com> Date: Mon, 23 Sep 2024 12:10:46 +0200 Subject: [PATCH 30/41] V15 QA Enabled Nightly E2E Pipeline to run on V15 (#17103) * Uncommented * Added timeout (cherry picked from commit 142db8c0fb543c154db30a5fe95b1f4abfd5a2fe) --- build/nightly-E2E-test-pipelines.yml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/build/nightly-E2E-test-pipelines.yml b/build/nightly-E2E-test-pipelines.yml index b1dfe9261c..ce67b778e3 100644 --- a/build/nightly-E2E-test-pipelines.yml +++ b/build/nightly-E2E-test-pipelines.yml @@ -9,8 +9,7 @@ schedules: branches: include: - v14/dev - ## Uncomment after merged to v15/dev - ## - v15/dev + - v15/dev variables: nodeVersion: 20 @@ -109,7 +108,7 @@ stages: # E2E Tests - job: displayName: E2E Tests (SQLite) - timeoutInMinutes: 120 + timeoutInMinutes: 180 variables: # Connection string CONNECTIONSTRINGS__UMBRACODBDSN: Data Source=Umbraco;Mode=Memory;Cache=Shared;Foreign Keys=True;Pooling=True @@ -244,7 +243,7 @@ stages: - job: displayName: E2E Tests (SQL Server) condition: and(succeeded(), ${{ eq(parameters.runSqlServerE2ETests, true) }}) - timeoutInMinutes: 120 + timeoutInMinutes: 180 variables: # Connection string CONNECTIONSTRINGS__UMBRACODBDSN: Data Source=(localdb)\MSSQLLocalDB;AttachDbFilename=|DataDirectory|\Umbraco.mdf;Integrated Security=True From 264fbb698738b901667e4981e50a85b522bd3670 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Tue, 24 Sep 2024 14:34:00 +0200 Subject: [PATCH 31/41] 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 662e2268cb..0880e2551d 160000 --- a/src/Umbraco.Web.UI.Client +++ b/src/Umbraco.Web.UI.Client @@ -1 +1 @@ -Subproject commit 662e2268cbdbf7562fe9bcd4fc9222bb24e26563 +Subproject commit 0880e2551d8f0e3dc095742795f5182b9467d6a1 From e4dacf5c8c1771360d7e2a2919727102308ac3bb Mon Sep 17 00:00:00 2001 From: Andreas Zerbst <73799582+andr317c@users.noreply.github.com> Date: Wed, 25 Sep 2024 07:04:12 +0200 Subject: [PATCH 32/41] V14 QA fixed E2E tests for SQL server (#17122) * Updated version of test helpers * Added option to run smoke tests * Found the issue * Fixed pipeline * Removed duplicate file creation * Removed * Always run sql server tests * Removed unused parameter * Enables sqlServer E2E to run on pipeline * Removed comment --- build/azure-pipelines.yml | 11 ++----- build/nightly-E2E-test-pipelines.yml | 30 +++++++++++-------- build/nightly-build-trigger.yml | 2 +- .../package-lock.json | 18 +++++------ .../Umbraco.Tests.AcceptanceTest/package.json | 4 +-- 5 files changed, 33 insertions(+), 32 deletions(-) diff --git a/build/azure-pipelines.yml b/build/azure-pipelines.yml index dc5ef96bde..9e7207bf07 100644 --- a/build/azure-pipelines.yml +++ b/build/azure-pipelines.yml @@ -5,10 +5,6 @@ parameters: displayName: Run SQL Server Integration Tests type: boolean default: false - - name: sqlServerAcceptanceTests - displayName: Run SQL Server Acceptance Tests - type: boolean - default: false - name: myGetDeploy displayName: Deploy to MyGet type: boolean @@ -553,8 +549,6 @@ stages: - job: displayName: E2E Tests (SQL Server) - # condition: or(eq(stageDependencies.Build.A.outputs['build.NBGV_PublicRelease'], 'True'), ${{parameters.sqlServerAcceptanceTests}}) # Outcommented due to timeouts - condition: eq(${{parameters.sqlServerAcceptanceTests}}, True) variables: # Connection string CONNECTIONSTRINGS__UMBRACODBDSN: Data Source=(localdb)\MSSQLLocalDB;AttachDbFilename=|DataDirectory|\Umbraco.mdf;Integrated Security=True @@ -590,7 +584,8 @@ stages: - pwsh: | "UMBRACO_USER_LOGIN=$(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSEREMAIL) UMBRACO_USER_PASSWORD=$(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSERPASSWORD) - URL=$(ASPNETCORE_URLS)" | Out-File .env + URL=$(ASPNETCORE_URLS) + STORAGE_STAGE_PATH=$(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest/playwright/.auth/user.json" | Out-File .env displayName: Generate .env workingDirectory: $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest @@ -708,7 +703,7 @@ stages: dependsOn: - Unit - Integration -# - E2E + # - E2E condition: and(succeeded(), or(eq(dependencies.Build.outputs['A.build.NBGV_PublicRelease'], 'True'), ${{parameters.myGetDeploy}})) jobs: - job: diff --git a/build/nightly-E2E-test-pipelines.yml b/build/nightly-E2E-test-pipelines.yml index ce67b778e3..6a069ef38c 100644 --- a/build/nightly-E2E-test-pipelines.yml +++ b/build/nightly-E2E-test-pipelines.yml @@ -4,12 +4,12 @@ pr: none trigger: none schedules: -- cron: '0 0 * * *' - displayName: Daily midnight build - branches: - include: - - v14/dev - - v15/dev + - cron: '0 0 * * *' + displayName: Daily midnight build + branches: + include: + - v14/dev + - v15/dev variables: nodeVersion: 20 @@ -24,8 +24,8 @@ variables: NODE_OPTIONS: --max_old_space_size=16384 parameters: - - name: runSqlServerE2ETests - displayName: Run the SQL Server E2E Tests + - name: runSmokeTests + displayName: Run the smoke tests type: boolean default: false @@ -206,7 +206,10 @@ stages: workingDirectory: tests/Umbraco.Tests.AcceptanceTest # Test - - pwsh: npm run test --ignore-certificate-errors + - ${{ if eq(parameters.runSmokeTests, true) }}: + pwsh: npm run smokeTest --ignore-certificate-errors + ${{ else }}: + pwsh: npm run test --ignore-certificate-errors displayName: Run Playwright tests continueOnError: true workingDirectory: tests/Umbraco.Tests.AcceptanceTest @@ -242,7 +245,6 @@ stages: - job: displayName: E2E Tests (SQL Server) - condition: and(succeeded(), ${{ eq(parameters.runSqlServerE2ETests, true) }}) timeoutInMinutes: 180 variables: # Connection string @@ -279,7 +281,8 @@ stages: - pwsh: | "UMBRACO_USER_LOGIN=$(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSEREMAIL) UMBRACO_USER_PASSWORD=$(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSERPASSWORD) - URL=$(ASPNETCORE_URLS)" | Out-File .env + URL=$(ASPNETCORE_URLS) + STORAGE_STAGE_PATH=$(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest/playwright/.auth/user.json" | Out-File .env displayName: Generate .env workingDirectory: $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest @@ -350,7 +353,10 @@ stages: workingDirectory: tests/Umbraco.Tests.AcceptanceTest # Test - - pwsh: npm run test --ignore-certificate-errors + - ${{ if eq(parameters.runSmokeTests, true) }}: + pwsh: npm run smokeTest --ignore-certificate-errors + ${{ else }}: + pwsh: npm run test --ignore-certificate-errors displayName: Run Playwright tests continueOnError: true workingDirectory: tests/Umbraco.Tests.AcceptanceTest diff --git a/build/nightly-build-trigger.yml b/build/nightly-build-trigger.yml index 16cc06533d..7e128b2af7 100644 --- a/build/nightly-build-trigger.yml +++ b/build/nightly-build-trigger.yml @@ -26,7 +26,7 @@ steps: useSameBranch: true waitForQueuedBuildsToFinish: false storeInEnvironmentVariable: false - templateParameters: 'sqlServerIntegrationTests: true, sqlServerAcceptanceTests: true, forceReleaseTestFilter: true, myGetDeploy: true, isNightly: true' + templateParameters: 'sqlServerIntegrationTests: true, forceReleaseTestFilter: true, myGetDeploy: true, isNightly: true' authenticationMethod: 'OAuth Token' enableBuildInQueueCondition: false dependentOnSuccessfulBuildCondition: false diff --git a/tests/Umbraco.Tests.AcceptanceTest/package-lock.json b/tests/Umbraco.Tests.AcceptanceTest/package-lock.json index c8fe8a078f..bb227d8a12 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.17", - "@umbraco/playwright-testhelpers": "^2.0.0-beta.82", + "@umbraco/json-models-builders": "^2.0.20", + "@umbraco/playwright-testhelpers": "^2.0.0-beta.84", "camelize": "^1.0.0", "dotenv": "^16.3.1", "node-fetch": "^2.6.7" @@ -55,21 +55,21 @@ } }, "node_modules/@umbraco/json-models-builders": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/@umbraco/json-models-builders/-/json-models-builders-2.0.18.tgz", - "integrity": "sha512-VC2KCuWVhae0HzVpo9RrOQt6zZSQqSpWqwCoKYYwmhRz/SYo6hARV6sH2ceEFsQwGqqJvakXuUWzlJK7bFqK1Q==", + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/@umbraco/json-models-builders/-/json-models-builders-2.0.20.tgz", + "integrity": "sha512-LmTtklne1HlhMr1nALA+P5FrjIC9jL3A6Pcxj4dy+IPnTgnU2vMYaQIfE8wwz5Z5fZ5AAhWx/Zpdi8xCTbVSuQ==", "license": "MIT", "dependencies": { "camelize": "^1.0.1" } }, "node_modules/@umbraco/playwright-testhelpers": { - "version": "2.0.0-beta.82", - "resolved": "https://registry.npmjs.org/@umbraco/playwright-testhelpers/-/playwright-testhelpers-2.0.0-beta.82.tgz", - "integrity": "sha512-VkArVyvkKuTwJJH8eCHSvbho4H1Owx2ifidVuPyN8EVGDWbxOTb5i9jmtFjJnfDg9mg50JhRYKas4lUGvy1pBA==", + "version": "2.0.0-beta.84", + "resolved": "https://registry.npmjs.org/@umbraco/playwright-testhelpers/-/playwright-testhelpers-2.0.0-beta.84.tgz", + "integrity": "sha512-vH13Lg48knTkkLVTwhMXUKTOdjtmixFj0wF5Qhgb++13u4AVDb+oW+TbFwTjSYaLeNMraq5Uhwmto/XuJPs2Rw==", "license": "MIT", "dependencies": { - "@umbraco/json-models-builders": "2.0.18", + "@umbraco/json-models-builders": "2.0.20", "node-fetch": "^2.6.7" } }, diff --git a/tests/Umbraco.Tests.AcceptanceTest/package.json b/tests/Umbraco.Tests.AcceptanceTest/package.json index 78cbb58c73..30706ad5de 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/package.json +++ b/tests/Umbraco.Tests.AcceptanceTest/package.json @@ -18,8 +18,8 @@ "typescript": "^4.8.3" }, "dependencies": { - "@umbraco/json-models-builders": "^2.0.17", - "@umbraco/playwright-testhelpers": "^2.0.0-beta.82", + "@umbraco/json-models-builders": "^2.0.20", + "@umbraco/playwright-testhelpers": "^2.0.0-beta.84", "camelize": "^1.0.0", "dotenv": "^16.3.1", "node-fetch": "^2.6.7" From 609b5f76d4c74e9a0ee9a7d1ffa62562ffbfaf23 Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Thu, 26 Sep 2024 07:47:33 +0200 Subject: [PATCH 33/41] Fix `IContentBase.GetUdi()` extension method to support document-blueprint entity type (#16939) * Add tests for all UDI entity types * Fix IContentBase UDI entity type for blueprints * Remove redundant switch statements and reorder methods --- .../Extensions/UdiGetterExtensions.cs | 424 +++++++++--------- .../Builders/ContentBuilder.cs | 10 + .../Extensions/UdiGetterExtensionsTests.cs | 268 ++++++++++- 3 files changed, 460 insertions(+), 242 deletions(-) diff --git a/src/Umbraco.Core/Extensions/UdiGetterExtensions.cs b/src/Umbraco.Core/Extensions/UdiGetterExtensions.cs index e4b11ccb6c..66c5002604 100644 --- a/src/Umbraco.Core/Extensions/UdiGetterExtensions.cs +++ b/src/Umbraco.Core/Extensions/UdiGetterExtensions.cs @@ -8,7 +8,7 @@ using Umbraco.Cms.Core.Models.Entities; namespace Umbraco.Extensions; /// -/// Provides extension methods that return udis for Umbraco entities. +/// Provides extension methods that return UDIs for Umbraco entities. /// public static class UdiGetterExtensions { @@ -19,11 +19,177 @@ public static class UdiGetterExtensions /// /// The entity identifier of the entity. /// - public static GuidUdi GetUdi(this ITemplate entity) + public static Udi GetUdi(this IEntity entity) { ArgumentNullException.ThrowIfNull(entity); - return new GuidUdi(Constants.UdiEntityType.Template, entity.Key).EnsureClosed(); + return entity switch + { + // Concrete types + EntityContainer container => container.GetUdi(), + Script script => script.GetUdi(), + Stylesheet stylesheet => stylesheet.GetUdi(), + // Interfaces + IContentBase contentBase => contentBase.GetUdi(), + IContentTypeComposition contentTypeComposition => contentTypeComposition.GetUdi(), + IDataType dataType => dataType.GetUdi(), + IDictionaryItem dictionaryItem => dictionaryItem.GetUdi(), + ILanguage language => language.GetUdi(), + IMemberGroup memberGroup => memberGroup.GetUdi(), + IPartialView partialView => partialView.GetUdi(), + IRelationType relationType => relationType.GetUdi(), + ITemplate template => template.GetUdi(), + IWebhook webhook => webhook.GetUdi(), + _ => throw new NotSupportedException($"Entity type {entity.GetType().FullName} is not supported."), + }; + } + + /// + /// Gets the entity identifier of the entity. + /// + /// The entity. + /// + /// The entity identifier of the entity. + /// + public static GuidUdi GetUdi(this EntityContainer entity) + { + ArgumentNullException.ThrowIfNull(entity); + + string entityType; + if (entity.ContainedObjectType == Constants.ObjectTypes.DataType) + { + entityType = Constants.UdiEntityType.DataTypeContainer; + } + else if (entity.ContainedObjectType == Constants.ObjectTypes.DocumentType) + { + entityType = Constants.UdiEntityType.DocumentTypeContainer; + } + else if (entity.ContainedObjectType == Constants.ObjectTypes.MediaType) + { + entityType = Constants.UdiEntityType.MediaTypeContainer; + } + else if (entity.ContainedObjectType == Constants.ObjectTypes.DocumentBlueprint) + { + entityType = Constants.UdiEntityType.DocumentBlueprintContainer; + } + else + { + throw new NotSupportedException($"Contained object type {entity.ContainedObjectType} is not supported."); + } + + return new GuidUdi(entityType, entity.Key).EnsureClosed(); + } + + /// + /// Gets the entity identifier of the entity. + /// + /// The entity. + /// + /// The entity identifier of the entity. + /// + public static StringUdi GetUdi(this Script entity) + { + ArgumentNullException.ThrowIfNull(entity); + + return GetUdiFromPath(Constants.UdiEntityType.Script, entity.Path); + } + + /// + /// Gets the entity identifier of the entity. + /// + /// The entity. + /// + /// The entity identifier of the entity. + /// + public static StringUdi GetUdi(this Stylesheet entity) + { + ArgumentNullException.ThrowIfNull(entity); + + return GetUdiFromPath(Constants.UdiEntityType.Stylesheet, entity.Path); + } + + /// + /// Gets the entity identifier of the entity. + /// + /// The entity. + /// + /// The entity identifier of the entity. + /// + public static GuidUdi GetUdi(this IContentBase entity) + { + ArgumentNullException.ThrowIfNull(entity); + + return entity switch + { + IContent content => content.GetUdi(), + IMedia media => media.GetUdi(), + IMember member => member.GetUdi(), + _ => throw new NotSupportedException($"Content base type {entity.GetType().FullName} is not supported."), + }; + } + + /// + /// Gets the entity identifier of the entity. + /// + /// The entity. + /// + /// The entity identifier of the entity. + /// + public static GuidUdi GetUdi(this IContent entity) + { + ArgumentNullException.ThrowIfNull(entity); + + string entityType = entity.Blueprint ? Constants.UdiEntityType.DocumentBlueprint : Constants.UdiEntityType.Document; + + return new GuidUdi(entityType, entity.Key).EnsureClosed(); + } + + /// + /// Gets the entity identifier of the entity. + /// + /// The entity. + /// + /// The entity identifier of the entity. + /// + public static GuidUdi GetUdi(this IMedia entity) + { + ArgumentNullException.ThrowIfNull(entity); + + return new GuidUdi(Constants.UdiEntityType.Media, entity.Key).EnsureClosed(); + } + + /// + /// Gets the entity identifier of the entity. + /// + /// The entity. + /// + /// The entity identifier of the entity. + /// + public static GuidUdi GetUdi(this IMember entity) + { + ArgumentNullException.ThrowIfNull(entity); + + return new GuidUdi(Constants.UdiEntityType.Member, entity.Key).EnsureClosed(); + } + + /// + /// Gets the entity identifier of the entity. + /// + /// The entity. + /// + /// The entity identifier of the entity. + /// + public static GuidUdi GetUdi(this IContentTypeComposition entity) + { + ArgumentNullException.ThrowIfNull(entity); + + return entity switch + { + IContentType contentType => contentType.GetUdi(), + IMediaType mediaType => mediaType.GetUdi(), + IMemberType memberType => memberType.GetUdi(), + _ => throw new NotSupportedException($"Composition type {entity.GetType().FullName} is not supported."), + }; } /// @@ -68,42 +234,6 @@ public static class UdiGetterExtensions return new GuidUdi(Constants.UdiEntityType.MemberType, entity.Key).EnsureClosed(); } - /// - /// Gets the entity identifier of the entity. - /// - /// The entity. - /// - /// The entity identifier of the entity. - /// - public static GuidUdi GetUdi(this IMemberGroup entity) - { - ArgumentNullException.ThrowIfNull(entity); - - return new GuidUdi(Constants.UdiEntityType.MemberGroup, entity.Key).EnsureClosed(); - } - - /// - /// Gets the entity identifier of the entity. - /// - /// The entity. - /// - /// The entity identifier of the entity. - /// - public static GuidUdi GetUdi(this IContentTypeComposition entity) - { - ArgumentNullException.ThrowIfNull(entity); - - string entityType = entity switch - { - IContentType => Constants.UdiEntityType.DocumentType, - IMediaType => Constants.UdiEntityType.MediaType, - IMemberType => Constants.UdiEntityType.MemberType, - _ => throw new NotSupportedException(string.Format("Composition type {0} is not supported.", entity.GetType().FullName)), - }; - - return new GuidUdi(entityType, entity.Key).EnsureClosed(); - } - /// /// Gets the entity identifier of the entity. /// @@ -118,129 +248,6 @@ public static class UdiGetterExtensions return new GuidUdi(Constants.UdiEntityType.DataType, entity.Key).EnsureClosed(); } - /// - /// Gets the entity identifier of the entity. - /// - /// The entity. - /// - /// The entity identifier of the entity. - /// - public static GuidUdi GetUdi(this EntityContainer entity) - { - ArgumentNullException.ThrowIfNull(entity); - - string entityType; - if (entity.ContainedObjectType == Constants.ObjectTypes.DataType) - { - entityType = Constants.UdiEntityType.DataTypeContainer; - } - else if (entity.ContainedObjectType == Constants.ObjectTypes.DocumentType) - { - entityType = Constants.UdiEntityType.DocumentTypeContainer; - } - else if (entity.ContainedObjectType == Constants.ObjectTypes.MediaType) - { - entityType = Constants.UdiEntityType.MediaTypeContainer; - } - else if (entity.ContainedObjectType == Constants.ObjectTypes.DocumentBlueprint) - { - entityType = Constants.UdiEntityType.DocumentBlueprintContainer; - } - else - { - throw new NotSupportedException(string.Format("Contained object type {0} is not supported.", entity.ContainedObjectType)); - } - - return new GuidUdi(entityType, entity.Key).EnsureClosed(); - } - - /// - /// Gets the entity identifier of the entity. - /// - /// The entity. - /// - /// The entity identifier of the entity. - /// - public static GuidUdi GetUdi(this IMedia entity) - { - ArgumentNullException.ThrowIfNull(entity); - - return new GuidUdi(Constants.UdiEntityType.Media, entity.Key).EnsureClosed(); - } - - /// - /// Gets the entity identifier of the entity. - /// - /// The entity. - /// - /// The entity identifier of the entity. - /// - public static GuidUdi GetUdi(this IContent entity) - { - ArgumentNullException.ThrowIfNull(entity); - - string entityType = entity.Blueprint ? Constants.UdiEntityType.DocumentBlueprint : Constants.UdiEntityType.Document; - - return new GuidUdi(entityType, entity.Key).EnsureClosed(); - } - - /// - /// Gets the entity identifier of the entity. - /// - /// The entity. - /// - /// The entity identifier of the entity. - /// - public static GuidUdi GetUdi(this IMember entity) - { - ArgumentNullException.ThrowIfNull(entity); - - return new GuidUdi(Constants.UdiEntityType.Member, entity.Key).EnsureClosed(); - } - - /// - /// Gets the entity identifier of the entity. - /// - /// The entity. - /// - /// The entity identifier of the entity. - /// - public static StringUdi GetUdi(this Stylesheet entity) - { - ArgumentNullException.ThrowIfNull(entity); - - return GetUdiFromPath(Constants.UdiEntityType.Stylesheet, entity.Path); - } - - /// - /// Gets the entity identifier of the entity. - /// - /// The entity. - /// - /// The entity identifier of the entity. - /// - public static StringUdi GetUdi(this Script entity) - { - ArgumentNullException.ThrowIfNull(entity); - - return GetUdiFromPath(Constants.UdiEntityType.Script, entity.Path); - } - - /// - /// Gets the UDI from a path. - /// - /// The type of the entity. - /// The path. - /// - /// The entity identifier of the entity. - /// - private static StringUdi GetUdiFromPath(string entityType, string path) - { - string id = path.TrimStart(Constants.CharArrays.ForwardSlash).Replace("\\", "/"); - - return new StringUdi(entityType, id).EnsureClosed(); - } - /// /// Gets the entity identifier of the entity. /// @@ -262,11 +269,11 @@ public static class UdiGetterExtensions /// /// The entity identifier of the entity. /// - public static StringUdi GetUdi(this IPartialView entity) + public static StringUdi GetUdi(this ILanguage entity) { ArgumentNullException.ThrowIfNull(entity); - return GetUdiFromPath(Constants.UdiEntityType.PartialView, entity.Path); + return new StringUdi(Constants.UdiEntityType.Language, entity.IsoCode).EnsureClosed(); } /// @@ -276,19 +283,25 @@ public static class UdiGetterExtensions /// /// The entity identifier of the entity. /// - public static GuidUdi GetUdi(this IContentBase entity) + public static GuidUdi GetUdi(this IMemberGroup entity) { ArgumentNullException.ThrowIfNull(entity); - string type = entity switch - { - IContent => Constants.UdiEntityType.Document, - IMedia => Constants.UdiEntityType.Media, - IMember => Constants.UdiEntityType.Member, - _ => throw new NotSupportedException(string.Format("Content base type {0} is not supported.", entity.GetType().FullName)), - }; + return new GuidUdi(Constants.UdiEntityType.MemberGroup, entity.Key).EnsureClosed(); + } - return new GuidUdi(type, entity.Key).EnsureClosed(); + /// + /// Gets the entity identifier of the entity. + /// + /// The entity. + /// + /// The entity identifier of the entity. + /// + public static StringUdi GetUdi(this IPartialView entity) + { + ArgumentNullException.ThrowIfNull(entity); + + return GetUdiFromPath(Constants.UdiEntityType.PartialView, entity.Path); } /// @@ -305,6 +318,20 @@ public static class UdiGetterExtensions return new GuidUdi(Constants.UdiEntityType.RelationType, entity.Key).EnsureClosed(); } + /// + /// Gets the entity identifier of the entity. + /// + /// The entity. + /// + /// The entity identifier of the entity. + /// + public static GuidUdi GetUdi(this ITemplate entity) + { + ArgumentNullException.ThrowIfNull(entity); + + return new GuidUdi(Constants.UdiEntityType.Template, entity.Key).EnsureClosed(); + } + /// /// Gets the entity identifier of the entity. /// @@ -320,56 +347,17 @@ public static class UdiGetterExtensions } /// - /// Gets the entity identifier of the entity. + /// Gets the UDI from a path. /// - /// The entity. + /// The type of the entity. + /// The path. /// /// The entity identifier of the entity. /// - public static StringUdi GetUdi(this ILanguage entity) + private static StringUdi GetUdiFromPath(string entityType, string path) { - ArgumentNullException.ThrowIfNull(entity); + string id = path.TrimStart(Constants.CharArrays.ForwardSlash).Replace("\\", "/"); - return new StringUdi(Constants.UdiEntityType.Language, entity.IsoCode).EnsureClosed(); - } - - /// - /// Gets the entity identifier of the entity. - /// - /// The entity. - /// - /// The entity identifier of the entity. - /// - public static Udi GetUdi(this IEntity entity) - { - ArgumentNullException.ThrowIfNull(entity); - - return entity switch - { - // Concrete types - EntityContainer container => container.GetUdi(), - Stylesheet stylesheet => stylesheet.GetUdi(), - Script script => script.GetUdi(), - // Content types - IContentType contentType => contentType.GetUdi(), - IMediaType mediaType => mediaType.GetUdi(), - IMemberType memberType => memberType.GetUdi(), - IContentTypeComposition contentTypeComposition => contentTypeComposition.GetUdi(), - // Content - IContent content => content.GetUdi(), - IMedia media => media.GetUdi(), - IMember member => member.GetUdi(), - IContentBase contentBase => contentBase.GetUdi(), - // Other - IDataType dataTypeComposition => dataTypeComposition.GetUdi(), - IDictionaryItem dictionaryItem => dictionaryItem.GetUdi(), - ILanguage language => language.GetUdi(), - IMemberGroup memberGroup => memberGroup.GetUdi(), - IPartialView partialView => partialView.GetUdi(), - IRelationType relationType => relationType.GetUdi(), - ITemplate template => template.GetUdi(), - IWebhook webhook => webhook.GetUdi(), - _ => throw new NotSupportedException(string.Format("Entity type {0} is not supported.", entity.GetType().FullName)), - }; + return new StringUdi(entityType, id).EnsureClosed(); } } diff --git a/tests/Umbraco.Tests.Common/Builders/ContentBuilder.cs b/tests/Umbraco.Tests.Common/Builders/ContentBuilder.cs index 05cc4b80dd..53c2f50f10 100644 --- a/tests/Umbraco.Tests.Common/Builders/ContentBuilder.cs +++ b/tests/Umbraco.Tests.Common/Builders/ContentBuilder.cs @@ -53,6 +53,7 @@ public class ContentBuilder private int? _sortOrder; private bool? _trashed; private DateTime? _updateDate; + private bool? _blueprint; private int? _versionId; DateTime? IWithCreateDateBuilder.CreateDate @@ -145,6 +146,13 @@ public class ContentBuilder set => _updateDate = value; } + public ContentBuilder WithBlueprint(bool blueprint) + { + _blueprint = blueprint; + + return this; + } + public ContentBuilder WithVersionId(int versionId) { _versionId = versionId; @@ -217,6 +225,7 @@ public class ContentBuilder { var id = _id ?? 0; var versionId = _versionId ?? 0; + var blueprint = _blueprint ?? false; var key = _key ?? Guid.NewGuid(); var parentId = _parentId ?? -1; var parent = _parent; @@ -253,6 +262,7 @@ public class ContentBuilder content.Id = id; content.VersionId = versionId; + content.Blueprint = blueprint; content.Key = key; content.CreateDate = createDate; content.UpdateDate = updateDate; diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Extensions/UdiGetterExtensionsTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Extensions/UdiGetterExtensionsTests.cs index b5da0a4f2f..f5a5e79234 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Extensions/UdiGetterExtensionsTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Extensions/UdiGetterExtensionsTests.cs @@ -3,6 +3,7 @@ using NUnit.Framework; using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Entities; using Umbraco.Cms.Tests.Common.Builders; using Umbraco.Cms.Tests.Common.Builders.Extensions; @@ -13,15 +14,22 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Extensions; [TestFixture] public class UdiGetterExtensionsTests { - [TestCase("style.css", "umb://stylesheet/style.css")] - [TestCase("editor\\style.css", "umb://stylesheet/editor/style.css")] - [TestCase("editor/style.css", "umb://stylesheet/editor/style.css")] - public void GetUdiForStylesheet(string path, string expected) + [TestCase(Constants.ObjectTypes.Strings.DataType, "6ad82c70-685c-4e04-9b36-d81bd779d16f", "umb://data-type-container/6ad82c70685c4e049b36d81bd779d16f")] + [TestCase(Constants.ObjectTypes.Strings.DocumentType, "6ad82c70-685c-4e04-9b36-d81bd779d16f", "umb://document-type-container/6ad82c70685c4e049b36d81bd779d16f")] + [TestCase(Constants.ObjectTypes.Strings.MediaType, "6ad82c70-685c-4e04-9b36-d81bd779d16f", "umb://media-type-container/6ad82c70685c4e049b36d81bd779d16f")] + [TestCase(Constants.ObjectTypes.Strings.DocumentBlueprint, "6ad82c70-685c-4e04-9b36-d81bd779d16f", "umb://document-blueprint-container/6ad82c70685c4e049b36d81bd779d16f")] + public void GetUdiForEntityContainer(Guid containedObjectType, Guid key, string expected) { - var builder = new StylesheetBuilder(); - var stylesheet = builder.WithPath(path).Build(); - var result = stylesheet.GetUdi(); - Assert.AreEqual(expected, result.ToString()); + EntityContainer entity = new EntityContainer(containedObjectType) + { + Key = key + }; + + Udi udi = entity.GetUdi(); + Assert.AreEqual(expected, udi.ToString()); + + udi = ((IEntity)entity).GetUdi(); + Assert.AreEqual(expected, udi.ToString()); } [TestCase("script.js", "umb://script/script.js")] @@ -29,10 +37,195 @@ public class UdiGetterExtensionsTests [TestCase("editor/script.js", "umb://script/editor/script.js")] public void GetUdiForScript(string path, string expected) { - var builder = new ScriptBuilder(); - var script = builder.WithPath(path).Build(); - var result = script.GetUdi(); - Assert.AreEqual(expected, result.ToString()); + Script entity = new ScriptBuilder() + .WithPath(path) + .Build(); + + Udi udi = entity.GetUdi(); + Assert.AreEqual(expected, udi.ToString()); + + udi = ((IEntity)entity).GetUdi(); + Assert.AreEqual(expected, udi.ToString()); + } + + [TestCase("style.css", "umb://stylesheet/style.css")] + [TestCase("editor\\style.css", "umb://stylesheet/editor/style.css")] + [TestCase("editor/style.css", "umb://stylesheet/editor/style.css")] + public void GetUdiForStylesheet(string path, string expected) + { + Stylesheet entity = new StylesheetBuilder() + .WithPath(path) + .Build(); + + Udi udi = entity.GetUdi(); + Assert.AreEqual(expected, udi.ToString()); + + udi = ((IEntity)entity).GetUdi(); + Assert.AreEqual(expected, udi.ToString()); + } + + [TestCase("6ad82c70-685c-4e04-9b36-d81bd779d16f", false, "umb://document/6ad82c70685c4e049b36d81bd779d16f")] + [TestCase("6ad82c70-685c-4e04-9b36-d81bd779d16f", true, "umb://document-blueprint/6ad82c70685c4e049b36d81bd779d16f")] + public void GetUdiForContent(Guid key, bool blueprint, string expected) + { + Content entity = new ContentBuilder() + .WithKey(key) + .WithBlueprint(blueprint) + .WithContentType(ContentTypeBuilder.CreateBasicContentType()) + .Build(); + + Udi udi = entity.GetUdi(); + Assert.AreEqual(expected, udi.ToString()); + + udi = ((IContentBase)entity).GetUdi(); + Assert.AreEqual(expected, udi.ToString()); + + udi = ((IEntity)entity).GetUdi(); + Assert.AreEqual(expected, udi.ToString()); + } + + [TestCase("6ad82c70-685c-4e04-9b36-d81bd779d16f", "umb://media/6ad82c70685c4e049b36d81bd779d16f")] + public void GetUdiForMedia(Guid key, string expected) + { + Media entity = new MediaBuilder() + .WithKey(key) + .WithMediaType(MediaTypeBuilder.CreateImageMediaType()) + .Build(); + + Udi udi = entity.GetUdi(); + Assert.AreEqual(expected, udi.ToString()); + + udi = ((IContentBase)entity).GetUdi(); + Assert.AreEqual(expected, udi.ToString()); + + udi = ((IEntity)entity).GetUdi(); + Assert.AreEqual(expected, udi.ToString()); + } + + [TestCase("6ad82c70-685c-4e04-9b36-d81bd779d16f", "umb://member/6ad82c70685c4e049b36d81bd779d16f")] + public void GetUdiForMember(Guid key, string expected) + { + Member entity = new MemberBuilder() + .WithKey(key) + .WithMemberType(MemberTypeBuilder.CreateSimpleMemberType()) + .Build(); + + Udi udi = entity.GetUdi(); + Assert.AreEqual(expected, udi.ToString()); + + udi = ((IContentBase)entity).GetUdi(); + Assert.AreEqual(expected, udi.ToString()); + + udi = ((IEntity)entity).GetUdi(); + Assert.AreEqual(expected, udi.ToString()); + } + + [TestCase("6ad82c70-685c-4e04-9b36-d81bd779d16f", "umb://document-type/6ad82c70685c4e049b36d81bd779d16f")] + public void GetUdiForContentType(Guid key, string expected) + { + IContentType entity = new ContentTypeBuilder() + .WithKey(key) + .Build(); + + Udi udi = entity.GetUdi(); + Assert.AreEqual(expected, udi.ToString()); + + udi = ((IContentTypeComposition)entity).GetUdi(); + Assert.AreEqual(expected, udi.ToString()); + + udi = ((IEntity)entity).GetUdi(); + Assert.AreEqual(expected, udi.ToString()); + } + + [TestCase("6ad82c70-685c-4e04-9b36-d81bd779d16f", "umb://media-type/6ad82c70685c4e049b36d81bd779d16f")] + public void GetUdiForMediaType(Guid key, string expected) + { + IMediaType entity = new MediaTypeBuilder() + .WithKey(key) + .Build(); + + Udi udi = entity.GetUdi(); + Assert.AreEqual(expected, udi.ToString()); + + udi = ((IContentTypeComposition)entity).GetUdi(); + Assert.AreEqual(expected, udi.ToString()); + + udi = ((IEntity)entity).GetUdi(); + Assert.AreEqual(expected, udi.ToString()); + } + + [TestCase("6ad82c70-685c-4e04-9b36-d81bd779d16f", "umb://member-type/6ad82c70685c4e049b36d81bd779d16f")] + public void GetUdiForMemberType(Guid key, string expected) + { + IMemberType entity = new MemberTypeBuilder() + .WithKey(key) + .Build(); + + Udi udi = entity.GetUdi(); + Assert.AreEqual(expected, udi.ToString()); + + udi = ((IContentTypeComposition)entity).GetUdi(); + Assert.AreEqual(expected, udi.ToString()); + + udi = ((IEntity)entity).GetUdi(); + Assert.AreEqual(expected, udi.ToString()); + } + + [TestCase("6ad82c70-685c-4e04-9b36-d81bd779d16f", "umb://data-type/6ad82c70685c4e049b36d81bd779d16f")] + public void GetUdiForDataType(Guid key, string expected) + { + DataType entity = new DataTypeBuilder() + .WithKey(key) + .Build(); + + Udi udi = entity.GetUdi(); + Assert.AreEqual(expected, udi.ToString()); + + udi = ((IEntity)entity).GetUdi(); + Assert.AreEqual(expected, udi.ToString()); + } + + [TestCase("6ad82c70-685c-4e04-9b36-d81bd779d16f", "umb://dictionary-item/6ad82c70685c4e049b36d81bd779d16f")] + public void GetUdiForDictionaryItem(Guid key, string expected) + { + DictionaryItem entity = new DictionaryItemBuilder() + .WithKey(key) + .Build(); + + Udi udi = entity.GetUdi(); + Assert.AreEqual(expected, udi.ToString()); + + udi = ((IEntity)entity).GetUdi(); + Assert.AreEqual(expected, udi.ToString()); + } + + [TestCase("en-US", "umb://language/en-US")] + [TestCase("en", "umb://language/en")] + public void GetUdiForLanguage(string isoCode, string expected) + { + ILanguage entity = new LanguageBuilder() + .WithCultureInfo(isoCode) + .Build(); + + Udi udi = entity.GetUdi(); + Assert.AreEqual(expected, udi.ToString()); + + udi = ((IEntity)entity).GetUdi(); + Assert.AreEqual(expected, udi.ToString()); + } + + [TestCase("6ad82c70-685c-4e04-9b36-d81bd779d16f", "umb://member-group/6ad82c70685c4e049b36d81bd779d16f")] + public void GetUdiForMemberGroup(Guid key, string expected) + { + MemberGroup entity = new MemberGroupBuilder() + .WithKey(key) + .Build(); + + Udi udi = entity.GetUdi(); + Assert.AreEqual(expected, udi.ToString()); + + udi = ((IEntity)entity).GetUdi(); + Assert.AreEqual(expected, udi.ToString()); } [TestCase("test.cshtml", "umb://partial-view/test.cshtml")] @@ -40,26 +233,53 @@ public class UdiGetterExtensionsTests [TestCase("editor/test.cshtml", "umb://partial-view/editor/test.cshtml")] public void GetUdiForPartialView(string path, string expected) { - var builder = new PartialViewBuilder(); - var partialView = builder + IPartialView entity = new PartialViewBuilder() .WithPath(path) .Build(); - var result = partialView.GetUdi(); - Assert.AreEqual(expected, result.ToString()); + + Udi udi = entity.GetUdi(); + Assert.AreEqual(expected, udi.ToString()); + } + + [TestCase("6ad82c70-685c-4e04-9b36-d81bd779d16f", "umb://relation-type/6ad82c70685c4e049b36d81bd779d16f")] + public void GetUdiForRelationType(Guid key, string expected) + { + IRelationTypeWithIsDependency entity = new RelationTypeBuilder() + .WithKey(key) + .Build(); + + Udi udi = entity.GetUdi(); + Assert.AreEqual(expected, udi.ToString()); + + udi = ((IEntity)entity).GetUdi(); + Assert.AreEqual(expected, udi.ToString()); + } + + [TestCase("6ad82c70-685c-4e04-9b36-d81bd779d16f", "umb://template/6ad82c70685c4e049b36d81bd779d16f")] + public void GetUdiForTemplate(Guid key, string expected) + { + ITemplate entity = new TemplateBuilder() + .WithKey(key) + .Build(); + + Udi udi = entity.GetUdi(); + Assert.AreEqual(expected, udi.ToString()); + + udi = ((IEntity)entity).GetUdi(); + Assert.AreEqual(expected, udi.ToString()); } [TestCase("6ad82c70-685c-4e04-9b36-d81bd779d16f", "umb://webhook/6ad82c70685c4e049b36d81bd779d16f")] - public void GetUdiForWebhook(string key, string expected) + public void GetUdiForWebhook(Guid key, string expected) { - var builder = new WebhookBuilder(); - var webhook = builder - .WithKey(Guid.Parse(key)) + Webhook entity = new WebhookBuilder() + .WithKey(key) .Build(); - Udi result = webhook.GetUdi(); - Assert.AreEqual(expected, result.ToString()); + Udi udi = entity.GetUdi(); + Assert.AreEqual(expected, udi.ToString()); - result = ((IEntity)webhook).GetUdi(); - Assert.AreEqual(expected, result.ToString()); + udi = ((IEntity)entity).GetUdi(); + Assert.AreEqual(expected, udi.ToString()); } } From a76af1de9db3444d0bbaf6b324d9cbfe6276e49a Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Thu, 26 Sep 2024 07:47:33 +0200 Subject: [PATCH 34/41] Fix `IContentBase.GetUdi()` extension method to support document-blueprint entity type (#16939) * Add tests for all UDI entity types * Fix IContentBase UDI entity type for blueprints * Remove redundant switch statements and reorder methods (cherry picked from commit 609b5f76d4c74e9a0ee9a7d1ffa62562ffbfaf23) --- .../Extensions/UdiGetterExtensions.cs | 424 +++++++++--------- .../Builders/ContentBuilder.cs | 10 + .../Extensions/UdiGetterExtensionsTests.cs | 268 ++++++++++- 3 files changed, 460 insertions(+), 242 deletions(-) diff --git a/src/Umbraco.Core/Extensions/UdiGetterExtensions.cs b/src/Umbraco.Core/Extensions/UdiGetterExtensions.cs index e4b11ccb6c..66c5002604 100644 --- a/src/Umbraco.Core/Extensions/UdiGetterExtensions.cs +++ b/src/Umbraco.Core/Extensions/UdiGetterExtensions.cs @@ -8,7 +8,7 @@ using Umbraco.Cms.Core.Models.Entities; namespace Umbraco.Extensions; /// -/// Provides extension methods that return udis for Umbraco entities. +/// Provides extension methods that return UDIs for Umbraco entities. /// public static class UdiGetterExtensions { @@ -19,11 +19,177 @@ public static class UdiGetterExtensions /// /// The entity identifier of the entity. /// - public static GuidUdi GetUdi(this ITemplate entity) + public static Udi GetUdi(this IEntity entity) { ArgumentNullException.ThrowIfNull(entity); - return new GuidUdi(Constants.UdiEntityType.Template, entity.Key).EnsureClosed(); + return entity switch + { + // Concrete types + EntityContainer container => container.GetUdi(), + Script script => script.GetUdi(), + Stylesheet stylesheet => stylesheet.GetUdi(), + // Interfaces + IContentBase contentBase => contentBase.GetUdi(), + IContentTypeComposition contentTypeComposition => contentTypeComposition.GetUdi(), + IDataType dataType => dataType.GetUdi(), + IDictionaryItem dictionaryItem => dictionaryItem.GetUdi(), + ILanguage language => language.GetUdi(), + IMemberGroup memberGroup => memberGroup.GetUdi(), + IPartialView partialView => partialView.GetUdi(), + IRelationType relationType => relationType.GetUdi(), + ITemplate template => template.GetUdi(), + IWebhook webhook => webhook.GetUdi(), + _ => throw new NotSupportedException($"Entity type {entity.GetType().FullName} is not supported."), + }; + } + + /// + /// Gets the entity identifier of the entity. + /// + /// The entity. + /// + /// The entity identifier of the entity. + /// + public static GuidUdi GetUdi(this EntityContainer entity) + { + ArgumentNullException.ThrowIfNull(entity); + + string entityType; + if (entity.ContainedObjectType == Constants.ObjectTypes.DataType) + { + entityType = Constants.UdiEntityType.DataTypeContainer; + } + else if (entity.ContainedObjectType == Constants.ObjectTypes.DocumentType) + { + entityType = Constants.UdiEntityType.DocumentTypeContainer; + } + else if (entity.ContainedObjectType == Constants.ObjectTypes.MediaType) + { + entityType = Constants.UdiEntityType.MediaTypeContainer; + } + else if (entity.ContainedObjectType == Constants.ObjectTypes.DocumentBlueprint) + { + entityType = Constants.UdiEntityType.DocumentBlueprintContainer; + } + else + { + throw new NotSupportedException($"Contained object type {entity.ContainedObjectType} is not supported."); + } + + return new GuidUdi(entityType, entity.Key).EnsureClosed(); + } + + /// + /// Gets the entity identifier of the entity. + /// + /// The entity. + /// + /// The entity identifier of the entity. + /// + public static StringUdi GetUdi(this Script entity) + { + ArgumentNullException.ThrowIfNull(entity); + + return GetUdiFromPath(Constants.UdiEntityType.Script, entity.Path); + } + + /// + /// Gets the entity identifier of the entity. + /// + /// The entity. + /// + /// The entity identifier of the entity. + /// + public static StringUdi GetUdi(this Stylesheet entity) + { + ArgumentNullException.ThrowIfNull(entity); + + return GetUdiFromPath(Constants.UdiEntityType.Stylesheet, entity.Path); + } + + /// + /// Gets the entity identifier of the entity. + /// + /// The entity. + /// + /// The entity identifier of the entity. + /// + public static GuidUdi GetUdi(this IContentBase entity) + { + ArgumentNullException.ThrowIfNull(entity); + + return entity switch + { + IContent content => content.GetUdi(), + IMedia media => media.GetUdi(), + IMember member => member.GetUdi(), + _ => throw new NotSupportedException($"Content base type {entity.GetType().FullName} is not supported."), + }; + } + + /// + /// Gets the entity identifier of the entity. + /// + /// The entity. + /// + /// The entity identifier of the entity. + /// + public static GuidUdi GetUdi(this IContent entity) + { + ArgumentNullException.ThrowIfNull(entity); + + string entityType = entity.Blueprint ? Constants.UdiEntityType.DocumentBlueprint : Constants.UdiEntityType.Document; + + return new GuidUdi(entityType, entity.Key).EnsureClosed(); + } + + /// + /// Gets the entity identifier of the entity. + /// + /// The entity. + /// + /// The entity identifier of the entity. + /// + public static GuidUdi GetUdi(this IMedia entity) + { + ArgumentNullException.ThrowIfNull(entity); + + return new GuidUdi(Constants.UdiEntityType.Media, entity.Key).EnsureClosed(); + } + + /// + /// Gets the entity identifier of the entity. + /// + /// The entity. + /// + /// The entity identifier of the entity. + /// + public static GuidUdi GetUdi(this IMember entity) + { + ArgumentNullException.ThrowIfNull(entity); + + return new GuidUdi(Constants.UdiEntityType.Member, entity.Key).EnsureClosed(); + } + + /// + /// Gets the entity identifier of the entity. + /// + /// The entity. + /// + /// The entity identifier of the entity. + /// + public static GuidUdi GetUdi(this IContentTypeComposition entity) + { + ArgumentNullException.ThrowIfNull(entity); + + return entity switch + { + IContentType contentType => contentType.GetUdi(), + IMediaType mediaType => mediaType.GetUdi(), + IMemberType memberType => memberType.GetUdi(), + _ => throw new NotSupportedException($"Composition type {entity.GetType().FullName} is not supported."), + }; } /// @@ -68,42 +234,6 @@ public static class UdiGetterExtensions return new GuidUdi(Constants.UdiEntityType.MemberType, entity.Key).EnsureClosed(); } - /// - /// Gets the entity identifier of the entity. - /// - /// The entity. - /// - /// The entity identifier of the entity. - /// - public static GuidUdi GetUdi(this IMemberGroup entity) - { - ArgumentNullException.ThrowIfNull(entity); - - return new GuidUdi(Constants.UdiEntityType.MemberGroup, entity.Key).EnsureClosed(); - } - - /// - /// Gets the entity identifier of the entity. - /// - /// The entity. - /// - /// The entity identifier of the entity. - /// - public static GuidUdi GetUdi(this IContentTypeComposition entity) - { - ArgumentNullException.ThrowIfNull(entity); - - string entityType = entity switch - { - IContentType => Constants.UdiEntityType.DocumentType, - IMediaType => Constants.UdiEntityType.MediaType, - IMemberType => Constants.UdiEntityType.MemberType, - _ => throw new NotSupportedException(string.Format("Composition type {0} is not supported.", entity.GetType().FullName)), - }; - - return new GuidUdi(entityType, entity.Key).EnsureClosed(); - } - /// /// Gets the entity identifier of the entity. /// @@ -118,129 +248,6 @@ public static class UdiGetterExtensions return new GuidUdi(Constants.UdiEntityType.DataType, entity.Key).EnsureClosed(); } - /// - /// Gets the entity identifier of the entity. - /// - /// The entity. - /// - /// The entity identifier of the entity. - /// - public static GuidUdi GetUdi(this EntityContainer entity) - { - ArgumentNullException.ThrowIfNull(entity); - - string entityType; - if (entity.ContainedObjectType == Constants.ObjectTypes.DataType) - { - entityType = Constants.UdiEntityType.DataTypeContainer; - } - else if (entity.ContainedObjectType == Constants.ObjectTypes.DocumentType) - { - entityType = Constants.UdiEntityType.DocumentTypeContainer; - } - else if (entity.ContainedObjectType == Constants.ObjectTypes.MediaType) - { - entityType = Constants.UdiEntityType.MediaTypeContainer; - } - else if (entity.ContainedObjectType == Constants.ObjectTypes.DocumentBlueprint) - { - entityType = Constants.UdiEntityType.DocumentBlueprintContainer; - } - else - { - throw new NotSupportedException(string.Format("Contained object type {0} is not supported.", entity.ContainedObjectType)); - } - - return new GuidUdi(entityType, entity.Key).EnsureClosed(); - } - - /// - /// Gets the entity identifier of the entity. - /// - /// The entity. - /// - /// The entity identifier of the entity. - /// - public static GuidUdi GetUdi(this IMedia entity) - { - ArgumentNullException.ThrowIfNull(entity); - - return new GuidUdi(Constants.UdiEntityType.Media, entity.Key).EnsureClosed(); - } - - /// - /// Gets the entity identifier of the entity. - /// - /// The entity. - /// - /// The entity identifier of the entity. - /// - public static GuidUdi GetUdi(this IContent entity) - { - ArgumentNullException.ThrowIfNull(entity); - - string entityType = entity.Blueprint ? Constants.UdiEntityType.DocumentBlueprint : Constants.UdiEntityType.Document; - - return new GuidUdi(entityType, entity.Key).EnsureClosed(); - } - - /// - /// Gets the entity identifier of the entity. - /// - /// The entity. - /// - /// The entity identifier of the entity. - /// - public static GuidUdi GetUdi(this IMember entity) - { - ArgumentNullException.ThrowIfNull(entity); - - return new GuidUdi(Constants.UdiEntityType.Member, entity.Key).EnsureClosed(); - } - - /// - /// Gets the entity identifier of the entity. - /// - /// The entity. - /// - /// The entity identifier of the entity. - /// - public static StringUdi GetUdi(this Stylesheet entity) - { - ArgumentNullException.ThrowIfNull(entity); - - return GetUdiFromPath(Constants.UdiEntityType.Stylesheet, entity.Path); - } - - /// - /// Gets the entity identifier of the entity. - /// - /// The entity. - /// - /// The entity identifier of the entity. - /// - public static StringUdi GetUdi(this Script entity) - { - ArgumentNullException.ThrowIfNull(entity); - - return GetUdiFromPath(Constants.UdiEntityType.Script, entity.Path); - } - - /// - /// Gets the UDI from a path. - /// - /// The type of the entity. - /// The path. - /// - /// The entity identifier of the entity. - /// - private static StringUdi GetUdiFromPath(string entityType, string path) - { - string id = path.TrimStart(Constants.CharArrays.ForwardSlash).Replace("\\", "/"); - - return new StringUdi(entityType, id).EnsureClosed(); - } - /// /// Gets the entity identifier of the entity. /// @@ -262,11 +269,11 @@ public static class UdiGetterExtensions /// /// The entity identifier of the entity. /// - public static StringUdi GetUdi(this IPartialView entity) + public static StringUdi GetUdi(this ILanguage entity) { ArgumentNullException.ThrowIfNull(entity); - return GetUdiFromPath(Constants.UdiEntityType.PartialView, entity.Path); + return new StringUdi(Constants.UdiEntityType.Language, entity.IsoCode).EnsureClosed(); } /// @@ -276,19 +283,25 @@ public static class UdiGetterExtensions /// /// The entity identifier of the entity. /// - public static GuidUdi GetUdi(this IContentBase entity) + public static GuidUdi GetUdi(this IMemberGroup entity) { ArgumentNullException.ThrowIfNull(entity); - string type = entity switch - { - IContent => Constants.UdiEntityType.Document, - IMedia => Constants.UdiEntityType.Media, - IMember => Constants.UdiEntityType.Member, - _ => throw new NotSupportedException(string.Format("Content base type {0} is not supported.", entity.GetType().FullName)), - }; + return new GuidUdi(Constants.UdiEntityType.MemberGroup, entity.Key).EnsureClosed(); + } - return new GuidUdi(type, entity.Key).EnsureClosed(); + /// + /// Gets the entity identifier of the entity. + /// + /// The entity. + /// + /// The entity identifier of the entity. + /// + public static StringUdi GetUdi(this IPartialView entity) + { + ArgumentNullException.ThrowIfNull(entity); + + return GetUdiFromPath(Constants.UdiEntityType.PartialView, entity.Path); } /// @@ -305,6 +318,20 @@ public static class UdiGetterExtensions return new GuidUdi(Constants.UdiEntityType.RelationType, entity.Key).EnsureClosed(); } + /// + /// Gets the entity identifier of the entity. + /// + /// The entity. + /// + /// The entity identifier of the entity. + /// + public static GuidUdi GetUdi(this ITemplate entity) + { + ArgumentNullException.ThrowIfNull(entity); + + return new GuidUdi(Constants.UdiEntityType.Template, entity.Key).EnsureClosed(); + } + /// /// Gets the entity identifier of the entity. /// @@ -320,56 +347,17 @@ public static class UdiGetterExtensions } /// - /// Gets the entity identifier of the entity. + /// Gets the UDI from a path. /// - /// The entity. + /// The type of the entity. + /// The path. /// /// The entity identifier of the entity. /// - public static StringUdi GetUdi(this ILanguage entity) + private static StringUdi GetUdiFromPath(string entityType, string path) { - ArgumentNullException.ThrowIfNull(entity); + string id = path.TrimStart(Constants.CharArrays.ForwardSlash).Replace("\\", "/"); - return new StringUdi(Constants.UdiEntityType.Language, entity.IsoCode).EnsureClosed(); - } - - /// - /// Gets the entity identifier of the entity. - /// - /// The entity. - /// - /// The entity identifier of the entity. - /// - public static Udi GetUdi(this IEntity entity) - { - ArgumentNullException.ThrowIfNull(entity); - - return entity switch - { - // Concrete types - EntityContainer container => container.GetUdi(), - Stylesheet stylesheet => stylesheet.GetUdi(), - Script script => script.GetUdi(), - // Content types - IContentType contentType => contentType.GetUdi(), - IMediaType mediaType => mediaType.GetUdi(), - IMemberType memberType => memberType.GetUdi(), - IContentTypeComposition contentTypeComposition => contentTypeComposition.GetUdi(), - // Content - IContent content => content.GetUdi(), - IMedia media => media.GetUdi(), - IMember member => member.GetUdi(), - IContentBase contentBase => contentBase.GetUdi(), - // Other - IDataType dataTypeComposition => dataTypeComposition.GetUdi(), - IDictionaryItem dictionaryItem => dictionaryItem.GetUdi(), - ILanguage language => language.GetUdi(), - IMemberGroup memberGroup => memberGroup.GetUdi(), - IPartialView partialView => partialView.GetUdi(), - IRelationType relationType => relationType.GetUdi(), - ITemplate template => template.GetUdi(), - IWebhook webhook => webhook.GetUdi(), - _ => throw new NotSupportedException(string.Format("Entity type {0} is not supported.", entity.GetType().FullName)), - }; + return new StringUdi(entityType, id).EnsureClosed(); } } diff --git a/tests/Umbraco.Tests.Common/Builders/ContentBuilder.cs b/tests/Umbraco.Tests.Common/Builders/ContentBuilder.cs index 05cc4b80dd..53c2f50f10 100644 --- a/tests/Umbraco.Tests.Common/Builders/ContentBuilder.cs +++ b/tests/Umbraco.Tests.Common/Builders/ContentBuilder.cs @@ -53,6 +53,7 @@ public class ContentBuilder private int? _sortOrder; private bool? _trashed; private DateTime? _updateDate; + private bool? _blueprint; private int? _versionId; DateTime? IWithCreateDateBuilder.CreateDate @@ -145,6 +146,13 @@ public class ContentBuilder set => _updateDate = value; } + public ContentBuilder WithBlueprint(bool blueprint) + { + _blueprint = blueprint; + + return this; + } + public ContentBuilder WithVersionId(int versionId) { _versionId = versionId; @@ -217,6 +225,7 @@ public class ContentBuilder { var id = _id ?? 0; var versionId = _versionId ?? 0; + var blueprint = _blueprint ?? false; var key = _key ?? Guid.NewGuid(); var parentId = _parentId ?? -1; var parent = _parent; @@ -253,6 +262,7 @@ public class ContentBuilder content.Id = id; content.VersionId = versionId; + content.Blueprint = blueprint; content.Key = key; content.CreateDate = createDate; content.UpdateDate = updateDate; diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Extensions/UdiGetterExtensionsTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Extensions/UdiGetterExtensionsTests.cs index b5da0a4f2f..f5a5e79234 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Extensions/UdiGetterExtensionsTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Extensions/UdiGetterExtensionsTests.cs @@ -3,6 +3,7 @@ using NUnit.Framework; using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Entities; using Umbraco.Cms.Tests.Common.Builders; using Umbraco.Cms.Tests.Common.Builders.Extensions; @@ -13,15 +14,22 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Extensions; [TestFixture] public class UdiGetterExtensionsTests { - [TestCase("style.css", "umb://stylesheet/style.css")] - [TestCase("editor\\style.css", "umb://stylesheet/editor/style.css")] - [TestCase("editor/style.css", "umb://stylesheet/editor/style.css")] - public void GetUdiForStylesheet(string path, string expected) + [TestCase(Constants.ObjectTypes.Strings.DataType, "6ad82c70-685c-4e04-9b36-d81bd779d16f", "umb://data-type-container/6ad82c70685c4e049b36d81bd779d16f")] + [TestCase(Constants.ObjectTypes.Strings.DocumentType, "6ad82c70-685c-4e04-9b36-d81bd779d16f", "umb://document-type-container/6ad82c70685c4e049b36d81bd779d16f")] + [TestCase(Constants.ObjectTypes.Strings.MediaType, "6ad82c70-685c-4e04-9b36-d81bd779d16f", "umb://media-type-container/6ad82c70685c4e049b36d81bd779d16f")] + [TestCase(Constants.ObjectTypes.Strings.DocumentBlueprint, "6ad82c70-685c-4e04-9b36-d81bd779d16f", "umb://document-blueprint-container/6ad82c70685c4e049b36d81bd779d16f")] + public void GetUdiForEntityContainer(Guid containedObjectType, Guid key, string expected) { - var builder = new StylesheetBuilder(); - var stylesheet = builder.WithPath(path).Build(); - var result = stylesheet.GetUdi(); - Assert.AreEqual(expected, result.ToString()); + EntityContainer entity = new EntityContainer(containedObjectType) + { + Key = key + }; + + Udi udi = entity.GetUdi(); + Assert.AreEqual(expected, udi.ToString()); + + udi = ((IEntity)entity).GetUdi(); + Assert.AreEqual(expected, udi.ToString()); } [TestCase("script.js", "umb://script/script.js")] @@ -29,10 +37,195 @@ public class UdiGetterExtensionsTests [TestCase("editor/script.js", "umb://script/editor/script.js")] public void GetUdiForScript(string path, string expected) { - var builder = new ScriptBuilder(); - var script = builder.WithPath(path).Build(); - var result = script.GetUdi(); - Assert.AreEqual(expected, result.ToString()); + Script entity = new ScriptBuilder() + .WithPath(path) + .Build(); + + Udi udi = entity.GetUdi(); + Assert.AreEqual(expected, udi.ToString()); + + udi = ((IEntity)entity).GetUdi(); + Assert.AreEqual(expected, udi.ToString()); + } + + [TestCase("style.css", "umb://stylesheet/style.css")] + [TestCase("editor\\style.css", "umb://stylesheet/editor/style.css")] + [TestCase("editor/style.css", "umb://stylesheet/editor/style.css")] + public void GetUdiForStylesheet(string path, string expected) + { + Stylesheet entity = new StylesheetBuilder() + .WithPath(path) + .Build(); + + Udi udi = entity.GetUdi(); + Assert.AreEqual(expected, udi.ToString()); + + udi = ((IEntity)entity).GetUdi(); + Assert.AreEqual(expected, udi.ToString()); + } + + [TestCase("6ad82c70-685c-4e04-9b36-d81bd779d16f", false, "umb://document/6ad82c70685c4e049b36d81bd779d16f")] + [TestCase("6ad82c70-685c-4e04-9b36-d81bd779d16f", true, "umb://document-blueprint/6ad82c70685c4e049b36d81bd779d16f")] + public void GetUdiForContent(Guid key, bool blueprint, string expected) + { + Content entity = new ContentBuilder() + .WithKey(key) + .WithBlueprint(blueprint) + .WithContentType(ContentTypeBuilder.CreateBasicContentType()) + .Build(); + + Udi udi = entity.GetUdi(); + Assert.AreEqual(expected, udi.ToString()); + + udi = ((IContentBase)entity).GetUdi(); + Assert.AreEqual(expected, udi.ToString()); + + udi = ((IEntity)entity).GetUdi(); + Assert.AreEqual(expected, udi.ToString()); + } + + [TestCase("6ad82c70-685c-4e04-9b36-d81bd779d16f", "umb://media/6ad82c70685c4e049b36d81bd779d16f")] + public void GetUdiForMedia(Guid key, string expected) + { + Media entity = new MediaBuilder() + .WithKey(key) + .WithMediaType(MediaTypeBuilder.CreateImageMediaType()) + .Build(); + + Udi udi = entity.GetUdi(); + Assert.AreEqual(expected, udi.ToString()); + + udi = ((IContentBase)entity).GetUdi(); + Assert.AreEqual(expected, udi.ToString()); + + udi = ((IEntity)entity).GetUdi(); + Assert.AreEqual(expected, udi.ToString()); + } + + [TestCase("6ad82c70-685c-4e04-9b36-d81bd779d16f", "umb://member/6ad82c70685c4e049b36d81bd779d16f")] + public void GetUdiForMember(Guid key, string expected) + { + Member entity = new MemberBuilder() + .WithKey(key) + .WithMemberType(MemberTypeBuilder.CreateSimpleMemberType()) + .Build(); + + Udi udi = entity.GetUdi(); + Assert.AreEqual(expected, udi.ToString()); + + udi = ((IContentBase)entity).GetUdi(); + Assert.AreEqual(expected, udi.ToString()); + + udi = ((IEntity)entity).GetUdi(); + Assert.AreEqual(expected, udi.ToString()); + } + + [TestCase("6ad82c70-685c-4e04-9b36-d81bd779d16f", "umb://document-type/6ad82c70685c4e049b36d81bd779d16f")] + public void GetUdiForContentType(Guid key, string expected) + { + IContentType entity = new ContentTypeBuilder() + .WithKey(key) + .Build(); + + Udi udi = entity.GetUdi(); + Assert.AreEqual(expected, udi.ToString()); + + udi = ((IContentTypeComposition)entity).GetUdi(); + Assert.AreEqual(expected, udi.ToString()); + + udi = ((IEntity)entity).GetUdi(); + Assert.AreEqual(expected, udi.ToString()); + } + + [TestCase("6ad82c70-685c-4e04-9b36-d81bd779d16f", "umb://media-type/6ad82c70685c4e049b36d81bd779d16f")] + public void GetUdiForMediaType(Guid key, string expected) + { + IMediaType entity = new MediaTypeBuilder() + .WithKey(key) + .Build(); + + Udi udi = entity.GetUdi(); + Assert.AreEqual(expected, udi.ToString()); + + udi = ((IContentTypeComposition)entity).GetUdi(); + Assert.AreEqual(expected, udi.ToString()); + + udi = ((IEntity)entity).GetUdi(); + Assert.AreEqual(expected, udi.ToString()); + } + + [TestCase("6ad82c70-685c-4e04-9b36-d81bd779d16f", "umb://member-type/6ad82c70685c4e049b36d81bd779d16f")] + public void GetUdiForMemberType(Guid key, string expected) + { + IMemberType entity = new MemberTypeBuilder() + .WithKey(key) + .Build(); + + Udi udi = entity.GetUdi(); + Assert.AreEqual(expected, udi.ToString()); + + udi = ((IContentTypeComposition)entity).GetUdi(); + Assert.AreEqual(expected, udi.ToString()); + + udi = ((IEntity)entity).GetUdi(); + Assert.AreEqual(expected, udi.ToString()); + } + + [TestCase("6ad82c70-685c-4e04-9b36-d81bd779d16f", "umb://data-type/6ad82c70685c4e049b36d81bd779d16f")] + public void GetUdiForDataType(Guid key, string expected) + { + DataType entity = new DataTypeBuilder() + .WithKey(key) + .Build(); + + Udi udi = entity.GetUdi(); + Assert.AreEqual(expected, udi.ToString()); + + udi = ((IEntity)entity).GetUdi(); + Assert.AreEqual(expected, udi.ToString()); + } + + [TestCase("6ad82c70-685c-4e04-9b36-d81bd779d16f", "umb://dictionary-item/6ad82c70685c4e049b36d81bd779d16f")] + public void GetUdiForDictionaryItem(Guid key, string expected) + { + DictionaryItem entity = new DictionaryItemBuilder() + .WithKey(key) + .Build(); + + Udi udi = entity.GetUdi(); + Assert.AreEqual(expected, udi.ToString()); + + udi = ((IEntity)entity).GetUdi(); + Assert.AreEqual(expected, udi.ToString()); + } + + [TestCase("en-US", "umb://language/en-US")] + [TestCase("en", "umb://language/en")] + public void GetUdiForLanguage(string isoCode, string expected) + { + ILanguage entity = new LanguageBuilder() + .WithCultureInfo(isoCode) + .Build(); + + Udi udi = entity.GetUdi(); + Assert.AreEqual(expected, udi.ToString()); + + udi = ((IEntity)entity).GetUdi(); + Assert.AreEqual(expected, udi.ToString()); + } + + [TestCase("6ad82c70-685c-4e04-9b36-d81bd779d16f", "umb://member-group/6ad82c70685c4e049b36d81bd779d16f")] + public void GetUdiForMemberGroup(Guid key, string expected) + { + MemberGroup entity = new MemberGroupBuilder() + .WithKey(key) + .Build(); + + Udi udi = entity.GetUdi(); + Assert.AreEqual(expected, udi.ToString()); + + udi = ((IEntity)entity).GetUdi(); + Assert.AreEqual(expected, udi.ToString()); } [TestCase("test.cshtml", "umb://partial-view/test.cshtml")] @@ -40,26 +233,53 @@ public class UdiGetterExtensionsTests [TestCase("editor/test.cshtml", "umb://partial-view/editor/test.cshtml")] public void GetUdiForPartialView(string path, string expected) { - var builder = new PartialViewBuilder(); - var partialView = builder + IPartialView entity = new PartialViewBuilder() .WithPath(path) .Build(); - var result = partialView.GetUdi(); - Assert.AreEqual(expected, result.ToString()); + + Udi udi = entity.GetUdi(); + Assert.AreEqual(expected, udi.ToString()); + } + + [TestCase("6ad82c70-685c-4e04-9b36-d81bd779d16f", "umb://relation-type/6ad82c70685c4e049b36d81bd779d16f")] + public void GetUdiForRelationType(Guid key, string expected) + { + IRelationTypeWithIsDependency entity = new RelationTypeBuilder() + .WithKey(key) + .Build(); + + Udi udi = entity.GetUdi(); + Assert.AreEqual(expected, udi.ToString()); + + udi = ((IEntity)entity).GetUdi(); + Assert.AreEqual(expected, udi.ToString()); + } + + [TestCase("6ad82c70-685c-4e04-9b36-d81bd779d16f", "umb://template/6ad82c70685c4e049b36d81bd779d16f")] + public void GetUdiForTemplate(Guid key, string expected) + { + ITemplate entity = new TemplateBuilder() + .WithKey(key) + .Build(); + + Udi udi = entity.GetUdi(); + Assert.AreEqual(expected, udi.ToString()); + + udi = ((IEntity)entity).GetUdi(); + Assert.AreEqual(expected, udi.ToString()); } [TestCase("6ad82c70-685c-4e04-9b36-d81bd779d16f", "umb://webhook/6ad82c70685c4e049b36d81bd779d16f")] - public void GetUdiForWebhook(string key, string expected) + public void GetUdiForWebhook(Guid key, string expected) { - var builder = new WebhookBuilder(); - var webhook = builder - .WithKey(Guid.Parse(key)) + Webhook entity = new WebhookBuilder() + .WithKey(key) .Build(); - Udi result = webhook.GetUdi(); - Assert.AreEqual(expected, result.ToString()); + Udi udi = entity.GetUdi(); + Assert.AreEqual(expected, udi.ToString()); - result = ((IEntity)webhook).GetUdi(); - Assert.AreEqual(expected, result.ToString()); + udi = ((IEntity)entity).GetUdi(); + Assert.AreEqual(expected, udi.ToString()); } } From 14a0e622781278ec6212c4daebd873de04677df4 Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Thu, 26 Sep 2024 07:51:16 +0200 Subject: [PATCH 35/41] Use version of the assembly with the same name as the package ID (#16544) --- .../Services/Implement/PackagingService.cs | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/Umbraco.Infrastructure/Services/Implement/PackagingService.cs b/src/Umbraco.Infrastructure/Services/Implement/PackagingService.cs index c4e152e0b5..53d6c8ba6c 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/PackagingService.cs +++ b/src/Umbraco.Infrastructure/Services/Implement/PackagingService.cs @@ -1,3 +1,6 @@ +using System.Diagnostics.CodeAnalysis; +using System.Reflection; +using System.Runtime.Loader; using System.Xml.Linq; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.FileProviders; @@ -354,8 +357,16 @@ public class PackagingService : IPackagingService if (!string.IsNullOrEmpty(packageManifest.Version)) { + // Always use package version from manifest installedPackage.Version = packageManifest.Version; } + else if (string.IsNullOrEmpty(installedPackage.Version) && + string.IsNullOrEmpty(installedPackage.PackageId) is false && + TryGetAssemblyInformationalVersion(installedPackage.PackageId, out string? version)) + { + // Use version of the assembly with the same name as the package ID + installedPackage.Version = version; + } } // Return all packages with an ID or name in the package manifest or package migrations @@ -414,4 +425,20 @@ public class PackagingService : IPackagingService return packageFile.CreateReadStream(); } + + private static bool TryGetAssemblyInformationalVersion(string name, [NotNullWhen(true)] out string? version) + { + foreach (Assembly assembly in AssemblyLoadContext.Default.Assemblies) + { + AssemblyName assemblyName = assembly.GetName(); + if (string.Equals(assemblyName.Name, name, StringComparison.OrdinalIgnoreCase) && + assembly.TryGetInformationalVersion(out version)) + { + return true; + } + } + + version = null; + return false; + } } From 635d9b83f9e1837cd9a640a024b54bae2bdf9644 Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Thu, 26 Sep 2024 07:51:16 +0200 Subject: [PATCH 36/41] Use version of the assembly with the same name as the package ID (#16544) (cherry picked from commit 14a0e622781278ec6212c4daebd873de04677df4) --- .../Services/Implement/PackagingService.cs | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/Umbraco.Infrastructure/Services/Implement/PackagingService.cs b/src/Umbraco.Infrastructure/Services/Implement/PackagingService.cs index c4e152e0b5..53d6c8ba6c 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/PackagingService.cs +++ b/src/Umbraco.Infrastructure/Services/Implement/PackagingService.cs @@ -1,3 +1,6 @@ +using System.Diagnostics.CodeAnalysis; +using System.Reflection; +using System.Runtime.Loader; using System.Xml.Linq; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.FileProviders; @@ -354,8 +357,16 @@ public class PackagingService : IPackagingService if (!string.IsNullOrEmpty(packageManifest.Version)) { + // Always use package version from manifest installedPackage.Version = packageManifest.Version; } + else if (string.IsNullOrEmpty(installedPackage.Version) && + string.IsNullOrEmpty(installedPackage.PackageId) is false && + TryGetAssemblyInformationalVersion(installedPackage.PackageId, out string? version)) + { + // Use version of the assembly with the same name as the package ID + installedPackage.Version = version; + } } // Return all packages with an ID or name in the package manifest or package migrations @@ -414,4 +425,20 @@ public class PackagingService : IPackagingService return packageFile.CreateReadStream(); } + + private static bool TryGetAssemblyInformationalVersion(string name, [NotNullWhen(true)] out string? version) + { + foreach (Assembly assembly in AssemblyLoadContext.Default.Assemblies) + { + AssemblyName assemblyName = assembly.GetName(); + if (string.Equals(assemblyName.Name, name, StringComparison.OrdinalIgnoreCase) && + assembly.TryGetInformationalVersion(out version)) + { + return true; + } + } + + version = null; + return false; + } } From 0c1daa290b677351d37e647afd4170e91dfa256c Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Thu, 26 Sep 2024 07:52:39 +0200 Subject: [PATCH 37/41] Add `RemoveDefault()` extension method to fluent API for CMS webhook events (#15424) * Add RemoveDefault extension method * Move default webhook event types to single list (cherry picked from commit 8f26263178656f092972e845e332962e9e158f1e) --- ...hookEventCollectionBuilderCmsExtensions.cs | 33 +++++++++++++++---- 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Core/Webhooks/WebhookEventCollectionBuilderCmsExtensions.cs b/src/Umbraco.Core/Webhooks/WebhookEventCollectionBuilderCmsExtensions.cs index 361891de6a..679e105b72 100644 --- a/src/Umbraco.Core/Webhooks/WebhookEventCollectionBuilderCmsExtensions.cs +++ b/src/Umbraco.Core/Webhooks/WebhookEventCollectionBuilderCmsExtensions.cs @@ -9,6 +9,15 @@ namespace Umbraco.Cms.Core.DependencyInjection; /// public static class WebhookEventCollectionBuilderCmsExtensions { + private static readonly Type[] _defaultTypes = + [ + typeof(ContentDeletedWebhookEvent), + typeof(ContentPublishedWebhookEvent), + typeof(ContentUnpublishedWebhookEvent), + typeof(MediaDeletedWebhookEvent), + typeof(MediaSavedWebhookEvent), + ]; + /// /// Adds the default webhook events. /// @@ -21,12 +30,24 @@ public static class WebhookEventCollectionBuilderCmsExtensions /// public static WebhookEventCollectionBuilderCms AddDefault(this WebhookEventCollectionBuilderCms builder) { - builder.Builder - .Add() - .Add() - .Add() - .Add() - .Add(); + builder.Builder.Add(_defaultTypes); + + return builder; + } + + /// + /// Removes the default webhook events. + /// + /// The builder. + /// + /// The builder. + /// + public static WebhookEventCollectionBuilderCms RemoveDefault(this WebhookEventCollectionBuilderCms builder) + { + foreach (Type type in _defaultTypes) + { + builder.Builder.Remove(type); + } return builder; } From 910d70302e695e0864296ef420a5a2ff77549c16 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Thu, 26 Sep 2024 08:45:08 +0200 Subject: [PATCH 38/41] Move all V14 User and User Group migration to pre-migrations (#17130) --- .../Migrations/Upgrade/UmbracoPlan.cs | 8 ++++---- .../Upgrade/UmbracoPremigrationPlan.cs | 4 ++++ .../Upgrade/V_14_0_0/AddGuidsToUserGroups.cs | 17 ++++++----------- .../Upgrade/V_14_0_0/AddGuidsToUsers.cs | 11 ++++++----- 4 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs index 7d6537d4da..713168a6c7 100644 --- a/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs +++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs @@ -67,15 +67,15 @@ public class UmbracoPlan : MigrationPlan // To 14.0.0 To("{419827A0-4FCE-464B-A8F3-247C6092AF55}"); - To("{69E12556-D9B3-493A-8E8A-65EC89FB658D}"); - To("{F2B16CD4-F181-4BEE-81C9-11CF384E6025}"); - To("{A8E01644-9F2E-4988-8341-587EF5B7EA69}"); + To("{69E12556-D9B3-493A-8E8A-65EC89FB658D}"); + To("{F2B16CD4-F181-4BEE-81C9-11CF384E6025}"); + To("{A8E01644-9F2E-4988-8341-587EF5B7EA69}"); To("{E073DBC0-9E8E-4C92-8210-9CB18364F46E}"); To("{80D282A4-5497-47FF-991F-BC0BCE603121}"); To("{96525697-E9DC-4198-B136-25AD033442B8}"); To("{7FC5AC9B-6F56-415B-913E-4A900629B853}"); To("{1539A010-2EB5-4163-8518-4AE2AA98AFC6}"); - To("{C567DE81-DF92-4B99-BEA8-CD34EF99DA5D}"); + To("{C567DE81-DF92-4B99-BEA8-CD34EF99DA5D}"); To("{0D82C836-96DD-480D-A924-7964E458BD34}"); To("{1A0FBC8A-6FC6-456C-805C-B94816B2E570}"); To("{302DE171-6D83-4B6B-B3C0-AC8808A16CA1}"); diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPremigrationPlan.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPremigrationPlan.cs index c9d23acb90..a8131a0da4 100644 --- a/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPremigrationPlan.cs +++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPremigrationPlan.cs @@ -53,5 +53,9 @@ public class UmbracoPremigrationPlan : MigrationPlan // To 14.0.0 To("{76FBF80E-37E6-462E-ADC1-25668F56151D}"); + To("{37CF4AC3-8489-44BC-A7E8-64908FEEC656}"); + To("{7BCB5352-B2ED-4D4B-B27D-ECDED930B50A}"); + To("{3E69BF9B-BEAB-41B1-BB11-15383CCA1C7F}"); + To("{F12C609B-86B9-4386-AFA4-78E02857247C}"); } } diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_14_0_0/AddGuidsToUserGroups.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_14_0_0/AddGuidsToUserGroups.cs index ad8e5091ea..67bdaf7395 100644 --- a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_14_0_0/AddGuidsToUserGroups.cs +++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_14_0_0/AddGuidsToUserGroups.cs @@ -20,6 +20,12 @@ public class AddGuidsToUserGroups : UnscopedMigrationBase protected override void Migrate() { + // If the new column already exists we'll do nothing. + if (ColumnExists(Constants.DatabaseSchema.Tables.UserGroup, NewColumnName)) + { + return; + } + // SQL server can simply add the column, but for SQLite this won't work, // so we'll have to create a new table and copy over data. if (DatabaseType != DatabaseType.SQLite) @@ -37,11 +43,6 @@ public class AddGuidsToUserGroups : UnscopedMigrationBase using IDisposable notificationSuppression = scope.Notifications.Suppress(); ScopeDatabase(scope); - if (ColumnExists(Constants.DatabaseSchema.Tables.UserGroup, NewColumnName)) - { - return; - } - var columns = SqlSyntax.GetColumnsInSchema(Context.Database).ToList(); AddColumnIfNotExists(columns, NewColumnName); @@ -68,12 +69,6 @@ public class AddGuidsToUserGroups : UnscopedMigrationBase using IDisposable notificationSuppression = scope.Notifications.Suppress(); ScopeDatabase(scope); - // If the new column already exists we'll do nothing. - if (ColumnExists(Constants.DatabaseSchema.Tables.UserGroup, NewColumnName)) - { - return; - } - // This isn't pretty, // But since you cannot alter columns, we have to copy the data over and delete the old table. // However we cannot do this due to foreign keys, so temporarily disable these keys while migrating. diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_14_0_0/AddGuidsToUsers.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_14_0_0/AddGuidsToUsers.cs index fe730fd2b8..6d5dbce1d8 100644 --- a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_14_0_0/AddGuidsToUsers.cs +++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_14_0_0/AddGuidsToUsers.cs @@ -26,6 +26,12 @@ internal class AddGuidsToUsers : UnscopedMigrationBase protected override void Migrate() { + if (ColumnExists(Constants.DatabaseSchema.Tables.User, NewColumnName)) + { + Context.Complete(); + return; + } + InvalidateBackofficeUserAccess = true; using IScope scope = _scopeProvider.CreateScope(); using IDisposable notificationSuppression = scope.Notifications.Suppress(); @@ -75,11 +81,6 @@ internal class AddGuidsToUsers : UnscopedMigrationBase private void MigrateSqlite() { - if (ColumnExists(Constants.DatabaseSchema.Tables.User, NewColumnName)) - { - return; - } - /* * We commit the initial transaction started by the scope. This is required in order to disable the foreign keys. * We then begin a new transaction, this transaction will be committed or rolled back by the scope, like normal. From d3a67fe4e0c2b22b6fb948ef52130e46d1591ea1 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Thu, 26 Sep 2024 08:45:08 +0200 Subject: [PATCH 39/41] Move all V14 User and User Group migration to pre-migrations (#17130) (cherry picked from commit 910d70302e695e0864296ef420a5a2ff77549c16) --- .../Migrations/Upgrade/UmbracoPlan.cs | 8 ++++---- .../Upgrade/UmbracoPremigrationPlan.cs | 4 ++++ .../Upgrade/V_14_0_0/AddGuidsToUserGroups.cs | 17 ++++++----------- .../Upgrade/V_14_0_0/AddGuidsToUsers.cs | 11 ++++++----- 4 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs index 7d6537d4da..713168a6c7 100644 --- a/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs +++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs @@ -67,15 +67,15 @@ public class UmbracoPlan : MigrationPlan // To 14.0.0 To("{419827A0-4FCE-464B-A8F3-247C6092AF55}"); - To("{69E12556-D9B3-493A-8E8A-65EC89FB658D}"); - To("{F2B16CD4-F181-4BEE-81C9-11CF384E6025}"); - To("{A8E01644-9F2E-4988-8341-587EF5B7EA69}"); + To("{69E12556-D9B3-493A-8E8A-65EC89FB658D}"); + To("{F2B16CD4-F181-4BEE-81C9-11CF384E6025}"); + To("{A8E01644-9F2E-4988-8341-587EF5B7EA69}"); To("{E073DBC0-9E8E-4C92-8210-9CB18364F46E}"); To("{80D282A4-5497-47FF-991F-BC0BCE603121}"); To("{96525697-E9DC-4198-B136-25AD033442B8}"); To("{7FC5AC9B-6F56-415B-913E-4A900629B853}"); To("{1539A010-2EB5-4163-8518-4AE2AA98AFC6}"); - To("{C567DE81-DF92-4B99-BEA8-CD34EF99DA5D}"); + To("{C567DE81-DF92-4B99-BEA8-CD34EF99DA5D}"); To("{0D82C836-96DD-480D-A924-7964E458BD34}"); To("{1A0FBC8A-6FC6-456C-805C-B94816B2E570}"); To("{302DE171-6D83-4B6B-B3C0-AC8808A16CA1}"); diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPremigrationPlan.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPremigrationPlan.cs index c9d23acb90..a8131a0da4 100644 --- a/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPremigrationPlan.cs +++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPremigrationPlan.cs @@ -53,5 +53,9 @@ public class UmbracoPremigrationPlan : MigrationPlan // To 14.0.0 To("{76FBF80E-37E6-462E-ADC1-25668F56151D}"); + To("{37CF4AC3-8489-44BC-A7E8-64908FEEC656}"); + To("{7BCB5352-B2ED-4D4B-B27D-ECDED930B50A}"); + To("{3E69BF9B-BEAB-41B1-BB11-15383CCA1C7F}"); + To("{F12C609B-86B9-4386-AFA4-78E02857247C}"); } } diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_14_0_0/AddGuidsToUserGroups.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_14_0_0/AddGuidsToUserGroups.cs index ad8e5091ea..67bdaf7395 100644 --- a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_14_0_0/AddGuidsToUserGroups.cs +++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_14_0_0/AddGuidsToUserGroups.cs @@ -20,6 +20,12 @@ public class AddGuidsToUserGroups : UnscopedMigrationBase protected override void Migrate() { + // If the new column already exists we'll do nothing. + if (ColumnExists(Constants.DatabaseSchema.Tables.UserGroup, NewColumnName)) + { + return; + } + // SQL server can simply add the column, but for SQLite this won't work, // so we'll have to create a new table and copy over data. if (DatabaseType != DatabaseType.SQLite) @@ -37,11 +43,6 @@ public class AddGuidsToUserGroups : UnscopedMigrationBase using IDisposable notificationSuppression = scope.Notifications.Suppress(); ScopeDatabase(scope); - if (ColumnExists(Constants.DatabaseSchema.Tables.UserGroup, NewColumnName)) - { - return; - } - var columns = SqlSyntax.GetColumnsInSchema(Context.Database).ToList(); AddColumnIfNotExists(columns, NewColumnName); @@ -68,12 +69,6 @@ public class AddGuidsToUserGroups : UnscopedMigrationBase using IDisposable notificationSuppression = scope.Notifications.Suppress(); ScopeDatabase(scope); - // If the new column already exists we'll do nothing. - if (ColumnExists(Constants.DatabaseSchema.Tables.UserGroup, NewColumnName)) - { - return; - } - // This isn't pretty, // But since you cannot alter columns, we have to copy the data over and delete the old table. // However we cannot do this due to foreign keys, so temporarily disable these keys while migrating. diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_14_0_0/AddGuidsToUsers.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_14_0_0/AddGuidsToUsers.cs index fe730fd2b8..6d5dbce1d8 100644 --- a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_14_0_0/AddGuidsToUsers.cs +++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_14_0_0/AddGuidsToUsers.cs @@ -26,6 +26,12 @@ internal class AddGuidsToUsers : UnscopedMigrationBase protected override void Migrate() { + if (ColumnExists(Constants.DatabaseSchema.Tables.User, NewColumnName)) + { + Context.Complete(); + return; + } + InvalidateBackofficeUserAccess = true; using IScope scope = _scopeProvider.CreateScope(); using IDisposable notificationSuppression = scope.Notifications.Suppress(); @@ -75,11 +81,6 @@ internal class AddGuidsToUsers : UnscopedMigrationBase private void MigrateSqlite() { - if (ColumnExists(Constants.DatabaseSchema.Tables.User, NewColumnName)) - { - return; - } - /* * We commit the initial transaction started by the scope. This is required in order to disable the foreign keys. * We then begin a new transaction, this transaction will be committed or rolled back by the scope, like normal. From 8c4780380d7523e21906d2cc17b806c0db839bf1 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Thu, 26 Sep 2024 08:51:36 +0200 Subject: [PATCH 40/41] 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 0880e2551d..a5500fd8de 160000 --- a/src/Umbraco.Web.UI.Client +++ b/src/Umbraco.Web.UI.Client @@ -1 +1 @@ -Subproject commit 0880e2551d8f0e3dc095742795f5182b9467d6a1 +Subproject commit a5500fd8de2fb14285d8f99cd3d5edeb1c5eb462 From a43db4ff0550d62f0263c14d9c9d3652e3fcb1a0 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Thu, 26 Sep 2024 08:52:40 +0200 Subject: [PATCH 41/41] 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 0880e2551d..a5500fd8de 160000 --- a/src/Umbraco.Web.UI.Client +++ b/src/Umbraco.Web.UI.Client @@ -1 +1 @@ -Subproject commit 0880e2551d8f0e3dc095742795f5182b9467d6a1 +Subproject commit a5500fd8de2fb14285d8f99cd3d5edeb1c5eb462